[Java] Spring AOP execution order

Introduction

This time, I will write about the execution order of Spring AOP. We will show you how to prioritize advices within the same Aspect and how to prioritize the same Advice between different Aspects!

For a brief explanation of Spring AOP itself, see Qiita (First Spring AOP), so if you want to know more about AOP, please refer to it. It will be smooth if you read it after receiving it.

Usage environment and version

Advice priority

There is a priority in the order in which Advice is executed. [^ 1]

  1. @Around
  2. @Before
  3. @After
  4. @AfterReturning
  5. @AfterThrowing

[^ 1]: Since it will be a Spring-core 5.3 series document, it is slightly different from the version (5.2.6) used this time, but 5.2.6 does not specify the priority of Advice Since there was no discrepancy with the experimental results described later, I referred to the 5.3 series only for this part. https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#aop-ataspectj-advice-ordering

That is, if @ Around and @ Before exist in the same join point, the processing before the join point defined in @ Around is executed first. So what if there are @Before, @After, @AfterReturning, and @Around, which are processed before and after the joinpoint is executed, at the same joinpoint in the same Aspect?

I experimented with a simple Hello World code consisting of the following 3 classes.

  1. Main class
  2. Controller class
  3. The class that defines Aspect

A brief introduction and source code are provided for each.

① Main class It is a class that simply executes Spring. No special processing is done. Since @EnableAspectJAutoProxy is an annotation required to enable AOP, it is added to all classes this time.

AopSampleApplication.java


package com.example.practiceaop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@EnableAspectJAutoProxy
@SpringBootApplication
public class AopSampleApplication {
	    public static void main(String[] args) {
	        SpringApplication.run(AopSampleApplication.class, args);
	    }
}

② Controller class Tap http: // localhost: 8080 / to display [Hello World!] On the console. I used the code from Qiita article I wrote before quite a bit (I'm glad I made it!).

AopSampleController.java


package com.example.practiceaop;

import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@EnableAspectJAutoProxy
public class AopSampleController {
    @RequestMapping("/")
    @ResponseBody
    public void helloWorld() {
    	System.out.println("【Hello World!】");
    }
}

③ Class that defines Aspect The join point is helloWorld () of AopSampleController, which is common to all Advice.

Aspect1.java


