[Java] Ordre d'exécution de Spring AOP

introduction

Cette fois, j'écrirai sur l'ordre d'exécution de Spring AOP. Nous allons vous montrer comment prioriser les conseils dans le même Aspect et comment prioriser les mêmes Conseils entre différents Aspects!

Pour une brève explication de Spring AOP lui-même, voir Qiita (First Spring AOP), donc si vous voulez en savoir plus sur AOP, veuillez vous y référer. Ce sera fluide si vous le lisez après l'avoir reçu.

Environnement d'utilisation et version

Priorité des conseils

Il existe une priorité dans l'ordre dans lequel l'avis est exécuté. [^ 1]

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

[^ 1]: Puisqu'il s'agira d'un document de la série Spring-core 5.3, il est légèrement différent de la version (5.2.6) utilisée cette fois, mais 5.2.6 ne spécifie pas la priorité de Advice Comme il n'y avait pas de divergence avec les résultats expérimentaux décrits plus loin, je me suis référé à la série 5.3 uniquement pour cette partie. https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#aop-ataspectj-advice-ordering

Autrement dit, si «@ Around» et «@ Before» existent dans le même point de jointure, le processus de pré-exécution défini dans «@ Around» est exécuté en premier. Alors, que se passe-t-il s'il y a @ Before, @ After, @ AfterReturning et @ Around, qui sont traités avant et après l'exécution du point de jointure, au même point de jointure dans le même aspect?

J'ai expérimenté un code HelloWorld simple composé des 3 classes suivantes.

  1. Classe principale
  2. Classe de contrôleur
  3. La classe qui définit Aspect

Une brève introduction et un code source sont fournis pour chacun.

① Classe principale Une classe qui exécute simplement Spring. Aucun traitement spécial n'est effectué. Puisque @ EnableAspectJAutoProxy est une annotation requise pour activer AOP, elle est ajoutée à toutes les classes cette fois.

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);
	    }
}

② Classe de contrôleur Appuyez sur http: // localhost: 8080 / pour afficher [Hello World!] Sur la console. J'ai beaucoup utilisé le code de l'article Qiita que j'ai écrit avant (je suis content de l'avoir fait!).

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!】");
    }
}

③ Classe qui définit l'aspect Le point de jonction est helloWorld () de AopSampleController, qui est commun à tous les conseils.

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("Avant d'exécuter la méthode Around1");
		try {
			Object result = pjp.proceed();
			System.out.println("Après avoir exécuté la méthode Around1");
			return result;
		} catch (Throwable e) {
			throw e;
		}
	}
}


Le résultat de l'exécution est le suivant.

Avant d'exécuter la méthode Around1
Before1
【Hello World!】
AfterReturning1
After1
Après avoir exécuté la méthode Around1

Pourquoi l'ordre d'exécution se trouve-t-il en bas après le point de jointure alors que la priorité Autour devrait être la première? [Documentation officielle](https://docs.spring.io/spring-framework/docs/5.2.6.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering] pour vous aider à comprendre ) Est traduit en japonais par Traduction approfondie (l'apparence est ajustée un peu manuellement).

Spring AOP suit les mêmes règles de priorité qu'AspectJ pour déterminer l'ordre dans lequel le conseil est exécuté. Celui qui a la priorité la plus élevée est exécuté en premier (c'est-à-dire que s'il y a deux conseils Before, celui qui a la priorité la plus élevée est exécuté en premier). À la sortie du point de jointure, l'avis de priorité la plus élevée est exécuté en dernier (donc si deux conseils After sont donnés, la priorité la plus élevée est exécutée en second).

«À la sortie du point de jonction, le conseil de haute priorité est exécuté en dernier», ce qui prête à confusion. En d'autres termes, avant que la méthode de point de jointure ne soit exécutée, la priorité est égale à l'ordre d'exécution, mais après l'exécution de la méthode de point de jointure, celle avec la priorité la plus faible est exécutée en premier.

Priorisation dans le cas de la même ligne

Par exemple, supposons que vous souhaitiez placer le traitement des transactions et la sortie du journal avant le même point de jointure. Supposons également que l'ordre d'exécution soit que la transaction soit effectuée absolument en premier. Vous pouvez utiliser l'un des conseils, Avant, pour l'exécuter devant le point de jointure. Cependant, selon le Document officiel Si plusieurs avis du même type existent au même point de jointure, l'ordre d'exécution est indéfini et changera d'une exécution à l'autre.

Si deux conseils définis dans le même aspect doivent être exécutés au même point de jointure, l'ordre est indéfini (car il n'y a aucun moyen d'obtenir l'ordre de déclaration par réflexion dans une classe compilée avec javac). ).

