Programmation orientée aspect, Spring AOP

La programmation orientée aspect (AOP) est un paradigme de programmation qui est un développement ultérieur de la programmation procédurale et orientée objet (OOP). L'idée de l'AOP est de mettre en évidence la fonctionnalité dite de bout en bout. Et donc tout est en ordre, ici je vais montrer comment le faire dans Java - Spring @AspectJ style d'annotation (il y a aussi un style xml basé sur un schéma, la fonctionnalité est similaire).

Mise en évidence de la fonctionnalité de bout en bout


À

image

et après

image

C'est-à-dire il existe des fonctionnalités qui affectent plusieurs modules, mais elles ne sont pas directement liées au code métier, et il serait bien de les placer dans un endroit séparé, comme illustré dans la figure ci-dessus.

Point de jonction



image

Join point - le prochain concept d'AOP, ce sont les points d'observation, l'adhésion au code où l'introduction de la fonctionnalité est prévue.

Pointcut


image

Un pointcut est une tranche, une requête de points d'attache, il peut s'agir d'un ou plusieurs points. Les règles d'interrogation des points sont très diverses, dans la figure ci-dessus, une demande d'annotation sur une méthode et une méthode spécifique. Les règles peuvent être combinées par &&, ||,!

Des conseils


image

Conseils - un ensemble d'instructions exécutées sur les points de coupure (Pointcut). Les instructions peuvent être exécutées sur un événement de différents types:

  • Avant - avant un appel de méthode
  • Après - après un appel de méthode
  • Après le retour - après le retour d'une valeur d'une fonction
  • Après le lancer - en cas d'exception
  • Après enfin - si le bloc enfin est exécuté
  • Autour - vous pouvez faire le pré., Le post., Le traitement avant l'appel de méthode, et également contourner généralement l'appel de méthode.

sur un Pointcut vous pouvez "accrocher" plusieurs Conseils de différents types.

Aspect


image

Aspect - un module qui contient des descriptions Pointcut et Advice.

Maintenant, je vais donner un exemple et finalement tout se mettra (ou presque) en place. Nous connaissons tous le code de journalisation qui imprègne de nombreux modules, sans rapport avec le code métier, mais néanmoins il est impossible sans lui. Et donc je sépare cette fonctionnalité du code d'entreprise.
Exemple - enregistrement de code

Service cible

@Service public class MyService { public void method1(List<String> list) { list.add("method1"); System.out.println("MyService method1 list.size=" + list.size()); } @AspectAnnotation public void method2() { System.out.println("MyService method2"); } public boolean check() { System.out.println("MyService check"); return true; } } 

Un aspect avec une description de Pointcut et des conseils.

 @Aspect @Component public class MyAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Before("callAtMyServicePublic()") public void beforeCallAtMethod1(JoinPoint jp) { String args = Arrays.stream(jp.getArgs()) .map(a -> a.toString()) .collect(Collectors.joining(",")); logger.info("before " + jp.toString() + ", args=[" + args + "]"); } @After("callAtMyServicePublic()") public void afterCallAt(JoinPoint jp) { logger.info("after " + jp.toString()); } } 

Et le code de test appelant

 @RunWith(SpringRunner.class) @SpringBootTest public class DemoAspectsApplicationTests { @Autowired private MyService service; @Test public void testLoggable() { List<String> list = new ArrayList(); list.add("test"); service.method1(list); service.method2(); Assert.assertTrue(service.check()); } } 

Explications Dans le service cible, il n'est pas fait mention de la journalisation, dans le code appelant d'autant plus que toute la journalisation est concentrée dans un module séparé
@Aspect
class MyAspect ...


Dans pointcut

  @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } 

J'ai demandé toutes les méthodes MyService publiques avec n'importe quel type de retour * et le nombre d'arguments (..)

Dans les conseils avant et après qui font référence à Pointcut (callAtMyServicePublic) , j'ai écrit des instructions pour écrire dans le journal. JoinPoint n'est pas un paramètre obligatoire, qui fournit des informations supplémentaires, mais s'il est utilisé, il devrait être le premier.
Tout est espacé en différents modules! Code de l'appelant, cible, journalisation.

