问题?SpringAop的定义及使用
Aop定义:
aop:
1、切面
事务、日志、安全性框架、权限等都是切面
2、通知
切面中的方法就是通知
3、目标类
4、切入点
只有符合切入点,才能让通知和目标方法结合在一起
5、织入:
形成代理对象的方法的过程
好处:
事务、日志、安全性框架、权限、目标方法之间完全是松耦合的
execution详解:
execution(public * *(..)) 所有的公共方法
execution(*set*(..)) 以set开头的任意方法
execution(*com.xyz.service.AccountService.*(..))com.xyz.service.AccountService类中的所有的方法
execution(*com.xyz.service.*.*(..)) com.xyz.service包中的所有的类的所有的方法
execution(*com.xyz.service..*.*(..))com.xyz.service包及子包中所有的类的所有的方法
execution(*cn.itcast.spring.sh..*.*(String,?,Integer)) cn.itcast.spring.sh包及子包中所有的类的有三个参数
第一个参数为String,第二个参数为任意类型,
第三个参数为Integer类型的方法
加载过程:
springAOP的具体加载步骤:
1、当spring容器启动的时候,加载了spring的配置文件
2、为配置文件中所有的bean创建对象
3、spring容器会解析aop:config的配置
1、解析切入点表达式,用切入点表达式和纳入spring容器中的bean做匹配
如果匹配成功,则会为该bean创建代理对象,代理对象的方法=目标方法+通知
如果匹配不成功,不会创建代理对象
4、在客户端利用context.getBean获取对象时,如果该对象有代理对象则返回代理对象,如果代理对象,则返回目标对象
说明:如果目标类没有实现接口,则spring容器会采用cglib的方式产生代理对象,如果实现了接口,会采用jdk的方式
通知:
通知:
1、前置通知
1、在目标方法执行之前执行
2、无论目标方法是否抛出异常,都执行,因为在执行前置通知的时候,目标方法还没有执行,还没有遇到异常
2、后置通知
1、在目标方法执行之后执行
2、当目标方法遇到异常,后置通知将不再执行
3、后置通知可以接受目标方法的返回值,但是必须注意:
后置通知的参数的名称和配置文件中returning="var"的值是一致的
3、最终通知:
1、在目标方法执行之后执行
2、无论目标方法是否抛出异常,都执行,因为相当于finally
4、异常通知
1、接受目标方法抛出的异常信息
2、步骤
在异常通知方法中有一个参数Throwable ex
在配置文件中
<aop:after-throwingmethod="throwingMethod" pointcut-ref="perform"throwing="ex"/>
5、环绕通知
1、如果不在环绕通知中调用ProceedingJoinPoint的proceed,目标方法不会执行
2、环绕通知可以控制目标方法的执行
联合hibernate+spring的案例:
hibernate工具类:
package cn.itcast.spring.sh.aop; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; public class HibernateUtils { public static SessionFactory sessionFactory; static{ Configuration configuration = new Configuration(); configuration.configure("cn/itcast/spring/sh/aop/hibernate.cfg.xml"); sessionFactory = configuration.buildSessionFactory(); // Session session = sessionFactory.openSession(); // Transaction transaction = session.beginTransaction(); // transaction.commit(); } }
package cn.itcast.spring.sh.aop; import java.io.Serializable; public class Person implements Serializable{ private Long pid; private String pname; private String psex; public Person(){} public Person(String pname){ this.pname = pname; } public Long getPid() { return pid; } public void setPid(Long pid) { this.pid = pid; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public String getPsex() { return psex; } public void setPsex(String psex) { this.psex = psex; } }
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 用来描述一个持久化类 name 类的全名 table 可以不写 默认值和类名一样 catalog 数据库的名称 一般不写 --> <class name="cn.itcast.spring.sh.aop.Person"> <!-- 标示属性 和数据库中的主键对应 name 属性的名称 column 列的名称 --> <id name="pid" column="pid" length="200" type="java.lang.Long"> <!-- 主键的产生器 就该告诉hibernate容器用什么样的方式产生主键 --> <generator class="increment"></generator> </id> <!-- 描述一般属性 --> <property name="pname" column="pname" length="20" type="string"></property> <property name="psex" column="psex" length="10" type="java.lang.String"></property> </class> </hibernate-mapping>
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <!-- 一个session-factory只能连接一个数据库 --> <session-factory> <!-- 数据库的用户名 --> <property name="connection.username">root</property> <!-- 密码 --> <property name="connection.password">root</property> <!-- url --> <property name="connection.url"> jdbc:mysql://localhost:3306/Use_Aop </property> <!-- 作用:根据持久化类和映射文件生成表 validate create-drop create update --> <property name="hbm2ddl.auto">update</property> <!-- 显示hibernate内部生成的sql语句 --> <property name="show_sql">true</property> <property name="current_session_context_class">thread</property> <!-- 添加映射文件 --> <mapping resource="cn/itcast/spring/sh/aop/Person.hbm.xml" /> </session-factory> </hibernate-configuration>
package cn.itcast.spring.sh.aop; public interface PersonDao { public String savePerson(Person person); }
package cn.itcast.spring.sh.aop; import java.lang.reflect.Method; import org.junit.Test; public class PersonDaoImpl extends HibernateUtils implements PersonDao { public String savePerson(Person person) { //在这里首先得开启事务才能crud操作,Springaop配置已经准备好动态代理了 //int a=1/0; sessionFactory.getCurrentSession().save(person); System.out.println("执行保存操作了。"); return "aaa"; } }
package cn.itcast.spring.sh.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.hibernate.Transaction; public class MyTransaction extends HibernateUtils { private Transaction transaction; /** * 通过一些参数可以获取目标方法的相关信息,报告目标方法名称、返回值参数、异常信息等 * 不过相应的也要在配置文件中进行设置(通过实验,好像只配置后置及异常就可以只用了),不然就获取不了。 * 注:环绕通知不可以跟前置通知和后置通知一起用,因为环绕通知已经把两个通知的东西做了,它可以调用目标方法 * * */ public void beginTransaction(JoinPoint joinPoint){ //可以通过该参数得到目标方法名称(导的是这个包:import org.aspectj.lang.JoinPoint;) System.out.println(joinPoint.getSignature().getName()); //开启事务 System.out.println("开启事务"); this.transaction = sessionFactory.getCurrentSession().beginTransaction(); } //这里的var参数是后置通知,得到目标方法的返回值 public void commit(Object var){ //提交事务 this.transaction.commit(); System.out.println(var); System.out.println("关闭事务"); } //测试最终通知 // public void finallFun(){ // System.out.println("最终通知发生了"); // } //测试异常通知 //throwing="ag":表示异常信息参数,和配置文件中是相对应的 // public void exception(Throwable ag){ // System.out.println("异常信息:"+ag); // } //测试环绕异常通知 // public void aroundException(ProceedingJoinPoint joitPoint) throws Throwable{ // //joitPoint.proceed();//调用目标方法,不然目标方法不能执行,这个时候前置和后置都已经不用了 // System.out.println("环绕通知发生"); // } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- SpringAop案例 使用条件:必须有 xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" --> <!-- 导入目标类 --> <bean id="personDao" class="cn.itcast.spring.sh.aop.PersonDaoImpl"></bean> <bean id="myTransaction" class="cn.itcast.spring.sh.aop.MyTransaction"></bean> <!-- aop配置 --> <aop:config> <!-- 切入点表达式 --> <aop:pointcut expression="execution(* cn.itcast.spring.sh.aop.PersonDaoImpl.*(..))" id="perform"/> <!-- aop:aspect:引入切面 前置通知:aop:before:目标方法没执行前执行该方法 后置通知:aop:after-returning:目标方法执行后执行该方法。目标方法有异常,不会执行了, 这里的var是后置通知的参数,可以得到目标方法的返回值,并且和方法中所带参数(Object var)一致 最终通知::aop:after :表示目标方法执行后才执行,不管目标方法有没有异常,都会执行。 异常通知:aop:after-throwing:当目标方法出现错误了,就会执行该方法,throwing="ag":表示异常信息参数,在方法中带参数(Throwable ag)(可以使用 环绕通知:aop:around 可以控制目标方法的执行(注:环绕与前置、后置不能一起用.) pointcut-ref: 引入切入点 returning="var":代表接受目标类方法中的返回值 --> <aop:aspect ref="myTransaction"> <aop:before method="beginTransaction" pointcut-ref="perform"/> <aop:after-returning method="commit" pointcut-ref="perform" returning="var"/> <!-- <aop:after method="finallFun" pointcut-ref="perform"/> <aop:after-throwing method="exception" throwing="ag" pointcut-ref="perform"/> --> <!-- <aop:around method="aroundException" pointcut-ref="perform"/> --> </aop:aspect> <!-- 通知类型: 前置通知(Before advice):在某连接点之前执行的通知, 但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。 后置通知(After returning advice):在某连接点正常完成后执行的通知: 例如,一个方法没有抛出任何异常,正常返回。 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。 最终通知(After (finally) advice):当某连接点退出的时候执行的通知 (不论是正常返回还是异常退出)。 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。 这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。 它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。 --> </beans>
package cn.itcast.spring.sh.aop; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class PersonTest { @Test public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cn/itcast/spring/sh/aop/applicationContext.xml"); PersonDao personDao = (PersonDao)applicationContext.getBean("personDao"); Person person1 = new Person(); person1.setPname("李大鹏"); person1.setPsex("男"); personDao.savePerson(person1); } }
spring动态代理:
异常信息的执行结果:
注意:继承接口的是cglib动态代理方式,而在getbean的时候产生的不是接口的对象而是动态代理的对象