1 AOP是什么
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可对业务逻辑进行增强,在不改变原有逻辑的基础上,在其前后进行处理。降低了耦合性,减少了大量冗余的操作。特别适合用于大量方法都需要进行相同处理的操作。
2 AOP概念
AOP是做了个切面,在不破坏原有方法的基础上,将切面切进去,在其前后进行处理,整体逻辑关系图如下:
-
切面(Aspect):一般是指被
@Aspect
修饰的类,代表着某一具体功能的AOP
逻辑。 - 切入点(Pointcut):选择对哪些方法进行增强。
-
通知(Advice):对目标方法的增强,有一下五种增强的类型。
- 环绕通知(@Around):内部执行方法,可自定义在方法执行的前后操作。
- 前置通知(@Before):在方法执行前执行。
- 后置通知(@After):在方法执行后执行。
- 返回通知(@AfterReturning):在方法返回后执行。
- 异常通知(@AfterThrowing):在方法抛出异常后执行。
- 连接点(JoinPoint):就是那些被切入点选中的方法。这些方法会被增强处理。
对于各种通知的方法、注解等没有什么特别的操作,具体使用会在后面举例。而切入点是选择对哪些方法生效的定义,那怎么知道它选择的是哪些方法呢?因为有多种匹配方式。
表达式类型 | 功能 |
---|---|
execution() | 匹配方法,最全的一个 |
args() | 匹配入参类型 |
@args() | 匹配入参类型上的注解 |
@annotation() | 匹配方法上的注解 |
within() | 匹配类路径 |
@within() | 匹配类上的注解 |
this() | 匹配类路径,实际上AOP代理的类 |
target() | 匹配类路径,目标类 |
@target() | 匹配类上的注解 |
用的比较多的是execution()
和@annotation
-
execution(修饰符 返回值类型 方法名(参数)异常)
语法参数 描述 修饰符 可选,如public,protected,写在返回值前,任意修饰符填 *
号就可以返回值类型 必选
,可以使用*
来代表任意返回值方法名 必选
,可以用*
来代表任意方法参数 () 代表是没有参数,
(…)代表是匹配任意数量,任意类型的参数,当然也可以指定类型的参数进行匹配,如要接受一个String类型的参数,则
(java.lang.String), 任意数量的String类型参数:
(java.lang.String…)异常 可选,语法: throws 异常
,异常是完整带包名,可以是多个,用逗号分隔看几个常用的写法
// 所有方法 execution(* *..*(..)) // 指定参数,即入参本身的类型,不能放其接口、父类 execution(* *..*(java.lang.String, java.lang.String) // 指定方法前缀 execution(* *..*.prefix*(..)) // 指定方法后缀 execution(* *..*.*suffix(..)) // 组合,增强所有方法,但是去掉指定前缀和指定后缀的方法 execution(* *..*(..)) && (!execution(* *..prefix*(..)) || !execution(* *..*suffix(..)))
-
@annotation()
匹配方法上的注解,括号内写注解定义的全路径,所有加了此注解的方法都会被增强。
// 增强被指定注解修饰的方法(所有加了@TestAspect注解的都会被) @annotation(***.banmoon.test.annotation.TestAspect) // 指定前缀的注解修饰的方法 @annotation(***.banmoon.test.annotation.Prefix*) // 指定后缀的注解修饰的方法 @annotation(***.banmoon.test.annotation.*Suffix)
3 Springboot中使用AOP
-
引入依赖
其实主要起作用的依赖是第二个,但现在
spring-boot-starter-web
启动依赖中已经包含AOP依赖,所以只引入第一个也可。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
定义一个注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auth { }
-
创建一个切面类
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.***ponent; import java.lang.reflect.Method; @Aspect @***ponent public class AuthAspect { /** * 定义了一个切点 * 这里的路径填自定义注解的全路径 */ @Pointcut("@annotation(***.zz.business.annotations.Auth)") public void authCut() { } @Before("authCut()") public void cutProcess(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); System.out.println("注解方式AOP开始拦截, 当前拦截的方法名: " + method.getName()); } @After("authCut()") public void after(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); System.out.println("注解方式AOP执行的方法 :" + method.getName() + " 执行完了"); } @Around("authCut()") public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("注解方式AOP拦截开始进入环绕通知......."); Object proceed = joinPoint.proceed(); System.out.println("准备退出环绕......"); return proceed; } /** * returning属性指定连接点方法返回的结果放置在result变量中 * * @param joinPoint 连接点 * @param result 返回结果 */ @AfterReturning(value = "authCut()", returning = "result") public void afterReturn(JoinPoint joinPoint, Object result) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); System.out.println("注解方式AOP拦截的方法执行成功, 进入返回通知拦截, 方法名为: " + method.getName() + ", 返回结果为: " + result.toString()); } @AfterThrowing(value = "authCut()", throwing = "e") public void afterThrow(JoinPoint joinPoint, Exception e) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); System.out.println("注解方式AOP进入方法异常拦截, 方法名为: " + method.getName() + ", 异常信息为: " + e.getMessage()); } }
可以看到先是定义了一个切点
authCut
,之后前置通知、后置通知、环绕通知等都是绑在这个切点
上,在通过切点和指定方法连接起来。 -
连接点方法
该方法加了上面自定义的注解
@Auth
@RestController @RequestMapping("/***pany") @RefreshScope public class ***panyController { @GetMapping("/aopTest") @Auth public AjaxResult aopTest(@RequestParam String name){ //远程调用 System.out.println("正在执行接口name" + name); return AjaxResult.su***ess("执行成功" + name); } }
执行后的流程如下图
可以看到环绕通知是包在最外侧的。
4 AOP原理
AOP的代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。
动态代理:程序执行过程中,使用JDK的反射机制,创建代理类对象,并动态的指定要代理目标类。动态代理涉及到的三个类:
-
InvocationHandler接口:处理器,负责完调用目标方法(就是被代理类中的方法),并增强功能;通过代理类对象执行目标接口中的方法,会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的invoke()方法,我们需要把在该invoke()方法中实现调用目标类的目标方法;
-
Proxy 类:通过 JDK 的 java.lang.reflect.Proxy 类实现动态代理 ,使用其静态方法 newProxyInstance(),依据目标对象(被代理类的对象)、业务接口及调用处理器三者,自动生成一个动态代理对象。
-
Method 类:Method 是实例化的对象,有一个方法叫
invoke()
,该方法在反射中就是用来执行反射对象的方法的。
在上述代码中也可以看到,就是拿到了Method类型的对象,可以调用invoke()
方法执行,除此之外还可以获取方法的各种属性:getAnnotation-获取注解
、getName()-方法名字
等等。
5 应用场景
AOP在项目中通常用作一些共通的工作
- 接口方法日志的收集
- 接口方法的权限校验
- 前后对出入参的修改,先查缓存这种需求