Aspektorientierte Programmierung, Spring AOP

Aspektorientierte Programmierung (AOP) ist ein Programmierparadigma, das eine Weiterentwicklung der prozeduralen und objektorientierten Programmierung (OOP) darstellt. Die Idee von AOP ist es, die sogenannte End-to-End-Funktionalität hervorzuheben. Und damit alles in Ordnung ist, werde ich hier zeigen, wie es in Java gemacht wird - Spring @AspectJ Annotation Style (es gibt auch einen schemabasierten XML-Stil, die Funktionalität ist ähnlich).

Hervorheben der End-to-End-Funktionalität


Zu

Bild

und danach

Bild

Das heißt, Es gibt Funktionen, die mehrere Module betreffen, die sich jedoch nicht direkt auf den Geschäftscode beziehen. Es wäre schön, sie an einem separaten Ort abzulegen. Dies ist in der obigen Abbildung dargestellt.

Verbindungspunkt



Bild

Verbindungspunkt - das nächste Konzept von AOP, dies sind die Beobachtungspunkte, der Zugang zum Code, an dem die Einführung von Funktionen geplant ist.

Pointcut


Bild

Ein Pointcut ist ein Slice, eine Abfrage nach Anhangspunkten. Es kann sich um einen oder mehrere Punkte handeln. Die Regeln für das Abfragen von Punkten sind sehr unterschiedlich. In der obigen Abbildung gibt es eine Anforderung für Anmerkungen zu einer Methode und eine bestimmte Methode. Regeln können durch &&, ||, kombiniert werden!

Beratung


Bild

Hinweis - Eine Reihe von Anweisungen, die an den Schnittpunkten ausgeführt werden (Pointcut). Anweisungen können für eine Veranstaltung verschiedener Art ausgeführt werden:

  • Vor - vor einem Methodenaufruf
  • Nach - nach einem Methodenaufruf
  • Nach der Rückgabe - nach der Rückgabe eines Wertes von einer Funktion
  • Nach dem Werfen - im Ausnahmefall
  • Nach finally - wenn der finally-Block ausgeführt wird
  • Around - Sie können die Vor-, Nach- und Verarbeitung vor dem Methodenaufruf durchführen und den Methodenaufruf im Allgemeinen umgehen.

An einem Pointcut können Sie mehrere Ratschläge verschiedener Typen "aufhängen".

Aspekt


Bild

Aspekt - ein Modul, das Pointcut- und Hinweisbeschreibungen enthält.

Jetzt werde ich ein Beispiel geben und schließlich wird alles (oder fast alles) zusammenpassen. Wir alle kennen den Protokollierungscode, der viele Module durchdringt und nicht mit dem Geschäftscode zusammenhängt, aber ohne ihn ist dies nicht möglich. Und so trenne ich diese Funktionalität vom Geschäftscode.
Beispiel - Code-Protokollierung

Zieldienst

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

Ein Aspekt, der Pointcut und Ratschläge beschreibt.

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

Und der aufrufende Testcode

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

Erklärungen Im Zieldienst wird die Protokollierung nicht erwähnt, im aufrufenden Code wird die gesamte Protokollierung in einem separaten Modul konzentriert
@Aspect
class MyAspect ...


In pointcut

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

Ich habe alle öffentlichen MyService-Methoden mit einem beliebigen Rückgabetyp * und der Anzahl der Argumente (..) angefordert .

In den Hinweisen vor und nach dem Hinweis auf Pointcut (callAtMyServicePublic) habe ich Anweisungen zum Schreiben in das Protokoll geschrieben. JoinPoint ist kein erforderlicher Parameter, der zusätzliche Informationen bereitstellt. Wenn er jedoch verwendet wird, sollte er der erste sein.
Alles ist in verschiedene Module unterteilt! Anrufercode, Ziel, Protokollierung.

Ergebnis in der Konsole

Bild

Pointcut-Regeln können variieren
Einige Beispiele für Pointcut und Ratschläge:

Anforderung einer Anmerkung zu einer Methode.

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

Rat für ihn

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

Anforderung einer bestimmten Methode, die die Parameter der Zielmethode angibt

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

Rat für ihn

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

Pointcut für das Rückgabeergebnis

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

Rat für ihn

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

Ein Beispiel für die Überprüfung von Rechten für einen Hinweis vom Typ Around mithilfe von Anmerkungen

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

Die Methoden, die vor dem Aufruf auf der rechten Seite überprüft werden müssen, können mit "SecurityAnnotation" versehen werden. In Aspect erhalten wir dann einen Teil davon, und alle werden abgefangen, bevor der Aufruf und die Rechteprüfung durchgeführt werden.

Zielcode:

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

Anrufercode:

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

Das heißt, Im aufrufenden Code und im Ziel gibt es keine Überprüfung der Rechte, nur den Geschäftscode selbst.
Ein Beispiel für die Profilerstellung desselben Dienstes mithilfe eines Hinweises vom Typ Around

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

Wenn wir den aufrufenden Code mit Aufrufen der MyService-Methoden ausführen, erhalten wir die Zeit des Aufrufs für jede Methode. Ohne den aufrufenden Code und das Ziel zu ändern, habe ich neue Funktionen hinzugefügt: Protokollierung, Profiler und Sicherheit.
Beispiel für die Verwendung in UI-Formularen

Es gibt einen Code, der durch Festlegen von Feldern im Formular ein- / ausgeblendet wird:

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

Sie können updateVisibility auch in einem Hinweis vom Typ Around entfernen

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

usw.

Projektstruktur

Bild

POM-Datei
 <?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> 


Material

Aspektorientierte Programmierung mit Feder

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


All Articles