Spring AOP(1)
AOP概述
AOP是Spring框架的第二大核心(第一大核心是IoC).
什么是AOP?
即Aspect Oriented Programming(面向切面编程)
什么是面向切面编程呢? 切面就是指某一类特定的问题, 所以AOP也可以叫做面向特定方法编程.
什么是面向特定方法编程呢?比如上一篇中讲到的拦截器, 就是对比如登录校验这一类问题的统一处理. 所以, 拦截器也算是AOP的一种应用. AOP是一种思想, 拦截器是AOP的一种实现. Spring框架实现了这种思想, 提供了拦截器技术的相关接口.
同样地, 统一数据返回格式和同意一场处理, 也是AOP思想的一种实现.
简单来说: AOP是一种思想, 是对某一类事情的集中处理.
什么是Spring AOP?
AOP是一种思想, 它的实现方法有很多, 有Spring AOP, 也有AspectJ, CGLIB等.
Spring AOP是其中的一种实现方式.
学会统一功能之后, 是不是就学会了AOP呢? 当然不是.
因为拦截器的作用维度是URL(一次请求和响应), @ControllerAdvice应用场景主要是全局异常处理(配合自定义异常效果更佳), 数据绑定, 数据预处理. AOP的作用维度更加细致(可以根据包, 类, 方法, 参数等进行拦截), 能够实现更加复杂的业务逻辑.
举个栗子:
我们现在有一个项目, 项目中开发了很多的业务功能:
现在有一些业务的执行业务比较低, 耗时比较长, 我们需要对接口进行优化.
第一步就需要定位出执行耗时比较长的业务方法, 再针对该业务方法来进行优化.
如何定位呢? 就需要统计当前项目中每一个业务方法的执行耗时.
如何统计呢? 可以在业务方法运行前和运行后, 记录下方法的开始时间和结束时间, 两者之差就是这个方法的耗时.
这种方法是可以解决问题的, 但一个项目中会包含很多业务模块, 每个业务模块又有很多接口, 一个接口又包含很多方法, 如果我们要在每个业务方法上都这么整, 哥们愚公都没你能搬.
而AOP就可以做到在不改动这些原始方法的基础上, 针对特定的方法进行功能的加强.
AOP作用: 在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性:解耦)
Spring AOP快速入门
引入AOP依赖
在pom.xml文件中添加配置:
org.springframework.boot spring-boot-starter-aop
编写AOP程序
记录每个Controller每个方法的执行时间(细节后面会讲).
@Slf4j //需要被Spring管理 @Component //spring使用了aspectJ的注解, 具体实现是Spring实现的. AspectJ是一个第三方的Jar包 @Aspect public class TimeRecrodAspect { /** * 记录耗时 */ @Around("execution(* com.ashwakeup.demo.library.controller.*.*(..))") public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable { //joinPoint可理解为目标方法 //记录开始时间 long start = System.currentTimeMillis(); //执行目标方法 Object proceed = joinPoint.proceed(); //记录结束时间 long end = System.currentTimeMillis(); //日志打印耗时 log.info("耗时时间: " + (end - start) + "ms"); return proceed; } }
运行程序, 观察日志:
对程序进行简单讲解:
1.@Aspect: 标识这是一个切面类.
2.@Around: 环绕通知, 在目标方法的前后都会执行. 后面的表达式表示对哪些方法进行增强.
3.ProceedingJoinPoint.proceed()让原始方法执行.
通过上面的程序, 我们也可以感受到AOP面向切面编程的优势:
代码无侵入: 主要是因为不修改原始的业务方法, 主要是对原始业务方法的功能进行增强.
减少代码重复
提高开发效率
维护方便
Spring AOP详解
Spring AOP核心概念
切点(Pointcut)
Pointcut的作用就是提供一组规则, 告诉程序对哪些方法的功能进行增强.
这里的: @Around("execution(* com.ashwakeup.demo.library.controller.*.*(..))")就是切点表达式.
连接点(Join Point)
满足切点表达式规则的所有方法, 就是连接点. 也就是可以被AOP控制的方法,
比如上图, 就是指所有在com.ashwakeup.demo.library.controller下的所有方法都是连接点.
切点和连接点的关系: 连接点是满足切点表达式的元素. 切点可以看作是保存了众多连接点的一个集合.
eg:
切点表达式: 一班学生
连接点:张三, 李四.......
通知(Advice)
通知就是指具体的工作, 指哪些重复的逻辑, 也就是共性功能(最终体现为一个方法).
比如上述程序中记录业务方法的耗时时间, 就是通知.
在AOP面向切面编程当中, 我们把这部分重复的代码逻辑抽取出来单独定义, 这部分代码就是通知的内容.
切面(Aspect)
切面(Aspect) = 切点(Pointcut) + 通知(Advice)
通过切面就能描述当前AOP程序需要针对哪些方法, 在什么时候执行什么样的工作.
切面既包含了通知逻辑的定义, 也包含了连接点的定义.
切面所在的类, 我们一般称为切面类(被@Aspect注解标识的类)
通知类型
上面讲了什么是通知, 接下来学习通知的类型. @Around就是其中一种通知类型, 表示环绕通知. Spring AOP的通知类型有以下几种:
@Around: 环绕通知, 此注解标注的通知方法在目标方法前后都会被执行
@Before: 前置通知, 此注解标注的通知方法在目标方法前被执行.
@After: 后置通知, 此注解标注的通知方法在目标方法后执行, 无论是否有异常都会被执行.
@AfterReturning: 返回后通知, 此注解标注的通知方法在目标方法后执行, 有异常不会执行.
@AfterThrowing: 异常后通知, 此注解标注的通知方法发生异常后执行.
接下来通过代码来加深对这几个方法的理解:
@Slf4j @Component @Aspect public class AspectDemo { //前置通知 @Before("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))") public void doBefore() { log.info("执行 Before 方法"); } //后置通知 @After("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))") public void doAfter() { log.info("执行 After 方法"); } //返回后通知 @AfterReturning("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))") public void doAfterReturning(){ log.info("执行 AfterReturning 方法"); } //抛出异常后通知 @AfterThrowing("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))") public void doAfterThrowing() { log.info("执行 AfterThrowing 方法"); } //添加环绕通知 @Around("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { log.info("Around方法开始时执行"); Object result = joinPoint.proceed(); log.info("Around方法结束后执行"); return result; } }
写一个测试程序:
@RestController @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public String t1() { return "t1"; } @RequestMapping("/t2") public boolean t2() { int a = 10 / 0; return true; } }
运行代码, 观察日志:
1.执行t1的情况:
程序正常运行的情况下, @AfterThrowing标识的通知方法并不会执行.
从上图可以看出: 执行逻辑大致为: Around上半-> Before -> AfterReturning -> After -> Around下半.
2.异常时的情况:
@AfterReturning标识的通知方法不会执行, @AfterThrowing标识的通知方法执行了.
@Around环绕通知中原始方法调用时有异常, 通知中的环绕后的代码逻辑也不会在执行了(因为原始方法调用出异常了).
执行逻辑: @Around上半 -> @Before -> @AfterThrowing -> @After
注意事项:
@Around环绕通知需要调用ProceedingJoinPoint.proceed()来让原始方法执行, 其它通知不需要考虑目标执行.
@Around环绕通知方法的返回值, 必须指定为Object, 来接收原始方法的返回值, 否则原始方法执行完毕, 是获取不到返回值的.
一个切面类有多个切点.