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

  • Post author:
  • Post category:其他


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就是标识符,()里的就是操作参数。

6.组合切入点表达式

AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式



常见的切点表达式



execution:使用”execution(方法表达式)”匹配方法执行

// 任何公共方法的执行
public * *(..)

// 匹配指定包中的所有的方法
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, ..))

// 匹配所有带@Scheduled 注解的方法
execution(@org.springframework.scheduling.annotation.Scheduled * *(..))


within:使用”within(类型表达式)”匹配指定类型内的方法执行

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

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

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

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



@within:使用”@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名;

// 匹配注解
@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller) ||  @within(org.springframework.stereotype.Service) || @within(org.springframework.stereotype.Component)

//任何目标对象对应的类型持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
@within(cn.javass.spring.chapter6.Secure)



@annotation:使用”@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;



提示:括号里的”注解类型”不是接口!而是”@interface”!!

// 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配
@annotation(cn.javass.spring.chapter6.Secure )


匹配Bean名字

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


切点表达式组合

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

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



execution通常用与匹配方法,within通常用于匹配特定注解

参考:

彻底征服 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 之 实战篇


https://blog.csdn.net/qq_23167527/article/details/78623639



版权声明:本文为jsbylibo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。