Résultat dans la console

image

Les règles de coupe peuvent varier
Quelques exemples de pointcut et de conseils:

Demande d'annotation sur une méthode.

 @Pointcut("@annotation(AspectAnnotation)") public void callAtMyServiceAnnotation() { } 

Des conseils pour lui

  @Before("callAtMyServiceAnnotation()") public void beforeCallAt() { } 

Demande d'une méthode spécifique indiquant les paramètres de la méthode cible

 @Pointcut("execution(* com.example.demoAspects.MyService.method1(..)) && args(list,..))") public void callAtMyServiceMethod1(List<String> list) { } 

Des conseils pour lui

  @Before("callAtMyServiceMethod1(list)") public void beforeCallAtMethod1(List<String> list) { } 

Pointcut pour le résultat de retour

  @Pointcut("execution(* com.example.demoAspects.MyService.check())") public void callAtMyServiceAfterReturning() { } 

Des conseils pour lui

  @AfterReturning(pointcut="callAtMyServiceAfterReturning()", returning="retVal") public void afterReturningCallAt(boolean retVal) { } 

Un exemple de vérification des droits d'un Avis de type Around, via annotation

 @Retention(RUNTIME) @Target(METHOD) public @interface SecurityAnnotation { } // @Aspect @Component public class MyAspect { @Pointcut("@annotation(SecurityAnnotation) && args(user,..)") public void callAtMyServiceSecurityAnnotation(User user) { } @Around("callAtMyServiceSecurityAnnotation(user)") public Object aroundCallAt(ProceedingJoinPoint pjp, User user) { Object retVal = null; if (securityService.checkRight(user)) { retVal = pjp.proceed(); } return retVal; } 

Les méthodes qui doivent être vérifiées avant l'appel, à droite, peuvent être annotées avec "SecurityAnnotation", puis dans Aspect, nous en obtiendrons une tranche, et toutes seront interceptées avant l'appel et la vérification des droits sera effectuée.

Code cible:

 @Service public class MyService { @SecurityAnnotation public Balance getAccountBalance(User user) { // ... } @SecurityAnnotation public List<Transaction> getAccountTransactions(User user, Date date) { // ... } } 

Code de l'appelant:

 balance = myService.getAccountBalance(user); if (balance == null) { accessDenied(user); } else { displayBalance(balance); } 

C'est-à-dire dans le code appelant et la cible, il n'y a pas de vérification des droits, seulement le code métier lui-même.
Un exemple de profilage du même service à l'aide d'un Avis de type Autour

 @Aspect @Component public class MyAspect { @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Around("callAtMyServicePublic()") public Object aroundCallAt(ProceedingJoinPoint call) throws Throwable { StopWatch clock = new StopWatch(call.toString()); try { clock.start(call.toShortString()); return call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } } } 

Si nous exécutons le code appelant avec des appels aux méthodes MyService, nous obtenons l'heure de l'appel à chaque méthode. Ainsi, sans changer le code appelant et la cible, j'ai ajouté de nouvelles fonctionnalités: journalisation, profileur et sécurité.
Exemple d'utilisation dans les formulaires d'interface utilisateur

Il y a un code qui en définissant masque / affiche les champs du formulaire:

 public class EditForm extends Form { @Override public void init(Form form) { formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE)); // ... } 

vous pouvez également supprimer updateVisibility dans un avis de type Around

 @Aspect public class MyAspect { @Pointcut("execution(* com.example.demoAspects.EditForm.init() && args(form,..))") public void callAtInit(Form form) { } // ... @Around("callAtInit(form)") public Object aroundCallAt(ProceedingJoinPoint pjp, Form form) { formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE)); Object retVal = pjp.proceed(); return retVal; } 

etc.

Structure du projet

image

fichier pom
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demoAspects</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demoAspects</name> <description>Demo project for Spring Boot Aspects</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 


Matériaux

Programmation orientée aspect avec Spring

Source: https://habr.com/ru/post/fr428548/


All Articles