package com.example.practiceaop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Aspect
@Component
@EnableAspectJAutoProxy
public class Aspect1 {
	@Before("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
	public void before1(JoinPoint jp) {
		System.out.println("Before1");
	}
	
	@After("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
	public void after1(JoinPoint jp) {
		System.out.println("After1");
	}
	
	@AfterReturning("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
	public void afterReturning1(JoinPoint jp) {
		System.out.println("AfterReturning1");
	}
	
	@Around("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
	public Object around1(ProceedingJoinPoint pjp) throws Throwable {
		System.out.println("Before executing the Around1 method");
		try {
			Object result = pjp.proceed();
			System.out.println("After executing the Around1 method");
			return result;
		} catch (Throwable e) {
			throw e;
		}
	}
}


The execution result is as follows.

Before executing the Around1 method
Before1
【Hello World!】
AfterReturning1
After1
After executing the Around1 method

Why is Around's priority should be 1st, but after the join point it's in the lowest order of execution? [Official documentation](https://docs.spring.io/spring-framework/docs/5.2.6.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering] to help understanding ) Is translated into Japanese by Deepl Translator (the appearance is adjusted a little manually).

Spring AOP follows the same priority rules as AspectJ to determine the order in which advice is executed. The one with the highest priority is executed first (that is, if there are two Before advices, the one with the highest priority is executed first). On the way out of the join point, the higher priority advice is executed last (thus, if two After advices are given, the higher priority is executed second).

On the way out of the join point, the high-priority advice is executed last, which is confusing. In other words, before the join point method is executed, the priority is equal to the execution order, but after the join point method is executed, the one with the lowest priority is executed first.

Prioritization in the case of the same line

For example, suppose you want to put transaction processing and log output before the same join point. Also, suppose that the transaction is executed absolutely first in the execution order. You can use one of the advices, Before, to run it in front of the join point. However, according to the Official Document If multiple Advices of the same type exist at the same join point, the execution order is indefinite and will change from execution to execution.

If two pieces of advice defined in the same aspect need to be executed at the same join point, the order is indefinite (because there is no way to get the declarative order through reflection in a class compiled with javac). ).

There are two ways to solve this: put the transaction and log output in the same Advice, or cut it out into another Aspect and use @ Order. It's okay if the processes are included in the same Advice and there is no problem, but in most cases it is not, so it is more realistic to use @ Order. You can use @ Order to specify the execution order of Aspects that exist in the same column.

As a test, add @ Order (1) to the Aspect1 used earlier, create an Aspect2 class that copies Aspect1 and changes only the console output characters, and execute it with @ Order (2).

Aspect1.java


・
・
・
(Omitted)
@Aspect
@Component
@EnableAspectJAutoProxy
@Order(1) //Add annotation
public class Aspect1 {
(Omitted)
・
・
・

Aspect2.java


・
・
・
(Omitted)
@Aspect
@Component
@EnableAspectJAutoProxy
@Order(2) //Add annotation
public class Aspect2 { //A copy of Aspect1
@Before("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
	public void before1(JoinPoint jp) { 
		System.out.println("Before2"); //Changed from Before1 to Before2. The same applies to Advice below.
	}
(Omitted)
・
・
・

The results are as follows. You can see that the execution order is not disturbed between Aspect 1 and 2.

Before executing the Around1 method
Before1
Before executing the Around2 method
Before2
【Hello World!】
AfterReturning2
After2
After executing the Around2 method
AfterReturning1
After1
After executing the Around1 method

Just in case, I added @ Order (2) to Aspect1 and @Order (1) to Aspect2 and executed it. As shown below, the execution order was not confused between Aspects 1 and 2, and only the priorities of Aspects could be swapped cleanly.

Before executing the Around2 method
Before2
Before executing the Around1 method
Before1
【Hello World!】
AfterReturning1
After1
After executing the Around1 method
AfterReturning2
After2
After executing the Around2 method

in conclusion

This time, we looked at the execution order of Spring AOP. In particular, the behavior after the join point is not well understood, and even if I search it, the result may be inconsistent with the document, which was quite confusing. Perhaps the document creator himself feels confused, but as the new version comes, the description becomes more polite ...

I hope this article is helpful to anyone. Also, if you have any mistakes, please let me know ...!

Thank you for reading!

Recommended Posts

[Java] Spring AOP execution order
Java tips --Spring execution Summary
Spring Java
About Spring AOP
[Java] Spring DI ③
Java instruction execution statement
About Spring AOP Pointcut
Overview of Spring AOP
Parallel execution in Java
[Java] Spring AOP execution order
Java tips --Spring execution Summary
Spring + Gradle + Java Quick Start
Java debug execution [for Java beginners]
[Java] Thymeleaf Basic (Spring Boot)
Introduction to Spring Boot ② ~ AOP ~
CICS-Run Java application-(4) Spring Boot application
Using Mapper with Java (Spring)
[Java] How Spring DI works
External process execution in Java
[Java] [Spring] Spring Boot 1.4-> 1.2 Downgrade Note
Spring Boot + Java + GitHub authentication login
How to unit test Spring AOP
Execution environment test after Java installation
Java Spring environment in vs Code
spring framework Simple study memo (2): AOP
Elastic Beanstalk (Java) + Spring Boot + https
Java --Jersey Framework vs Spring Boot
About binding of Spring AOP Annotation
Implement reCAPTCHA v3 in Java / Spring
Spring Framework tools for Java developer
[Java] Spring DI ④ --Life cycle management
[Java] LINE integration with Spring Boot
IDE (eclipse) debug execution, step execution (Java)
Spring AOP for the first time