Spring AOP

本文对Spring AOP做一个简单的介绍。


一、引入AOP

2020年秋,凉风瑟瑟吹人寒。程序员小K接到了一个需求,要在某些功能的基础上增加“日志记录”、“权限验证”和“登录状态验证”等功能。令他非常绝望的是,需要增加这些功能的方法有上百个。即便将附加功能封装成工具类,也要在方法中添加调用代码,既破坏了封闭原则,又带来了大量的冗余代码:


冗余

AOP(Aspect Oriented Programming 面向切面编程)正是为了解决这一难题而诞生的:


AOP

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框架:

Spring-aspectjs

下面我们来初步学习下AspectJ框架的使用,主要是对几个注解的使用进行说明:

  • 切入点表达式
  • @Aspect
  • @Before
  • @AfterReturning
  • @AfterThrowing
  • @After
  • @Before、@After、@AfterReturning、@AfterThrowing执行顺序
  • @Around
  • @Pointcut


1、切入点表达式

切入点表达式用来指定切入点,即哪些方法需要被增强。

格式:

\[execution([访问修饰符]\quad方法返回值类型\quad[包][类]方法声明(参数)\quad[异常类型]) \]

[ ]内的内容可以省略


特殊符号:

符号 说明
* 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类:
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类:
Student

业务接口:
业务接口

业务实现类:
业务实现类

切面类:
切面类

配置文件:
配置文件

测试类:
测试类

测试结果:
测试结果

  • @Around使用起来更加灵活,类似于InvocationHandler。


8、@Pointcut

用来定义切入点:
Pointcut


四、总结

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

posted @ 2020-10-20 21:30  YuHover  阅读(128)  评论(0)    收藏  举报