欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

轻松搞定AOP

程序员文章站 2022-07-15 10:24:52
...

集成Spring AOP步骤

1 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.实体

@Slf4j
@Aspect
@Order(3)   // 有多个日志时,ORDER可以定义切面的执行顺序(数字越大,前置越后执行,后置越前执行)
@Component
public class LogAspect {
​
    ThreadLocal<Long> startTime = new ThreadLocal<>();
​
    @Pointcut("execution(public * cn.myframe.controller..*.*(..))")
    public void log() {
    }
​
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
​
        // 记录下请求内容
        log.info("URL : " + request.getRequestURL().toString());
        log.info("HTTP_METHOD : " + request.getMethod());
        log.info("IP : " + request.getRemoteAddr());
        log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()
                + "." + joinPoint.getSignature().getName());
        log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }
​
    @AfterReturning(returning = "ret", pointcut = "log()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        log.info("RESPONSE : " + ret);
        log.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
    }
}

通知位置的切入内容

  • 使用@Before在切入点开始处切入内容

  • 使用@After在切入点结尾处切入内容

  • 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)

  • 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容

  • 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑

AOP切面的优先级

由于通过AOP实现,程序得到了很好的解耦,但是也会带来一些问题,比如:我们可能会对Web层做多个切面,校验用户,校验头信息等等,这个时候经常会碰到切面的处理顺序问题。

@Order(i)注解来标识切面的优先级。i的值越小,优先级越高。假设我们还有一个切面是CheckNameAspect用来校验name必须为didi,我们为其设置@Order(10),而上文中WebLogAspect设置为@Order(5),所以WebLogAspect有更高的优先级,这个时候执行顺序是这样的:

在@Before中优先执行@Order(5)的内容,再执行@Order(10)的内容
在@After和@AfterReturning中优先执行@Order(10)的内容,再执行@Order(5)的内容
所以我们可以这样子总结:

在切入点前的操作,按order的值由小到大执行
在切入点后的操作,按order的值由大到小执行


定义切入点

切入点表达式的格式:execution([可见性] 返回类型 [声明类型].方法名(参数) [异常])

其中[]的为可选,其他的还支持通配符的使用:

  • *:匹配所有字符

  • ..:一般用于匹配多个包,多个参数

  • +:表示类及其子类

运算符有:&&、||、!

@Pointcut("execution(public * cn.myframe.controller..*.*(..))")
上面这段注解的意思如下:
1) execution(): 表达式主体
2) 第一个public *号:表示返回类型, *号表示所有的类型。
3) 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.king.controller包、子孙包下所有类的方法。
4) 第二个*号:表示类名,*号表示所有的类。
5) *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数


常用切入点表达式关键词

1)execution:用于匹配子表达式。

  //匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意
  @Pointcut("execution(* com.cjm.model...(..))")
  public void before(){}


2)within:用于匹配连接点所在的Java类或者包。

 //匹配Person类中的所有方法
 @Pointcut("within(com.cjm.model.Person)")
 public void before(){}
 
 //匹配com.cjm包及其子包中所有类中的所有方法
 @Pointcut("within(com.cjm..*)")
 public void before(){}


3)@annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。

@Pointcut("@annotation(com.cjm.annotation.AdviceAnnotation)")
public void before(){}


4)target:用于向通知方法中传入目标对象的引用。

@Before("before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
}


5)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。

@Pointcut("bean(person)")
 public void before(){}


6)args:用于将参数传入到通知方法中。

args来绑定。如果在一个args表达式中应该使用类型名字的地方 使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。

@Before("before() && args(age,username)")
public void beforeAdvide(JoinPoint point, int age, String username){
}


 

相关标签: springboot2 AOP