Spring AOP @Before @Around @After等执行顺序

1.AOP基本概念

切面(Aspect):通知(advice)和切入点(pointcut)共同组成了切面(aspect)

切入点(Pointcut):匹配join point的谓词,切面切向哪里,某个类或者某一层包路径

连接点(Joinpoint):aop拦截的类或者方法,例如方法被调用时、异常被抛出时。(在Spring中,所有的方法都可以认为是joinpoint,但是我们不希望所有的方法都添加Advice,而pointcut的作用就是提供一组规则来匹配joinpoint,给满足规则的joinpoint添加Advice。)

通知(Advice):切面何时使用,即注有@Around、@Before、@After等注解的方法

目标对象(Target Object):被通知的对象

AOP代理(AOP Proxy): AOP的代理有两种,一种是JDK动态代理,一种是CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理;反之,采用CGLIB代理

Joinpoint和Pointcut区别:在Spring AOP中,所有的方法执行都是joinpoint,而pointcut是一个描述信息,它修饰的是joinpoint,通过pointcut可以确定哪些jointpoint可以被Advice。

2.通知(Advice)类型说明

@Around:环绕通知,包围一个连接点的通知,可以在核心方法前后完成自定义的行为。这是最常用最重要的。这个注解需要传入参数ProceedingJoinPoint pjp,决定是否进入核心方法----调用pjp.proceed();如果不调的话将不进入核心方法!

@Before:前通知,核心代码执行前通知

@After:后通知,连接点执行退出时通知(不论正常返回还是异常退出)

@AfterReturning:返回后通知,正常返回后通知

@AfterThrowing:抛出异常后通知,抛出异常时通知

注意:除了@Around传的参数是ProceedingJoinPoint pjp外,其它都是传的JoinPoint jp,也就是说能控制是否进入核心代码的只有Around,因为aop走到核心代码就是通过调用ProceedingJoinPoint的proceed()方法,而JoinPoint没有这个方法。

3.Advice顺序:

情况一:一个方法只被一个Aspect类拦截

添加PointCut类  添加Aspect类   这两个可以放在一起

package com.bob.hello.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * Description
 *
 * @author Bob
 * @date 2020/6/2
 **/
@Component
@Aspect
public class DemoAspect {

    /**
     * @description 拦截com.bob.hello包下所有方法
     * @author Bob
     * @date 2020/6/2
     */
    @Pointcut(value = "within(com.bob.hello.controller.*)")
    public void demoPointCut() {

    }

    @Before(value = "demoPointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("[DemoAspect] before advice");
    }

    @Around(value = "demoPointCut()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[DemoAspect] around advice 1");
        proceedingJoinPoint.proceed();
        System.out.println("[DemoAspect] around advice 2");
    }

    @After(value = "demoPointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("[DemoAspect] after advice");
    }

    @AfterThrowing(value = "demoPointCut()")
    public void afterThrowing(JoinPoint joinPoint) {
        System.out.println("[DemoAspect] afterThrowing advice");
    }

    @AfterReturning(value = "demoPointCut()")
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("[DemoAspect] afterReturning advice");
    }
}

添加测试用的Controller

package com.bob.hello.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description
 *
 * @author Bob
 * @date 2020/5/26
 **/
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        System.out.println("hello");
        return "hello";
    }
}

测试 正常情况

浏览器直接输入:http://localhost:8080/hello

输出的结果:

[DemoAspect] around advice 1
[DemoAspect] before advice
hello
[DemoAspect] around advice 2
[DemoAspect] after advice
[DemoAspect] afterReturning advice

注意:如果用到了Spring Security,会先走登录拦截

结论

在一个方法只被一个aspect类拦截时,aspect类内部的 advice 将按照以下的顺序进行执行:

one-ok

情况二:同一个方法被多个Aspect类拦截

aspect1和aspect2的执行顺序是未知的,除非设置他们的Order顺序,具体参见:Spring AOP @Before @Around @After 等 advice 的执行顺序

4.定义Aspect(切面)

当使用@Aspect标注一个bean后,Spring框架会自动收集这些bean,并添加到Spring AOP中,例如:

@Component
@Aspect
public class DemoAspect {

}

注意:仅仅使用@Aspect 注解并不能将一个Java对象转换为bean,因此还需要类似@Component 之类的注解。

注意:如果一个类被@Aspect 标注,则这个类就不能是其它aspect的**advised object** 了,因为使用@Aspect后,这个类就会别排除在auto-proxying机制之外。

5.声明Pointcut(切入点)

在@AspectJ 风格的AOP中,这样来描述pointcut,例如:

@Pointcut(value = "within(com.bob.hello.controller.*)")
public void demoPointCut() {

}

value里面的值叫pointcut(切点)表达式,它由标识符操作参数组成,within就是标识符,()里的就是操作参数。

常见的切点表达式

匹配方法签名

// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))

// 匹配当前包中的指定类的所有方法
execution(* UserService.*(..))

// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.xys.service.*(String name, ..))

匹配类型签名

// 匹配指定包中的所有的方法, 但不包括子包
within(com.xys.service.*)

// 匹配指定包中的所有的方法, 包括子包
within(com.xys.service..*)

// 匹配当前包中的指定类中的方法
within(UserService)

// 匹配一个接口的所有实现类中的实现的方法
within(UserDao+)

匹配Bean名字

// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)

切点表达式组合

// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)

// 匹配名字以 Service 开头, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)

参考:彻底征服 Spring AOP 之 理论篇 

6.声明Advice(通知)

示例中的@Before @Around等

7.Spring AOP实战

HTTP接口鉴权

核心代码:

@Component
@Aspect
public class HttpAopAdviseDefine {

    // 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
    @Pointcut("@annotation(com.xys.demo1.AuthChecker)")
    public void pointcut() {
    }

    // 定义 advise
    @Around("pointcut()")
    public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();

        // 检查用户所传递的 token 是否合法
        String token = getUserToken(request);
        if (!token.equalsIgnoreCase("123456")) {
            return "错误, 权限不合法!";
        }

        return joinPoint.proceed();
    }

    private String getUserToken(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            return "";
        }
        for (Cookie cookie : cookies) {
            if (cookie.getName().equalsIgnoreCase("user_token")) {
                return cookie.getValue();
            }
        }
        return "";
    }
}

方法调用日志

核心代码:

@Component
@Aspect
public class LogAopAdviseDefine {
    private Logger logger = LoggerFactory.getLogger(getClass());

    // 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
    @Pointcut("within(NeedLogService)")
    public void pointcut() {
    }

    // 定义 advise
    @Before("pointcut()")
    public void logMethodInvokeParam(JoinPoint joinPoint) {
        logger.info("---Before method {} invoke, param: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs());
    }

    @AfterReturning(pointcut = "pointcut()", returning = "retVal")
    public void logMethodInvokeResult(JoinPoint joinPoint, Object retVal) {
        logger.info("---After method {} invoke, result: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs());
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "exception")
    public void logMethodInvokeException(JoinPoint joinPoint, Exception exception) {
        logger.info("---method {} invoke exception: {}---", joinPoint.getSignature().toShortString(), exception.getMessage());
    }
}

方法耗时统计

核心代码:

@Component
@Aspect
public class ExpiredAopAdviseDefine {
    private Logger logger = LoggerFactory.getLogger(getClass());

    // 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
    @Pointcut("within(SomeService)")
    public void pointcut() {
    }

    // 定义 advise
    // 定义 advise
    @Around("pointcut()")
    public Object methodInvokeExpiredTime(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束

        // 上报到公司监控平台
        reportToMonitorSystem(pjp.getSignature().toShortString(), stopWatch.getTotalTimeMillis());

        return retVal;
    }


    public void reportToMonitorSystem(String methodName, long expiredTime) {
        logger.info("---method {} invoked, expired time: {} ms---", methodName, expiredTime);
        //
    }
}

来自:彻底征服 Spring AOP 之 实战篇

参考:

Spring AOP @Before @Around @After 等 advice 的执行顺序

spring aop的@Before,@Around,@After,@AfterReturn,@AfterThrowing的理解

彻底征服 Spring AOP 之 理论篇 

彻底征服 Spring AOP 之 实战篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章