Are you really sure about the execution order of Spring AOP

Are you really sure about the execution order of Spring AOP

Preface

The company has launched a brand new project in the past two months, and the project schedule has expired, but it is still necessary to learn. This is not when it comes to building projects for companies.

Highlights of this article:

  • Problem Description
  • Spring AOP execution order
  • Explore the truth about the wrong order
  • Code verification
  • in conclusion

Problem Description

The company’s new project needs to build a new front-to-back separated HTTP service. I chose the familiar SpringBoot Web to quickly build a usable system.

Lu Xun said, don't just upgrade the stable version. I don't believe in this evil. I have used Spring for so long, so how can I not rush. Not to mention, the latest SprinBoot 2.3.4.RELEASE version was directly introduced, and the project began to be built.

At first, the introduction of most of the components went smoothly. I thought it was about to be done, but I didn't expect to stumble upon the log section.

As an interface service, in order to facilitate the query of interface call status and locate problems, the request log is generally printed out. Spring's AOP is supported as an aspect, which perfectly meets the requirements of logging.

In the previous project, the effect of running the correct aspect log record is as follows:

Are you really sure about the execution order of Spring AOP

You can see that a method call in the figure will output the request url, input and output parameters, and request IP, etc., in order to look good, a dividing line was added.

I put this implementation class into a new project, but the implementation looks like this:

Are you really sure about the execution order of Spring AOP

I rubbed my eyes and took a closer look at the copied old code. The simplified version is as follows:

/** 
 * 在切点之前织入 
 * @param joinPoint 
 * @throws Throwable 
 */ 
@Before("webLog()") 
public void doBefore(JoinPoint joinPoint) throws Throwable { 
    // 开始打印请求日志 
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 
    HttpServletRequest request = attributes.getRequest(); 

    // 初始化traceId 
    initTraceId(request); 

    // 打印请求相关参数 
    LOGGER.info("========================================== Start =========================================="); 
    // 打印请求 url 
    LOGGER.info("URL            : {}", request.getRequestURL().toString()); 
    // 打印 Http method 
    LOGGER.info("HTTP Method    : {}", request.getMethod()); 
    // 打印调用 controller 的全路径以及执行方法 
    LOGGER.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); 
    // 打印请求的 IP 
    LOGGER.info("IP             : {}", IPAddressUtil.getIpAdrress(request)); 
    // 打印请求入参 
    LOGGER.info("Request Args   : {}", joinPoint.getArgs()); 
} 

/** 
 * 在切点之后织入 
 * @throws Throwable 
 */ 
@After("webLog()") 
public void doAfter() throws Throwable { 
    LOGGER.info("=========================================== End ==========================================="); 
} 

/** 
 * 环绕 
 * @param proceedingJoinPoint 
 * @return 
 * @throws Throwable 
 */ 
@Around("webLog()") 
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 
    long startTime = System.currentTimeMillis(); 
    Object result = proceedingJoinPoint.proceed(); 
    // 打印出参 
    LOGGER.info("Response Args  : {}", result); 
    // 执行耗时 
    LOGGER.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); 
    return result; 
} 

The code feels that there is no problem at all, is there a bug in the new version of SpringBoot.

Obviously, mature frameworks will not make mistakes in this general direction. Could it be that the new version of SpringBoot reverses the order of @After and @Around?

In fact, things are not that simple.

Spring AOP execution order

Let's review the execution sequence of Spring AOP first.

We look up information about SpringAop execution order on the Internet. Most of the time, you will find the following answers:

normal circumstances

Are you really sure about the execution order of Spring AOP

abnormal situation

Are you really sure about the execution order of Spring AOP

Multiple aspects

Are you really sure about the execution order of Spring AOP

So @Around should be before @After, but in SprinBoot 2.3.4.RELEASE version, @Around is actually implemented after @After.

When I tried to switch back to the 2.2.5.RELEASE version, the execution order returned to @Around-->@After

Explore the truth about the wrong order

Now that you know that it is the problem (or the order change) caused by the SpringBoot version upgrade, let's see which library has changed the order of AOP execution. After all, SpringBoot is just "form", and the real core is Spring.

We open the pom.xml file, use the plug-in to check the version of spring-aop, and find that the AOP used by the SpringBoot 2.3.4.RELEASE version is spring-aop-5.2.9.RELEASE.

