Spring AOP
本文对Spring AOP做一个简单的介绍。
一、引入AOP
2020年秋,凉风瑟瑟吹人寒。程序员小K接到了一个需求,要在某些功能的基础上增加“日志记录”、“权限验证”和“登录状态验证”等功能。令他非常绝望的是,需要增加这些功能的方法有上百个。即便将附加功能封装成工具类,也要在方法中添加调用代码,既破坏了封闭原则,又带来了大量的冗余代码:
AOP(Aspect Oriented Programming 面向切面编程)正是为了解决这一难题而诞生的:
AOP将附加功能抽象封装成一个”切面“,将其”横切“到各个方法之中,不改变原有方法,也避免了冗余代码。AOP完成的工作看起来和代理很像,都是在不改变目标方法代码的情况下,对方法进行扩展或者增强。实际上Spring AOP就是通过动态代理实现的,使用的代理实现有JDK动态代理和CGLib(Code Generate Library)两种。
二、AOP专业术语
这一部分我们会列举一些AOP的专业术语并作出解释,为后面学习Spring AOP框架(AspectJ)做一些知识准备。
- Aspect:切面,切面是一个整体的概念,它是由Pointcut、Advice组成;
- Joint Point:连接点,通常指要被增强的方法或者代码块,这些代码正是连接业务逻辑代码和切面功能的地方,如下图中的Mi都是连接点;
- Pointcut:切入点,是连接点的集合,这些连接点都有相同的功能增强的需求,如下图中M1、M2、M3组成的集合即为切入点;
- Advice:增强,是对增强功能的具体实现,同时也规定了执行的时机,如:是在Mi之前运行,还是之后运行,还是抛出异常后运行等等。
三、AspectJ
我们使用Spring AOP框架时,实际上使用的是AspectJ框架:
下面我们来初步学习下AspectJ框架的使用,主要是对几个注解的使用进行说明:
- 切入点表达式
- @Aspect
- @Before
- @AfterReturning
- @AfterThrowing
- @After
- @Before、@After、@AfterReturning、@AfterThrowing执行顺序
- @Around
- @Pointcut
1、切入点表达式
切入点表达式用来指定切入点,即哪些方法需要被增强。
格式:
[ ]内的内容可以省略
特殊符号:
| 符号 | 说明 |
|---|---|
| * | 0至多个任意字符 |
| .. | 用在方法参数中,表示任意多个参数 用在包名后,表示当前包及其任意子包 |
| + | 用在类名后,表示当前类及其子类 用在接口后,表示当前类及其实现类 |
举例:
execution(public * *(..)) 任意公共方法
解释:访问修饰符为public;返回值类型任意;方法任意;参数任意;异常类型省略
execution(* set*(..)) 任意以"set"开头的方法
解释:访问修饰符省略;返回值类型任意;以set开头的方法;参数任意;异常类型省略
execution(* com.xyz.service.*.*(..)) com.xyz.service包下的任意类的任意方法(不包括子包中的类)
解释:访问修饰符省略;返回值类型任意;com.xyz.service包下的任意类的任意方法;参数任意;异常类型省略
execution(* com.xyz.service..*.*(..)) com.xyz.service包及其子包下的任意类的任意方法
解释:访问修饰符省略;返回值类型任意;com.xyz.service包及其子包下的任意类的任意方法;参数任意;异常类型省略
execution(* *..service.*.*(..)) 任意包及其子包下的service包的任意类的任意方法
解释:访问修饰符省略;返回值类型任意;任意包及其子包下的service包的任意类的任意方法;参数任意;异常类型省略
举例:com.service包下的任意类的任意方法,com.xyz.service包下的任意类的任意方法
execution(* *.service.*.*(..)) 任意包下的service包的任意类的任意方法
举例:com.service包下的任意类的任意方法,cn.service包下的任意类的任意方法
execution(public * *(..)) && !execution(public * wrong(..)) 任意公共方法,但排除wrong方法
========================================================================
execution(public * *(..)) and not execution(public * wrong(..)) 任意公共方法,但排除wrong方法
解释:多个切入点表达式可以通过||(or)、&&(and)、!(not)组合
2、@Before
@Before:
- value属性:切入点表达式;
- 位置:通知方法上方;
- 作用:目标方法前执行,又称作前置通知。
通知方法(Advice)定义要求:
- public;
- 无返回值(void);
- 方法参数类型只能是AspectJ所规定的类型,如下面的JoinPoint。
实例:
业务接口:

业务实现类:

切面类:

配置文件:

测试类:

测试结果:

-
使用@Aspect注解修饰的类即为切面类;
-
myBefore方法在目标方法doSome前执行;
-
我们在通知方法myBefore中使用了JoinPoint参数,即连接点,可以获取连接点(目标方法)的信息。JoinPoint参数只能放在第一个位置。
3、@AfterReturning
@AfterReturning:
- value属性:切入点表达式;returning属性:返回值形参名;
- 位置:通知方法上方;
- 作用:目标方法返回后(只要求返回后,返回值类型可以是void)执行,又称作后置通知;可原地修改返回值。
通知方法(Advice)定义要求:
- public;
- 无返回值(void);
- 方法参数类型只能是AspectJ所规定的类型和返回值的类型,如下面的JoinPoint和Object;
实例:
Student类:

业务接口:

业务实现类:

切面类:

配置文件:

测试类:

测试结果:

- myAfterReturning方法在doSome方法返回后执行;
- myAfterReturning方法可原地修改返回值,不要忘记声明返回值形参,即returning;
- 因为一个Advice可能要增强很多目标方法,多个目标方法可能具有不同的返回值类型,所以我们在参数中,一般将返回值类型设为Object。
- 我们使用了JoinPoint参数,即连接点,可以获取连接点(目标方法)的信息。JoinPoint参数只能放在第一个位置。
4、@AfterThrowing
@AfterThrowing:
- value属性:切入点表达式;throwing属性:捕获的异常的形参名;
- 位置:通知方法上方;
- 作用:目标方法抛出异常后执行,又称作异常通知。
通知方法(Advice)定义要求:
- public;
- 无返回值(void);
- 方法参数类型只能是AspectJ所规定的类型和返回值的类型,如下面的Exception;
实例:
业务接口:

业务实现类:

切面类:

配置文件:

测试类:

测试结果:

- myAfterThrowing方法在doSome方法抛出异常后执行;
- 不要忘记声明异常形参,即throwing;
- 因为一个Advice可能要增强很多目标方法,多个目标方法可能抛出不同的异常,所以我们在参数中,一般将异常类型设为Exception。
5、@After
@After:
- value属性:切入点表达式;
- 位置:通知方法上方;
- 作用:目标方法后执行。
通知方法(Advice)定义要求:
- public;
- 无返回值(void);
- 方法参数类型只能是AspectJ所规定的类型,如下面的JoinPoint
实例:

业务接口:

业务实现类:

切面类:

配置文件:

测试类:

测试结果:

- myAfter方法在doSome方法后执行;
- @After方法永远都会执行,而且执行时机会在@AfterReturning和@AfterThrowing之前;
- @After方法因为永远会执行,所以主要用于一些资源的清理和关闭。
6、@Before、@After、@AfterReturning、@AfterThrowing 执行顺序
- @After肯定会执行
- 目标方法抛出的异常会被原样传递到整个流程结束
- 通知方法抛出的异常会cause java.lang.reflect.UndeclaredThrowableException
- 后面的异常会覆盖前面的异常(待商榷)
7、@Around
@Around通知方法和JDK动态代理中的InvocationHandler相似,可以自己掌控整个方法调用的流程。
@Around:
- value属性:切入点表达式;
- 位置:通知方法上方;
- 作用:控制整个方法流程的调用
通知方法(Advice)定义要求:
- public;
- 必须有返回值, 推荐使用Object;
- 方法有参数,参数固定ProceedingJoinPoint。
实例:

Student类:

业务接口:

业务实现类:

切面类:

配置文件:

测试类:

测试结果:

- @Around使用起来更加灵活,类似于InvocationHandler。
8、@Pointcut
用来定义切入点:

四、总结
对AOP的概念进行了简单的介绍,并初步讲解了AspectJ AOP框架的使用,其中有很多疏漏之处,慢慢填坑。

浙公网安备 33010602011771号