Spring AOP解析(1)--AOP的简介及使用

前言

软件开发的目的是为了解决各种需求,而需求又分成业务需求和系统需求,比如有一个登录功能,那么用户输入密码之后登录就是业务需求,而在用户登录前后分别打印一行日志,这个就是系统需求;又或者用户访问系统的网页获取数据这个是业务需求,而用户每一次访问的时候,都需要进行一次用户权限校验,这个就是系统需求。可以看出业务需求是用户感知的,而系统需求是用户无感知的。业务需求和实现代码的对应关系往往是一一对应的关系,比如登录需求,就需要开发一个登录的接口;注册需求就需要开发一个注册接口。而系统需求往往是一对多的关系,比如打印用户操作日志功能,用户注册时需要打印日志,用户登录时还是需要打印日志。而如果在实现业务代码的时候,将系统需求的代码手动写入进去,那么就会导致系统需求的代码需要在每个业务代码中都需要加入,很显然就会导致很多的问题,比如维护比较困难,一旦需要改系统需求的代码,就需要将所有业务代码中的系统需求代码全部改一遍。如果我们将系统需求的实现代码抽离出来,由系统自动将系统需求的代码插入到业务代码中,很显然就解决了这个问题。而Spring的AOP思想就是这样的设计思想

一、AOP简介

AOP,全称是Aspect Oriented Programming,也叫做面向方面编程,或者叫面向切面编程。比如日志打印、权限校验等系统需求就像一把刀一样,横切在各个业务功能模块之上,在AOP中这把刀就叫做切面。

1.1、AOP基本概念

joinPoint(连接点、切入点):表示可以将横切的逻辑织入的地方,比如方法调用、方法执行、属性设置等

pointCut(切点):通过表达式定义的一组joinPoint的集合,比如定义一个pointCut为"com.lucky.test包下的所有Service中的add方法",这样就可以定义哪些具体的joinPoint需要织入横切逻辑

Advice(增强):横切的具体逻辑,比如日志打印,权限校验等这些系统需求就需要在业务代码上增强功能,这些具体的横切逻辑就叫做Advice

aspect(切面):切点和增强组合一起就叫做切面,一个切面就定义了在哪些连接点需要织入什么横切逻辑

target(目标):需要织入切面的具体目标对象,比如在UserService类的addUser方法前面织入打印日志逻辑,那么UserService这个类就是目标对象

weaving(织入):将横切逻辑添加到目标对象的过程叫做织入

用这些概念造句总结就是:在target的joinPoint处weaving一个或多个以Advice和pointCut组成的Aspect

1.2、增强的类型

增强根据执行时机和完成功能的不同分成以下几种类型

1.2.1、前置增强(Before Advice),在joinPoint代码执行之前执行,比如可以在前置增强中进行权限校验或者参数校验等,不合法的请求就可以直接在前置增强中处理掉了。

1.2.2、后置增强(After Advice),后置增强根据时机又分成三种类型

1.2.2.1、返回增强(After Returning Advice)当joinPoint方法正常执行并返回结果时,返回增强才会执行

1.2.2.2、异常增强(After throwing Advice)当joinPoint方法抛异常之后,异常增强才会执行

1.2.2.3、最终增强(After Advice)无论joinPoint如何执行,都会执行最终增强,相当于是在finally中执行的逻辑一样

1.2.3、环绕增强(Around Advice)在joinPoint方法执行之前和之后都会执行增强逻辑,环绕增强相当于同时实现了前置增强和后置增强的功能

1.2.4、附加增强(Introuction)在不改变目标类的定义的情况下,为目标类附加了新的属性和行为,这就好比开发人员本来只需要干开发的工作,但是如果测试资源不足需要开发也参与测试工作,那么就需要在保持开发人员定义不变的情况下,附加测试人员的角色给开发人员身上,但是并没有改变此人是一个开发人员的本质。

1.3、织入的分类

根据织入的时机可以分成三种:

编译期织入:在编译时期就将Advice织入到目标对象中

类加载期织入:在目标对象所在类加载的时候织入Advice

运行时动态织入:在目标对象的方法运行时,动态织入Advice

二、AOP的使用

Spring2.0开始支持@Aspect注解形式的AOP实现,使用案例如下:

