SpringBoot AOP
AOP(面向切面编程)是SpringBoot的两大核心功能之一,功能非常强大,为解耦提供了非常优秀的解决方案。
AOP术语
- 执行点(Excutepoint):类初始化,方法调用
- 连接点(JoinPoint):执行点 和 方位的组合,可以确定JoinPoint。比如类初始化前,初始化后,方法调用前,方法调用后。
- 切点(PointCut):在众多的执行点中,定位合适的执行点。ExcutePoint相当于数据库中的记录,Pointcut相当于查询条件。
- 增强(Advice):织入到目标类连接点上的一段代码,除了代码之后,还有执行点的方位信息。
- 目标对象(Target):增强逻辑的织入目标类。
- 引介(Introduction):一种特殊的增强,为类增加一些额外的属性和方法,动态为业务类增加其他接口的实现逻辑,让业务类成为这个接口的实现类。
- 代理(proxy):一个类被AOP织入后,产生了一个结果类,它便是融合了原类和增强逻辑的代理类。
- 切面(Aspect):切面由切点和增强组成既包括横切逻辑定义,也包括连接点定义。
AOP重点:
- 如何通过切点和增强定位到连接点
- 如何在增强中编写切面的代码
实现方式
-
添加MAVEN依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
正则匹配
-
创建切面类
/** * 日志切面 */ @Aspect @Component public class LogAspect { @Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))") public void webLog(){} @Before("webLog()") public void deBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 System.out.println("URL : " + request.getRequestURL().toString()); System.out.println("HTTP_METHOD : " + request.getMethod()); System.out.println("IP : " + request.getRemoteAddr()); System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfeterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 处理完请求,返回内容 System.out.println("方法的返回值 : " + ret); } //后置异常通知 @AfterThrowing("webLog()") public void throwss(JoinPoint jp){ System.out.println("方法异常时执行....."); } //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行 @After("webLog()") public void after(JoinPoint jp){ System.out.println("方法最后执行....."); } //环绕通知,环绕增强,相当于MethodInterceptor @Around("webLog()") public Object arround(ProceedingJoinPoint pjp) { System.out.println("方法环绕start....."); try { Object o = pjp.proceed(); System.out.println("方法环绕proceed,结果是 :" + o); return o; } catch (Throwable e) { e.printStackTrace(); return null; } } }
结果:
方法环绕start..... URL : http://localhost:8092/first HTTP_METHOD : GET IP : 0:0:0:0:0:0:0:1 CLASS_METHOD : com.xncoding.aop.controller.UserController.first ARGS : [] 方法环绕proceed,结果是 :first controller 方法最后执行..... 方法的返回值 : first controller
切面注解说明
- @Aspect 作用是把当前类标识为一个切面供容器读取
- @Pointcut 定义切点,切点方法不用任何代码,返回值是void,重要的是条件表达式
- @Before 标识一个前置增强方法,相当于BeforeAdvice的功能
- @AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行
- @AfterThrowing 异常抛出增强,相当于ThrowsAdvice
- @After final增强,不管是抛出异常或者正常退出都会执行
- @Around 环绕增强,相当于MethodInterceptor
方法参数说明
除了@Around外,每个方法里都可以加或者不加参数JoinPoint。
JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。
@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。
@AfterReturning方法里,可以加returning = “xxx”,xxx即为在controller里方法的返回值,本例中的返回值是”first controller”。
@AfterThrowing方法里,可以加throwing = “XXX”,读取异常信息,如本例中可以改为:
//后置异常通知 @AfterThrowing(throwing = "ex", pointcut = "webLog()") public void throwss(JoinPoint jp, Exception ex){ System.out.println("方法异常时执行....."); }
一般常用的有before和afterReturn组合,或者单独使用Around,即可获取方法开始前和结束后的切面。
关于切点PointCut
execution函数用于匹配方法执行的连接点,语法为:
execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
参数部分允许使用通配符:
表达式可由多个切点函数通过逻辑运算组成,与(&&)、 或(||)、 非(!)
使用注解实现AOP
自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Rentention(RetentionPolicy.RUNTIME)
public UserAccess {
String desc() default "无信息";
}
创建切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserAccessAspect {
@Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
public void access() {
}
@Before("access()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
System.out.println("second before");
}
@Around("@annotation(userAccess)")
public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
//获取注解里的值
System.out.println("second around:" + userAccess.desc());
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
主要看一下@Around注解这里,如果需要获取在controller注解中赋给UserAccess的desc里的值,就需要这种写法,这样UserAccess参数就有值了。
spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。 那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了。
所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可,order越小最先执行,也就是位于最外层。像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点