Il y a deux façons de résoudre ce problème: placer la transaction et la sortie du journal dans le même avis, ou le découper dans un autre aspect et utiliser @ Order. Ce n'est pas grave s'ils sont inclus dans le même avis et il n'y a pas de problème, mais dans la plupart des cas ce n'est pas le cas, il est donc plus réaliste d'utiliser @ Order. Vous pouvez utiliser @ Order pour spécifier l'ordre d'exécution des aspects qui sont dans la même ligne.

En guise de test, ajoutez @ Order (1) à l'Aspect1 utilisé précédemment, copiez Aspect1 pour créer une classe Aspect2 avec uniquement les caractères de sortie de la console modifiés et ajoutez @ Order (2) pour l'exécuter.

Aspect1.java


・
・
・
(Omis)
@Aspect
@Component
@EnableAspectJAutoProxy
@Order(1) //Ajouter une annotation
public class Aspect1 {
(Omis)
・
・
・

Aspect2.java


・
・
・
(Omis)
@Aspect
@Component
@EnableAspectJAutoProxy
@Order(2) //Ajouter une annotation
public class Aspect2 { //Une copie d'Aspect1
@Before("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
	public void before1(JoinPoint jp) { 
		System.out.println("Before2"); //Changé de Before1 à Before2. Il en va de même pour les conseils ci-dessous.
	}
(Omis)
・
・
・

Les résultats sont les suivants. Vous pouvez voir que l'ordre d'exécution n'est pas perturbé entre les aspects 1 et 2.

Avant d'exécuter la méthode Around1
Before1
Avant d'exécuter la méthode Around2
Before2
【Hello World!】
AfterReturning2
After2
Après avoir exécuté la méthode Around2
AfterReturning1
After1
Après avoir exécuté la méthode Around1

Juste au cas où, j'ai ajouté @ Order (2) à Aspect1 et @Order (1) à Aspect2. Comme indiqué ci-dessous, l'ordre d'exécution n'était pas confondu entre les aspects 1 et 2, et seules les priorités des aspects pouvaient être échangées proprement.

Avant d'exécuter la méthode Around2
Before2
Avant d'exécuter la méthode Around1
Before1
【Hello World!】
AfterReturning1
After1
Après avoir exécuté la méthode Around1
AfterReturning2
After2
Après avoir exécuté la méthode Around2

en conclusion

Cette fois, nous avons examiné l'ordre d'exécution de Spring AOP. En particulier, le comportement après le point de jointure n'était pas bien compris, et même si je le recherchais, les résultats n'étaient pas cohérents avec le document, ce qui était assez déroutant. Peut-être que le créateur du document lui-même est déroutant, et il semble que la description devienne plus polie à mesure que la nouvelle version sort ...

J'espère que cet article sera utile à tout le monde. Aussi, si vous avez des erreurs, faites-le moi savoir ...!

Merci pour la lecture!

Recommended Posts

[Java] Ordre d'exécution de Spring AOP
Astuces Java - Résumé de l'exécution de Spring
Spring Java
À propos de Spring AOP
[Java] Spring DI ③
A propos de Spring AOP Pointcut
Présentation de Spring AOP
Exécution parallèle en Java
[Java] Ordre d'exécution de Spring AOP
Astuces Java - Résumé de l'exécution de Spring
Spring + Gradle + Java Quick Start
Exécution de débogage Java [pour les débutants Java]
[Java] Thymeleaf Basic (Spring Boot)
Introduction à Spring Boot ② ~ AOP ~
Application Java CICS-Run - (4) Application Spring Boot
Utilisation de Mapper avec Java (Spring)
[Java] Fonctionnement de Spring DI
Exécution de processus externe en Java
[Java] [Spring] Spring Boot 1.4-> 1.2 Note de rétrogradation
Comment faire un test unitaire de Spring AOP
Environnement Java Spring dans vs Code
cadre de printemps Mémo d'étude simple (2): AOP
Elastic Beanstalk (Java) + Spring Boot + https
A propos de la liaison de l'annotation Spring AOP
Implémenter reCAPTCHA v3 dans Java / Spring
Outils Spring Framework pour développeur Java
[Java] Spring DI ④ --Gestion du cycle de vie
[Java] Intégration LINE avec Spring Boot
Exécution de débogage IDE (eclipse), exécution d'étape (Java)
Spring AOP pour la première fois