三、spring的AOP
AOP的基本认识
Aspect Oriented Programming,面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP的实现原理
Spring内部是使用动态代理的方式实现AOP
- JDK动态代理:只能对实现接口的类产生代理
- 产生代理对象的方法:Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,所以JDK动态代理只能对实现接口的类适用
- Cglib动态代理:可以对没有实现接口的类产生代理
- 继承这个类,生成该类的子类对象
- 所以这个类不能是final修饰的(无法继承)
- Spring实现AOP时,如果这个类实现了接口,默认使用JDK动态代理;如果这个类没实现接口,就使用Cglib产生代理
AOP的核心概念
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
- joinpoint:连接点,可以被拦截的点
- Spring中只支持方法类型的连接点,所以在Spring中连接点指的就是可以被拦截到的方法
- pointcut:切入点,对连接点进行拦截的定义,也就是真正被拦截的点 advice:方法层面的增强(也叫通知),就是指拦截到连接点之后要执行的代码
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- introduction:引介,指的是类层面的增强 (用的比较少)
- 在不修改代码的前提下,引介可以在运行期为类动态地添加一些方法或属性
- target:目标对象,需要被代理的那个类,也就是需要被增强的那个类
- proxy:代理对象,就是目标类被增强了之后就产生了一个结果代理对象
- weave:织入,将通知(advice)应用到目标对象(target)并导致代理对象创建的过程
- aspect:切面,是对横切关注点的抽象,其实就是切入点和通知的组合
AOP的简单使用
Spring有两套AOP开发的方式,一套是传统的Spring的AOP开发方式,十分繁琐,现在已经被弃用;目前Spring进行AOP开发使用的都是基于AspectJ的方式
XML方式
简单实现
- 导入相应jar,除spring开发的基础jar外,还应额外导入几个jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- com.springsource.org.aopalliance-1.0.0.jar
- spring-aop-4.2.4.RELEASE.jar
- spring-aspects-4.2.4.RELEASE.jar
- 编写目标类、切面类
- 切面类
package com.qf.aop.demo; public class MyAspectJ { public void advice() { System.out.println("========通知========"); } }
- 目标类
package com.qf.aop.demo; public interface TestDao { void add(); void delete(); void update(); void query(); }
package com.qf.aop.demo; public class TestDaoImpl implements TestDao { @Override public void add() { System.out.println("add()"); } @Override public void delete() { System.out.println("delete()"); } @Override public void update() { System.out.println("update()"); } @Override public void query() { System.out.println("query()"); } }
- 切面类
- 配置文件编写
- 引入aop的约束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
- 具体aop配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置spring管理需要被增强的目标对象 --> <bean id="testDao" class="com.qf.aop.demo.TestDaoImpl"></bean> <!-- 配置spring管理切面对象 --> <bean id="aspect" class="com.qf.aop.demo.MyAspectJ"></bean> <!-- 配置aop,实现对目标类产生代理 --> <aop:config> <!-- expression:具体哪个类的哪个方法需要增强 --> <aop:pointcut expression="execution(* com.qf.aop.demo.TestDaoImpl.delete(..))" id="pointcut01"/> <!-- 配置切面 --> <aop:aspect ref="aspect"> <!-- method:选择使用切面中的哪一个增强 --> <aop:after method="advice" pointcut-ref="pointcut01"/> </aop:aspect> </aop:config> </beans>
- 引入aop的约束
- 测试
- 测试类
package com.qf.aop.demo; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class TestDemo { @Resource(name="testDao") private TestDao testDao; @Test public void test() { testDao.add(); testDao.delete(); testDao.update(); testDao.query(); } }
- 测试结果
add() delete() ========通知======== update() query()
- 测试类
通知类型
- 前置通知:在目标方法之前进行操作
- 切面的增强方法中可以获得连接点信息
- 切面中的增强方法
public void advice(JoinPoint joinPoint) { System.out.println("========通知========"+joinPoint); }
- 配置文件
<aop:before method="advice" pointcut-ref="pointcut01"/>
- 切面中的增强方法
- 切面的增强方法中可以获得连接点信息
- 后置通知:在目标方法之后进行操作
- 可以获得目标方法的返回值
- 目标方法
@Override public String delete() { System.out.println("delete()"); return "testResult"; }
- 切面中的增强方法
public void advice(Object obj) { System.out.println("========通知========"+obj); }
- 配置文件,returning属性值和增强方法中的参数名称必须一致
<aop:after-returning method="advice" pointcut-ref="pointcut01" returning="obj"/>
- 目标方法
- 可以获得目标方法的返回值
- 环绕通知:在目标方法之前和之后都进行操作
- 切面中的增强方法
public Object advice(ProceedingJoinPoint pjp) throws Throwable { System.out.println("========环绕前通知========"); Object object = pjp.proceed(); System.out.println("========环绕后通知========"); return object; }
- 配置文件
<aop:around method="advice" pointcut-ref="pointcut01" />
- 异常通知:在目标方法出现异常时,进行操作
- 可以获取异常信息
- 目标方法中设置一个异常
public String delete() { System.out.println("delete()"); int a = 1/0; return "testResult"; }
- 切面中的增强方法
public void advice(Throwable e) { System.out.println("======异常通知=="+e.getMessage()); }
- 配置文件
<aop:after-throwing method="advice" pointcut-ref="pointcut01" throwing="e"/>
- 目标方法中设置一个异常
- 可以获取异常信息
- 最终通知:无论代码是否有异常,都会操作
<aop:after method="advice" pointcut-ref="pointcut01" />
切入表达式
- 基于execution函数
- 语法:[访问修饰符] 返回值 包名.类名.方法名(方法参数)
- *:通配符,* com.qf.test.*.*(..)表示com.qf.test包下的所有类的所有方法
- +:* com.qf.test.TestDao+.add(..)表示TestDao类和其子类下的add方法
- ..:* com.qf..*.*(..)表示com.qf包以及其子包下的所有类的所有方法
注解方式
简单实现
- 导入jar包,和XML方式的jar一致
- 编写目标类和切面类
- 编写目标类
package com.qf.aop.demo; public class TestDaoImpl implements TestDao { @Override public void add() { System.out.println("add()"); } @Override public String delete() { System.out.println("delete()"); return "testResult"; } @Override public void update() { System.out.println("update()"); } @Override public void query() { System.out.println("query()"); } }
- 编写切面类
package com.qf.aop.demo; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class MyAspectJ { //方式一: /*@Before(value="execution(* com.qf.aop.demo.TestDaoImpl.query(..))") public void advice() { System.out.println("======before通知=="); }*/ //方式二: @Pointcut(value="execution(* com.qf.aop.demo.TestDaoImpl.query(..))") private void pointcut() {}; @Before("MyAspectJ.pointcut()") public void advice() { System.out.println("======before通知=="); } }
- 编写目标类
- 编写配置文件
- 配置Spring管理bean,开启AOP注解
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置spring管理需要被增强的目标对象 --> <bean id="testDao" class="com.qf.aop.demo.TestDaoImpl"></bean> <!-- 配置spring管理切面对象 --> <bean id="aspect" class="com.qf.aop.demo.MyAspectJ"></bean> <!-- 配置打开aop注解 --> <aop:aspectj-autoproxy/> </beans>
- 配置Spring管理bean,开启AOP注解
- 测试
- 测试类
package com.qf.aop.demo; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class TestDemo { @Resource(name="testDao") private TestDao testDao; @Test public void test() { testDao.add(); testDao.delete(); testDao.update(); testDao.query(); } }
- 测试结果
add() delete() update() ======before通知== query()
- 测试类