一.AOP概述
在学习完Spring的统一功能之后,我们就要进入到SpringAOP的学习.
AOP是Spring框架的第二大核心.
AOP,全名叫Aspect Oriented Programming,翻译过来就是面向切面编程
简单来说AOP是一种思想,是对某一类事情的集中处理
就好比之前学过的拦截器,就是登入校验这一类问题的统一处理.
还有统一结果返回,统一异常处理,这些都算做AOP的实现.
但是spring考虑不到所有的情况,所以有些情况下需要自己实现AOP.
比如我现在要记录每个方法耗时的时间,又或者有别的什么需求.
二.SpringAOP快速入门
假如不使用AOP,我们要记录每个方法的运行时间,就需要在方法开头记个时间戳,然后return前记录个时间戳然后求差.这样子每个方法都得去写,不仅麻烦,还容易漏.
接下来我们用AOP来解决这个问题.
2.1 引入AOP依赖
在pom.xml文件中添加依赖
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 编写AOP程序
记录controller中的每个方法的执行时间.

AOP程序,主要通过@Aspect注解和@Around注解来实现.
@Aspect注解是用来告诉spring这是一个切面类.
@Around注解表示环绕通知,在目标方法前后都会被执行.括号内的内容表示对哪些方法进行功能增强(作用范围).
ProceedingJoinPoin.proceed()方法表示让原始方法执行.
接下来我们测试一下.
运行接口就能看到每个controller方法的耗时了.

三.SpringAOP详解
3.1 SpringAOP核心概念
3.1.1 切点
切点的作用就是来告诉程序对哪些方法进行增强,也就是AOP的作用范围.
下面这个就是切点表达式

3.1.2 连接点
所有满足切点表达式的方法,都叫做连接点,
以上面切点表达式为例,所有com.example.demo.controller下的方法都是连接点.
3.1.3 通知
通知就是具体要做的工作,指那些重复的逻辑,也就是共同的功能.
比如在上述程序中,记录方法的耗时,就是通知.
在AOP面向切面编程中,我们把这部分重复代码逻辑抽出来单独定义,这部分代码就是通知的内容.

3.1.4 切面
切面 = 切点+通知
通过切面能描述当前AOP程序的作用范围和进行什么样的的操作
下面这个就是切面

3.2通知类型
通知不止有@Around一种类型.@Around是环绕通知,就是在目标方法前后都执行.
SpringAOP的通知类型有以下几种:

我们来看下这些通知的执行顺序
java
public class AspectDemo {
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object AroundDemo(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around方法开始执行");
Object result = joinPoint.proceed();
log.info("Around方法执行完毕");
return result;
}
@Before("execution(* com.example.demo.controller.*.*(..))")
public void BeforeDemo() throws Throwable {
log.info("Before方法执行");
}
@After("execution(* com.example.demo.controller.*.*(..))")
public void AfterDemo() throws Throwable {
log.info("After方法执行");
}
@AfterReturning("execution(* com.example.demo.controller.*.*(..))")
public void AfterReturningDemo() throws Throwable {
log.info("AfterReturning方法执行");
}
@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
public void AfterThrowingDemo() throws Throwable {
log.info("AfterThrowing方法执行");
}
}
运行登入接口程序,观察日志
可以看到在正常情况下.AfterThrowing方法不会被执行,现在看看抛出异常的情况


可以看到在异常情况下Around执行只会执行一半,这个不是绝对的,如果在finally里面写,也是会执行的.
注意:

3.3@PointCut
上述代码存在一个问题,就是存在大量的切点表达式.
Spring提供了**@PointCut注解,把公共的切点表达式提取出来,在需要使用的时候在引入即可.**
使用pt()来表示切点表达式,这样在之后使用的时候不需要写那么多,同时在需要修改的时候,只需要改一处即可.

当切点定义使用private修饰的时候,只能在当前切面类使用,如果其他切面类也要使用切点定义,就需要将private改为public.引用方式为:全限定类名.方法名()

3.4切面优先级@Order
当我们一个项目中定义了多个切面类的时候,并且这些切面类同时匹配到了同一个方法,这时候这些切面类的顺序会是什么样子?
我们来定义三个切面类.



此时,运行程序,访问接口.可以看到还是有顺序的,这个顺序是按照字母和数字来排序的.

但是我们的类名还是有更多的意义,所以不方便随意改动,此时又想要按照我们想的顺序来执行,就需要用到@Order注解.



再次运行程序.可以看到此时顺序发生变化了.

从这个结果来看,@Order里面的数字越小,执行顺序越优先.
3.5 切点表达式
上述代码中,我们一直在使用切点表达式来描述切点,下面我们来介绍一下切点表达式的语法.
1.execution(......):根据方法的签名来匹配
2.@annotation(......):根据注解匹配
3.5.1 execution表达式
execution()是最常用的切点表达式,用来匹配方法.
语法:


3.5.2 @annotation
execution表达式更适用于有规则的,就好比某个包下面的所有类,或者同一个名字的包,类...
@annotation注解就不同了,他可以适用于无规则的多个方法
举个简单的例子,我们可以自定义一个注解,然后让加上这个注解的方法实现AOP
3.5.2.1 自定义注解@MyAspect
创建一个注解类

然后用**@Target表示Annotation所修饰的对象范围**,也就是说这个注解可以用在方法上还是类上.

@Retention注解指Annotation被保留的时间长短,也就是注解的生命周期
此时就已经定义完一个注解了.

3.5.2.2 切面类
定义完注解之后,我们需要使用@annotation切点表达式定义切点,只对@MyAspect注解生效.

我们需要把@Around注解里面的切点表达式改成
此时,只有加了@MyAspect注解的方法才能记录耗时.

分别运行t1,t2,可以看到只有加了@MyAspect注解的方法才能生效