首先需要定义一个切面,通过在类上添加@Aspect可以表示当前类是一个切面,代码如下:

 1 /**
 2  * @Aspect 表示当前类是一个切面
 3  * @Component 表示当前类会在Spring容器中初始化bean
 4  * */
 5 @Aspect
 6 @Component
 7 public class TimeAspect {
 8 
 9     private final static ThreadLocal<Long> localTime = new ThreadLocal<>();
10 
11     /**
12      * 前置增强:方法执行之前执行
13      * */
14     @Before("execution(* com.lucky.test.spring.aop..*.add*(..))")
15     public void beforeMethod(JoinPoint joinPoint){
16         localTime.set(System.currentTimeMillis());
17         String methodName = joinPoint.getSignature().getName();
18         System.out.println("前置通知:" + methodName);
19     }
20 
21     /** 后置增强:方法执行之后执行*/
22     @After("execution(* com.lucky.test.spring.aop..*.add*(..))")
23     public void afterMethod(JoinPoint joinPoint){
24         String methodName = joinPoint.getSignature().getName();
25         System.out.println("后置通过:"+methodName+",耗时:" + (System.currentTimeMillis() - localTime.get()));
26     }
27 
28     /**
29      * 环绕增强:方法执行之前和之后均执行,分别在前置和后置增强的前面执行
30      * */
31     @Around("execution(* com.lucky.test.spring.aop..*.*(..))")
32     public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
33         Long startTime = System.currentTimeMillis();
34         String methodName = joinPoint.getSignature().getName();
35         System.out.println("环绕通知开始:" + methodName);
36         Object result = joinPoint.proceed();//执行业务具体业务逻辑
37         System.out.println("环绕通知结束:" + methodName + ",耗时:" + (System.currentTimeMillis() - startTime));
38         return result;
39     }
40 
41     /**
42      * 异常增强:方法抛出异常之后会执行,如果方法没有抛异常则不执行
43      * */
44     @AfterThrowing(value = "execution(* com.lucky.test.spring.aop..*.*(..))", throwing = "e")
45     public void afterException(JoinPoint point, Exception e){
46         String method = point.getSignature().getName();
47         System.out.println("异常增强:" + method + "抛异常:" + e.getMessage());
48     }
49 
50     /** 返回增强:方法返回之后会执行,如果方法没有返回值则不执行*/
51     @AfterReturning(value = "execution(!void com.lucky.test.spring.aop..*.*())", returning = "res")
52     public String afterReturn(JoinPoint point, Object res){
53         String method = point.getSignature().getName();
54         System.out.println("返回增强:" + method + "返回结果:" + res);
55         return res.toString();
56     }
57 }

本案例中在TimeAspect上添加注解@Aspect表示当前类是一个切面,添加@Component注解表示当前类需要初始化一个bean到Spring容器中

@Before注解修饰方法表示当前方法会在连接点方法之前执行

@After注解修饰方法表示当前方法会在连接点之后执行

@Arount注解修饰方法表示当前方法会在连接点之前和之后均会执行

@AfterThrowing注解修饰方法表示当前方法会在连接点抛异常之后执行,如果不抛异常则不会执行

@AfterReturning注解修饰方法表示当前方法会在连接点返回结果时执行,如果连接点方法是void,则该注解不会生效

通过以上注解可以相当于定义了Advice,然后还需要定义切点,通过execution表达式来定义,如案例中的注解后面的execution表达式

* com.lucky.test.spring.aop..*.add*(..):表示拦截com.lucky.test.spring.aop包下的所有类中的以add为前缀的方法,方法参数无限制,方法返回类型无限制
* com.lucky.test.spring.aop..*.*(..):表示拦截com.lucky.test.spring.aop包下的所有类中的所有方法,方法参数无限制,方法返回类型无限制
!void com.lucky.test.spring.aop..*.*():表示拦截com.lucky.test.spring.aop包下的所有类中的所有方法,方法参数无限制,方法返回类型不可以是void修饰,也就是方法需要return结果

所以TimeAspect定义的切面效果为:

1、com.lucky.test.spring.aop包下的所有类中的add前缀的方法添加了前置增强和后置增强

2、com.lucky.test.spring.aop包下的所有类中的所有方法添加了环绕增强和异常增强

3、com.lucky.test.spring.aop包下的所有类中的所有有return结果的方法添加了返回增强

