Springboot2(17)轻松搞定AOP

版权声明:转载请注明出处 https://blog.csdn.net/cowbin2012/article/details/85251655

源码地址

集成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){
}

猜你喜欢

转载自blog.csdn.net/cowbin2012/article/details/85251655