And 2.2.5.RELEASE corresponds to spring-aop-5.2.4.RELEASE

So I went to the official website to search for the documentation, and I have to say that because Spring is too large, the documentation on the official website has reached the point of cumbersome, but I finally found it:

https://docs.spring.io/spring-framework/docs/5.2.9.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering

Are you really sure about the execution order of Spring AOP

As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around, @Before, @After, @AfterReturning, @AfterThrowing.

Let me briefly translate the key points:

Starting from Spring 5.2.7, in the same @Aspect class, the notification method will be executed according to its type from high to low priority: @Around, @Before, @After, @AfterReturning, @AfterThrowing.

In this way, the comparison is not obvious. Let's go back to the old version, which is spring-aop-5.2.4.RELEASE corresponding to 2.2.5.RELEASE. The document at that time wrote:

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first “on the way in” (so, given two pieces of before advice, the one with highest precedence runs first). “On the way out” from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).

Simple translation: In the same @Aspect class, Spring AOP follows the same priority rules as AspectJ to determine the order of advice execution.

Dig deeper, what are the priority rules of AspectJ?

I found the documentation of AspectJ:

https://www.eclipse.org/aspectj/doc/next/progguide/semantics-advice.html

Are you really sure about the execution order of Spring AOP

At a particular join point, advice is ordered by precedence.

A piece of around advice controls whether advice of lower precedence will run by calling proceed. The call to proceed will run the advice with next precedence, or the computation under the join point if there is no further advice.

A piece of before advice can prevent advice of lower precedence from running by throwing an exception. If it returns normally, however, then the advice of the next precedence, or the computation under the join pint if there is no further advice, will run.

Running after returning advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation returned normally, the body of the advice will run.

Running after throwing advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation threw an exception of an appropriate type, the body of the advice will run.

Running after advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then the body of the advice will run.

Everyone has to say again, oops, it’s too long to read! In short, Aspectj’s rules are as shown in the sequence diagram that we can find on the Internet. It’s still the old sequence.

Code verification

I deleted the business logic from the code and only verified the execution order of these advices:

/** 
 * 日志切面 
 */ 
@Aspect 
@Component 
public class WebLogAspect { 

    private final static Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class); 

    /** 以 controller 包下定义的所有请求为切入点 */ 
    @Pointcut("execution(public * com.xx.xxx.xxx.controller..*.*(..))") 
    public void webLog() {} 

    /** 
     * 在切点之前织入 
     * @param joinPoint 
     * @throws Throwable 
     */ 
    @Before("webLog()") 
    public void doBefore(JoinPoint joinPoint) throws Throwable { 
        LOGGER.info("-------------doBefore-------------"); 
    } 

    @AfterReturning("webLog()") 
    public void afterReturning() { 
        LOGGER.info("-------------afterReturning-------------"); 
    } 
    @AfterThrowing("webLog()") 
    public void afterThrowing() { 
        LOGGER.info("-------------afterThrowing-------------"); 
    } 

    /** 
     * 在切点之后织入 
     * @throws Throwable 
     */ 
    @After("webLog()") 
    public void doAfter() throws Throwable { 
        LOGGER.info("-------------doAfter-------------"); 
    } 

    /** 
     * 环绕 
     * @param proceedingJoinPoint 
     * @return 
     * @throws Throwable 
     */ 
    @Around("webLog()") 
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 
        long startTime = System.currentTimeMillis(); 
        LOGGER.info("-------------doAround before proceed-------------"); 
        Object result = proceedingJoinPoint.proceed(); 
        LOGGER.info("-------------doAround after proceed-------------"); 
        return result; 
    } 

We changed the version to 2.2.5.RELEASE, and the result is shown in the figure:

Are you really sure about the execution order of Spring AOP

We changed the version to 2.3.4.RELEASE, and the result is shown in the figure:

Are you really sure about the execution order of Spring AOP

in conclusion

After consulting the above documents, the conclusion I can give is:

Starting from Spring 5.2.7, Spring AOP no longer executes advice strictly according to the rules defined by AspectJ, but executes advice according to its type from high to low: @Around, @Before, @After, @AfterReturning, @AfterThrowing .

The research and thinking this time is very hasty. If the conclusion is wrong, please correct me actively. You are also welcome to try it yourself. After all, the only standard for laboratory testing of truth!

Guess you like

Origin blog.csdn.net/qwe123147369/article/details/109129164