测试案例,假设有两个测试接口定义和实现分别如下:

public interface GoodsService {

    public void addGoods();

    public String getGoods();
}
1 public interface OrderService {
2 
3     public void addOrder();
4 
5     public void getOrder();
6 }
 1 public class GoodsServiceImpl implements GoodsService {
 2 
 3     @Override
 4     public void addGoods() {
 5         System.out.println("处理addGoods方法业务逻辑");
 6     }
 7 
 8     @Override
 9     public String getGoods() {
10         System.out.println("处理getGoods方法业务逻辑");
11         return "myGoods";
12     }
13 }
 1 public class OrderServiceImpl implements OrderService {
 2 
 3     @Override
 4     public void addOrder() {
 5         System.out.println("处理addOrder方法");
 6         throw new RuntimeException("订单号不存在");
 7     }
 8 
 9     @Override
10     public void getOrder() {
11         System.out.println("处理getOrder方法");
12     }
13 }

测试方法如下:

 1 public static void main(String[] args) throws Exception {
 2 
 3         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
 4         GoodsService goodsService = context.getBean(GoodsService.class);
 5         OrderService orderService = context.getBean(OrderService.class);
 6 
 7         goodsService.addGoods();
 8         System.out.println("******************");
 9         goodsService.getGoods();
10         System.out.println("******************");
11         orderService.addOrder();
12 
13     }

调用goodsService的addGoods方法,该方法会执行前置增强、后置增强、环绕增强

调用goodsService的getGoods方法,该方法会执行环绕增强、返回增强

调用orderService的addOrder方法,由于addOrder方法抛了异常,或者该方法会执行前置增强、后置增强、环绕增强(前置处理)和异常增强,而不会执行环绕增强的后置处理,因为一旦抛了异常,后面的逻辑就不会再执行了,包括环绕增强的后置处理

执行结果如下:

 1 环绕通知开始:addGoods
 2 前置通知:addGoods
 3 处理addGoods方法业务逻辑
 4 环绕通知结束:addGoods,耗时:2
 5 后置通过:addGoods,耗时:0
 6 ******************
 7 环绕通知开始:getGoods
 8 处理getGoods方法业务逻辑
 9 环绕通知结束:getGoods,耗时:0
10 返回增强:getGoods返回结果:myGoods
11 ******************
12 环绕通知开始:addOrder
13 前置通知:addOrder
14 处理addOrder方法
15 后置通过:addOrder,耗时:0
16 异常增强:addOrder抛异常:订单号不存在
17 Exception in thread "main" java.lang.RuntimeException: 订单号不存在
18     at com.lucky.test.spring.aop.impl.OrderServiceImpl.addOrder(OrderServiceImpl.java:15)
19     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
20     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
21     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
22     at java.lang.reflect.Method.invoke(Method.java:498)
23     at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
24     at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
25     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
26     at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
27     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
28     at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
29     at com.lucky.test.spring.aop.TimeAspect.aroundMethod(TimeAspect.java:49)
30     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
31     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
32     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
33     at java.lang.reflect.Method.invoke(Method.java:498)
34     at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
35     at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
36     at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
37     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
38     at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
39     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
40     at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
41     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
42     at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
43     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
44     at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
45     at com.sun.proxy.$Proxy19.addOrder(Unknown Source)
46     at com.lucky.test.spring.MainTest.main(MainTest.java:25)

从结果可以得出以下结论:

1、环绕增强分别在前置增强和后置增强的前面执行

2、执行了异常执行,就不会再执行环绕增强的后置处理

3、返回增强是在后置增强的后面执行

4、如果同时存在环绕增强和返回增强,那么环绕增强必须返回数据,否则返回增强就获取不到结果(如案例中的环绕增强,如果ProceedingJoinPoint执行了proceed方法没有返回结果,那么返回增强获取的值就是null)

5、各种类型增强执行顺序为:环绕增强(前置) -> 前置增强 -> 环绕增强(后置) -> 后置增强 ->返回增强,如果存在异常增强的话,那么环绕增强(后置)和返回增强都不会执行,而后置增强相当于finally,肯定会执行,且在异常增强之前执行

下一篇:Spring AOP(2)--AOP实现原理及源码解析

猜你喜欢

转载自www.cnblogs.com/jackion5/p/13358657.html
今日推荐