说说Spring AOP
一、什么是Spring AOP?
在Java应用程序中,有许多和应用业务代码无关的通用功能代码,但是业务程序又离不开这些通用功能代码,如下图,一个订单功能的保存和取消接口,这两个接口在访问过程中,通常都会经过权限验证、日志记录、数据缓存、事务提交等通用内容,如果在每个接口的业务代码中都嵌入这些功能的代码,无疑会让业务代码的维护难度增加。于是Spring就这些功能代码从开发者手中转移Spring框架中处理,Spring在横向的业务接口中提供了一个切面,将通用的功能代码放入切面中处理,这种面向切面的处理方式就被称为AOP(Aspect Oriented Programming 面向切面编程),AOP实际上是在Spring框架中对原业务对象做功能增强来实现通用功能,基于动态代理来完成这种增强,后面具体来说;
二、怎么实现Spring AOP?
Spring框架的模块有很多,学习的时候,只引入相关模块,可以让我们更好理解各模块本身的功能,及模块之间的关系,所以我们只引入spring-context、spring-aop和spring-aop依赖的aspectjrt及aspectjweaver。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.4</version> </dependency> <!-- Spring AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!-- Spring AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.4</version> </dependency>
Spring AOP的实现方式有两种分别是基于传统的XML配置实现和基于@Aspect注解配置实现,下面的代码中,我们假设有订单服务OrderService,分别有订单保存接口saveOrder()和取消接口delOrder(),通过日志记录切面类AuditLogAspect ,实现对OrderService的AOP,下面就分别使用Demo说明:
1、基于传统的XML配置实现
application.xml文件
<?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" xmlns:context="http://www.springframework.org/schema/context" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置Bean自动扫描的包路径 --> <context:component-scan base-package="com.fan.spring.aop"></context:component-scan> <bean id="auditLogAspect" class="com.fan.spring.aop.aspect.AuditLogAspect"></bean> <!-- 开启AOP,对应@EnableAspectJAutoProxy注解 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- AOP切面配置 --> <aop:config> <aop:aspect ref="auditLogAspect"> <aop:pointcut id="pointCut" expression="execution(* com.fan.spring.aop.service.*.*(..))"/> <aop:before method="before" pointcut-ref="pointCut"/> <aop:after method="after" pointcut-ref="pointCut" /> <aop:after-returning method="afterReturn" pointcut-ref="pointCut" returning="result"/> <aop:around method="around" pointcut-ref="pointCut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="ex"/> </aop:aspect> </aop:config> </beans>
import org.springframework.stereotype.Component; @Component public class OrderService { public Integer saveOrder() { System.out.println("订单保存成功"); return 0; } public Integer delOrder() { System.out.println("订单取消成功"); return 0; } }
切面类
@Component public class AuditLogAspect { /** * 执行目标方法前执行前置方法,JoinPoint对象封装了SpringAop中目标方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象. **/ public void before(JoinPoint joinPoint) { System.out.println("访问前执行..."); } /** * 目标方法执行完成执行后置方法 **/ public void after(JoinPoint joinPoint) { System.out.println("访问后执行..."); } /** * 目标方法执行完成后返回目标方法的返回值 **/ public void afterReturn(JoinPoint joinPoint, Object result) { System.out.println("方法返回值:" + result); } /** * 目标方法执行异常后执行方法 **/ public void afterThrowing(JoinPoint joinPoint,Exception ex) { System.out.println("异常:" + ex.getMessage()); } /** * 伴随目标方法一起执行,目标方法开始后开始执行,目标方法执行结束后结束执行。ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中 **/ public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行doAround开始========="); // 调用方法的参数 Object[] args = pjp.getArgs(); // 调用的方法名 String method = pjp.getSignature().getName(); // 获取目标对象 Object target = pjp.getTarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 Object result = pjp.proceed(); System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); System.out.println("======执行doAround结束========="); return result; } }
Spring入口类
import com.fan.spring.aop.service.OrderService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopDemo { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); OrderService orderService = applicationContext.getBean(OrderService.class); orderService.saveOrder(); orderService.delOrder(); } }
2、基于@Aspect注解配置
application.xml文件
<?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" xmlns:context="http://www.springframework.org/schema/context" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置Bean自动扫描的包路径 --> <context:component-scan base-package="com.fan.spring.aop"></context:component-scan> <bean id="auditLogAspect" class="com.fan.spring.aop.aspect.AuditLogAspect"></bean> <!-- 开启AOP,对应@EnableAspectJAutoProxy注解 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
切面类
@Component @Aspect public class AuditLogAspect { @Pointcut(value = "execution(* com.fan.spring.aop.service.*.*(..))") public void pointCut(){} /** * 执行目标方法前执行前置方法 **/ @Before(value = "pointCut()") public void before(JoinPoint joinPoint) { System.out.println("访问前执行..."); } /** * 目标方法执行完成执行后置方法 **/ @After(value = "pointCut()") public void after(JoinPoint joinPoint) { System.out.println("访问后执行..."); } /** * 目标方法执行完成后返回目标方法的返回值 **/ @AfterReturning(value = "pointCut()", returning = "result") public void afterReturn(JoinPoint joinPoint, Object result) { System.out.println("方法返回值:" + result); } /** * 目标方法执行异常后执行方法 **/ @AfterThrowing(value = "pointCut()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex) { System.out.println("异常:" + ex.getMessage()); } /** * 伴随目标方法一起执行,目标方法开始后开始执行,目标方法执行结束后结束执行。 **/ @Around(value = "pointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行doAround开始========="); // 调用方法的参数 Object[] args = pjp.getArgs(); // 调用的方法名 String method = pjp.getSignature().getName(); // 获取目标对象 Object target = pjp.getTarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 Object result = pjp.proceed(); System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); System.out.println("======执行doAround结束========="); return result; } }
Spring入口类
import com.fan.spring.aop.service.OrderService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopDemo { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); OrderService orderService = applicationContext.getBean(OrderService.class); orderService.saveOrder(); orderService.delOrder(); } }
在上述的方式中,分别有@Before、@After、@AfterReturning、@AfterThrowing、@Around等五个注解修饰方法,它们的功能已经在代码中说明,他们的执行顺序分别是@Before->@After->@AfterReturning,@Around伴随目标一起执行,目标抛出异常时会进入@AfterThrowing的方法,不会执行@AfterReturning,但依然会执行@After,而@Around会执行pjp.proceed()后的内容。
三、Spring AOP的底层原理
Spring AOP实际上是在Spring框架中对原业务对象做了功能增强,这种增强动态代理来实现。
代理模式介绍
代理模式(Proxy Pattern)是通过一个类对另一个类方法的功能做增强的一种结构性设计模式,下面是UML图。
什么是动态代理?
代理模式有静态代理和动态代理,静态代理就是有开发者直接创建好代理类和原类之间的关系,比如继承、组合等方式实现代理,在编译之前,这种代理关系就已经确定。动态代理就是在程序运行期间,再去根据代理信息创建代理类,实现对原类的代理,动态代理通过Java反射的方式实现,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
动态代理方式有两种分别是Java动态代理和CGLib代理,Java动态代理基于interface接口实现,代理类会实现与原类相同的接口,CGLib代理基于类继承实现,代理类会继承原类,AOP会根据是否实现接口来决定使用那种代理方式。由于是动态代理没有对应的代理类class文件,所以只能通过IDEA工具查看对应代理类结构,通过上述截图可以发现,无论是哪种代理都是在代理类中持有一个原类对象,所以两种代理方式都是在重写原对象的方法同时通过调用持有的原类对象方法实现了功能增强,另外,代理类只会去处理代理的目标方法,并不会去填充原类中的属性,如OrderService中一个orderMapper属性,实际上在代理类中orderMapper = null,并给orderMapper填充属性没有什么实际的意义。动态代理其效果如下:
//CGLib代理类示例 class OrderServiceProxy extends OrderService { // 代理类持有一个原类对象orderService,而不是直接使用super.saveOrder(),而是使用orderService.saveOrder() OrderService orderService; // 诸如这样原类属性,代理类并不会填充属性,所以代理类中orderMapper == null,实际上这个属性在代理中也不会被使用 OrderMapper orderMapper; //前置方法before public void before() {} //后置方法after public void after() {} //... 还有很多切面方法,并且带有JoinPoint参数 @Override public Integer saveOrder() { //执行 前置方法 // ... Integer res = orderService.saveOrder(); //执行后置方法 // ... return res; } }
AOP代理的类前提都是Spring容器的Bean,所以AOP在源码中的实现也是在Bean创建的过程中,并且AOP也是Bean创建完成的最后一步,具体,源码在Spring的后置处理器中,即源码中org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors的方法中。