4. Spring AOP
4.1 Spring AOP的基本概念
4.1.1 AOP的概念
AOP ( Aspect-Oriented Programming )即面向切面编程,它与OOP (Object-Oriented Programming , 面向对象编程) 相辅相成,提供了与OOP 不同的抽象软件结构的视角。在OOP 中,以类作为程序的基本单元,而AOP 中的基本单元是Aspect (切面)。Struts2的拦截器设计就是基于AOP 的思想,是个比较经典的应用。
4.1.2 AOP的术语
- 切面
切面(Aspect)是指封装横切到系统功能(例如事务处理)的类。 - 连接点
连接点(Joinpoint)是指程序运行中的一些时间点,例如方法的调用或异常的抛出。 - 切人点
切入点(Pointcut)是指需要处理的连接点。在Spring AOP 中,所有的方法执行都是连接点,而切入点是一个描述信息,它修饰的是连接点,通过切入点确定哪些连接点需要被处理。
- 通知
通知(Advice)是由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点处所要执行的程序代码,可以将其理解为切面开启后切面的方法,因此通知是切面的具体实现。 - 引人
引入(Introduction)允许在现有的实现类中添加自定义的方法和属性。 - 目标对象
目标对象(Target Object)是指所有被通知的对象。如果AOP 框架使用运行时代理的方式(动态的AOP )来实现切面,那么通知对象总是一个代理对象。 - 代理
代理(Proxy)是通知应用到目标对象之后被动态创建的对象。 - 织人
织入(Weaving)是将切面代码插入到目标对象上,从而生成代理对象的过程。根据不同的实现技术, AOP 织入有3 种方式:编译期织入, 需要有特殊的Java 编译器;类装载期织入, 需要有特殊的类装载器;动态代理织入,在运行期为目标类添加通知生成子类的方式。SpringAOP 框架默认采用动态代理织入,而AspectJ(基于Java 语言的AOP 框架)采用编译期织入和类装载期织入。
4.2 动态代理
在Java中有多种动态代理技术,例如JDK、CGLIB、Javassist、ASM,其中最常用的动态代理技术是JDK和CGLIB。目前,在Spring AOP中常用JDK和CGLIB两种动态代理技术。
4.2.1 JDK动态代理
JDK 动态代理是java.lang . reflect. *包提供的方式,它必须借助一个接口才能产生代理对象。因此,对于使用业务接口的类, Spring 默认使用JDK 动态代理实现AOP 。
实例演示:
-
创建应用,创建接口及实现类,在src的目录下创建一个dynamic.jdk包,在该包中创建接口TestDao和接口实现类TestDaoImpl。该实现类作为目标类,在代理类中对其方法进行增强处理。
TestDao的代码如下:
package dynamic.jdk; public interface TestDao { public void save(); public void modify(); public void delete(); }
TestDaoImpl的代码如下:
package dynamic.jdk; public class TestDaoImpl implements TestDao { @Override public void save() { System.out.println("保存"); } @Override public void modify() { System.out.println("修改"); } @Override public void delete() { System.out.println("删除"); } }
-
创建切面类
在src目录下创建一个aspect包,在该包下创建切面类MyAspect,注意在该类中可以定义多个通知(增强处理的功能方法)。
MyAspect的代码如下:
package aspect; /** * 切面类,可以定义多个通知,即增强处理的方法 */ public class MyAspect { public void check() { System.out.println("模拟权限控制"); } public void except() { System.out.println("模拟一场处理"); } public void log() { System.out.println("模拟日志记录"); } public void monitor() { System.out.println("性能检测"); } }
-
创建代理类
在dynamic.jdk包中创建代理类JDKDynamicProxy。在JDK动态代理中代理类必须实现java.lang.reflect.InvocationHandler接口,并编写代理方法,在代理方法中需要通过Proxy实现动态代理。
JDKDynamicProxy的代码如下:
package dynamic.jdk; import aspect.MyAspect; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKDynamicProxy implements InvocationHandler { // 声明目标类接口对象(真实对象) private TestDao testDao; // 创建代理的方法,建立代理对象和真实对象的代理关系,并返回代理对象 public Object createProxy(TestDao testDao) { this.testDao = testDao; // 1. 类加载器 ClassLoader cld = JDKDynamicProxy.class.getClassLoader(); // 2. 被代理对象实现的所有接口 Class[] clazz = testDao.getClass().getInterfaces(); // 3. 使用代理类进行增强,返回代理后的对象 return Proxy.newProxyInstance(cld, clazz, this); } /** * 代理的逻辑方法,所有动态代理类的方法调用都交给该方法处理 * proxy是被代理对象 * method是将要被执行的方法 * args是执行方法时需要的参数 * return指返回代理结果 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 创建一个切面 MyAspect myAspect = new MyAspect(); // 前增强 myAspect.check(); myAspect.except(); // 在目标类上调用方法标并传入参数,相当于调用testDao中的方法 Object obj = method.invoke(testDao, args); // 后增强 myAspect.log(); myAspect.monitor(); return obj; } }
-
创建测试类
在dynamic.jdk包中创建测试类JDKDynamicTest。在主方法中创建代理对象和目标对象,然后从代理对象中获取对目标对象增强后的对象,最后调用该对象的添加、修改和删除方法。
JDKDynamicTest的代码如下:
package dynamic.jdk; public class JDKDynamicTest { public static void main(String[] args) { // 创建代理对象 JDKDynamicProxy jdkProxy = new JDKDynamicProxy(); // 创建目标对象 TestDao testDao = new TestDaoImpl(); // 从代理对象中获取增强后的目标对象,该对象是一个被代理的对象,它会进入代理的逻辑方法invoke中 TestDao testDaoAdvice = (TestDao) jdkProxy.createProxy(testDao); // 执行方法 testDaoAdvice.save(); System.out.println("==============="); testDaoAdvice.modify(); System.out.println("==============="); testDaoAdvice.delete(); } }
-
运行效果:
4.2.2 CGLIB动态代理
JDK 动态代理必须提供接口才能使用,对于没有提供接口的类,只能采用CGLIB 动态代理。
CGLIB (Code Generation Library ) 是一个高性能开源的代码生成包,采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。在Spring Core 包中己经集成了CGLIB 所需要的JAR 包,不需要另外导入JAR 包。
实例演示:
-
创建目标类
在src目录下创建一个dynamic.cglib包,在该包中创建目标类TestDao,注意该类不需要实现任何接口。
TestDao的代码如下:
4.3 基于代理类的AOP实现
在Spring中默认使用JDK动态代理实现AOP编程。使用org.springframework.aop.framwork.ProxyFactoryBean创建代理是Spring AOP实现的最基本方式。
4.3.1 通知类型
根据Spring中通知在目标类方法中的连接点位置,通知可以分为6种类型。
4.3.1.1 环绕通知
环绕通知(org.aopallicance.intercept.MethodIntercerptor)是在目标方法执行前和执行后实施增强,可应用于日志记录、事务处理等功能。
4.3.1.2 前置通知
前置通知(org.springframework.aop.MethodBeforeAdvice)实在目标方法执行前实施增强,可应用于权限管理等功能。
4.3.1.3 后置返回通知
后置返回通知(org.springframework.aop.AfterReturningAdvice)是在目标方法成功执行后实施增强,可应用于关闭流、删除临时文件等功能。
4.1.3.5 后置(最终)通知
后置通知(org.springframework.aop.AfterAdvice)是在目标方法执行后实施增强,与后置返回通知不同的是,不管是否发生异常都要执行该类通知,该类通知可应用于释放资源。
4.1.3.6 引入通知
引入通知(org.springframework.aop.IntroductionInterceptor)是在目标类中添加一些新的方法和属性,可应用于修改目标类(增强类)。
4.3.2 ProxyFactoryBean
ProxyFactoryBean是org.springframework.beans.factory.FactoryBean接口的实现类,FactoryBean负责实例化一个Bean实例,ProxyFactoryBean负责为其他Bean实例创建代理实例。
*** ProxyFactoryBean类的常用属性**:
4.4 基于XML配置开发AspectJ
AspectJ是一个基于Java语言的AOP框架。使用AspectJ实现Spring AOP的方式有两种,一是基于XML配置开发AspectJ,二是基于注解开发AspectJ。
基于XML配置开发AspectJ是指通过XML配置文件定义切面、切入点及通知,所有这些定义都必须在<aop:config>
元素内。<aop:config>
元素及其子元素如下表所示:
各类型通知与目标方法的执行过程,具体过程如图所示:
4.5 基于注解开发AspectJ
基于注解开发AspectJ要比基于XML配置开发AspectJ便捷许多,所以在实际开发中推荐使用注解方法。
AspectJ注解,如下表所示: