CodeGym /Java blog /Tilfældig /Hvad er AOP? Principper for aspektorienteret programmerin...
John Squirrels
Niveau
San Francisco

Hvad er AOP? Principper for aspektorienteret programmering

Udgivet i gruppen
Hej gutter og piger! Uden at forstå de grundlæggende begreber er det ret svært at dykke ned i rammer og tilgange til opbygning af funktionalitet. Så i dag vil vi tale om et sådant koncept - AOP, alias aspektorienteret programmering . Hvad er AOP?  Principper for aspektorienteret programmering - 1Dette emne er ikke let og bruges sjældent direkte, men mange rammer og teknologier bruger det under emhætten. Og selvfølgelig, nogle gange under interviews, kan du blive bedt om at beskrive i generelle vendinger, hvilken slags dyr dette er, og hvor det kan anvendes. Så lad os tage et kig på de grundlæggende begreber og nogle enkle eksempler på AOP i Java . Nu så står AOP for aspektorienteret programmering, som er et paradigme, der har til formål at øge modulariteten af ​​de forskellige dele af en applikation ved at adskille tværgående bekymringer. For at opnå dette føjes yderligere adfærd til den eksisterende kode uden at foretage ændringer i den originale kode. Med andre ord kan vi tænke på det som at hænge yderligere funktionalitet oven på metoder og klasser uden at ændre den ændrede kode. Hvorfor er dette nødvendigt? Før eller siden konkluderer vi, at den typiske objektorienterede tilgang ikke altid effektivt kan løse visse problemer. Og når det øjeblik kommer, kommer AOP til undsætning og giver os yderligere værktøjer til at bygge applikationer. Og yderligere værktøjer betyder øget fleksibilitet i softwareudvikling, hvilket betyder flere muligheder for at løse et bestemt problem.

Anvendelse af AOP

Aspektorienteret programmering er designet til at udføre tværgående opgaver, som kan være enhver kode, der kan gentages mange gange ved forskellige metoder, som ikke kan struktureres fuldstændigt i et separat modul. Derfor lader AOP os holde dette uden for hovedkoden og erklære det lodret. Et eksempel er at bruge en sikkerhedspolitik i en applikation. Typisk kører sikkerhed gennem mange elementer i en applikation. Desuden bør applikationens sikkerhedspolitik anvendes ligeligt på alle eksisterende og nye dele af applikationen. Samtidig kan en sikkerhedspolitik i brug selv udvikle sig. Dette er det perfekte sted at bruge AOP . Et andet eksempel er også logning. Der er flere fordele ved at bruge AOP-tilgangen til logning i stedet for manuelt at tilføje logningsfunktioner:
  1. Koden til logning er nem at tilføje og fjerne: alt du skal gøre er at tilføje eller fjerne et par konfigurationer af et eller andet aspekt.

  2. Al kildekoden til logning opbevares ét sted, så du behøver ikke manuelt at jage alle de steder, hvor den bruges.

  3. Logningskode kan tilføjes hvor som helst, hvad enten det er i metoder og klasser, der allerede er skrevet eller i ny funktionalitet. Dette reducerer antallet af kodningsfejl.

    Når du fjerner et aspekt fra en designkonfiguration, kan du også være sikker på, at al sporingskoden er væk, og at der ikke er gået glip af noget.

  4. Aspekter er separat kode, der kan forbedres og bruges igen og igen.
Hvad er AOP?  Principper for aspektorienteret programmering - 2AOP bruges også til undtagelseshåndtering, cachelagring og udtrækning af visse funktioner for at gøre det genbrugeligt.

Grundlæggende principper for AOP

For at komme videre i dette emne, lad os først lære hovedbegreberne i AOP at kende. Råd — Yderligere logik eller kode kaldet fra et joinpunkt. Rådgivning kan udføres før, efter eller i stedet for et join-punkt (mere om dem nedenfor). Mulige typer råd :
  1. Før — denne type rådgivning lanceres, før målmetoder, dvs. joinpunkter, udføres. Når vi bruger aspekter som klasser, bruger vi @Before -annotationen til at markere rådene som kommende. Når du bruger aspekter som .aj- filer, vil dette være before() -metoden.

  2. Efter — rådgivning, der udføres efter eksekvering af metoder (join points) er fuldført, både i normal udførelse såvel som ved at kaste en undtagelse.

    Når vi bruger aspekter som klasser, kan vi bruge @After- annotationen til at indikere, at dette er råd, der kommer efter.

    Når du bruger aspekter som .aj- filer, er dette after()- metoden.

  3. Efter returnering — denne rådgivning udføres kun, når målmetoden afsluttes normalt uden fejl.

    Når aspekter er repræsenteret som klasser, kan vi bruge @AfterReturning- annotationen til at markere rådgivningen som udført efter vellykket afslutning.

    Når du bruger aspekter som .aj- filer, vil dette være den after() returnerende (Object obj) metode.

  4. Efter at have kastet - dette råd er beregnet til tilfælde, hvor en metode, det vil sige join-punkt, kaster en undtagelse. Vi kan bruge denne rådgivning til at håndtere visse former for mislykket eksekvering (for eksempel at rulle en hel transaktion eller log tilbage med det krævede sporingsniveau).

    For klasseaspekter bruges @AfterThrowing- annotationen til at angive, at dette råd bruges efter at have kastet en undtagelse.

    Når du bruger aspekter som .aj- filer, vil dette være after() throwing-metoden (undtagelse e) .

  5. Rundt - måske en af ​​de vigtigste typer råd. Den omgiver en metode, det vil sige et joinpunkt, som vi kan bruge til for eksempel at vælge, om vi vil udføre en given joinpoint-metode eller ej.

    Du kan skrive rådgivningskode, der kører før og efter joinpoint-metoden er udført.

    Rådgivningen omkring er ansvarlig for at kalde join point-metoden og returværdierne, hvis metoden returnerer noget. Med andre ord, i dette råd kan du blot simulere driften af ​​en målmetode uden at kalde den, og returnere hvad du vil som et returresultat.

    I betragtning af aspekter som klasser bruger vi @Around -annotationen til at skabe råd, der omslutter et sammenføjningspunkt. Når du bruger aspekter i form af .aj- filer, vil denne metode være around()- metoden.

Join Point — det punkt i et kørende program (dvs. metodekald, objektoprettelse, variabel adgang), hvor rådgivningen skal anvendes. Dette er med andre ord en slags regulært udtryk, der bruges til at finde steder til kodeinjektion (steder, hvor råd bør anvendes). Pointcut — et sæt sammenføjningspunkter . Et pointcut afgør, om et givet råd er gældende for et givet join-punkt. Aspect — et modul eller en klasse, der implementerer tværgående funktionalitet. Aspect ændrer adfærden for den resterende kode ved at anvende rådjoinpunkter defineret af nogle pointcut . Det er med andre ord en kombination af råd og joinpunkter. Introduktion— ændring af strukturen af ​​en klasse og/eller ændring af arvehierarkiet for at tilføje aspektets funktionalitet til fremmed kode. Mål — det objekt, som rådgivningen vil blive anvendt på. Vævning — processen med at forbinde aspekter til andre objekter for at skabe anbefalede proxy-objekter. Dette kan gøres ved kompileringstid, indlæsningstid eller køretid. Der er tre typer vævning:
  • Kompileringstidsvævning — hvis du har aspektets kildekode og koden, hvor du bruger aspektet, så kan du kompilere kildekoden og aspektet direkte ved hjælp af AspectJ-kompileren;

  • Post-compile vævning (binær vævning) — hvis du ikke kan eller ønsker at bruge kildekodetransformationer til at væve aspekter ind i koden, kan du tage tidligere kompilerede klasser eller jar-filer og injicere aspekter i dem;

  • Load-time vævning — dette er blot binær vævning, der forsinkes, indtil klasseindlæseren indlæser klassefilen og definerer klassen for JVM.

    Der kræves en eller flere væveklasselæssere til at understøtte dette. De er enten eksplicit leveret af runtime eller aktiveret af et "vævemiddel".

AspectJ — En specifik implementering af AOP- paradigmet, der implementerer evnen til at udføre tværgående opgaver. Dokumentationen kan findes her .

Eksempler i Java

Dernæst, for at få en bedre forståelse af AOP , vil vi se på små eksempler i "Hello World"-stil. Til højre for flagermusen vil jeg bemærke, at vores eksempler vil bruge kompileringstidsvævning . Først skal vi tilføje følgende afhængighed i vores pom.xml- fil:

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Som regel er den særlige ajc -kompiler, hvordan vi bruger aspekter. IntelliJ IDEA inkluderer det ikke som standard, så når du vælger det som programkompiler, skal du angive stien til 5168 75 AspectJ -distributionen. Dette var den første måde. Den anden, som er den, jeg brugte, er at registrere følgende plugin i pom.xml- filen:

<build>
  <plugins>
     <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
           <complianceLevel>1.8</complianceLevel>
           <source>1.8</source>
           <target>1.8</target>
           <showWeaveInfo>true</showWeaveInfo>
           <<verbose>true<verbose>
           <Xlint>ignore</Xlint>
           <encoding>UTF-8</encoding>
        </configuration>
        <executions>
           <execution>
              <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
              </goals>
           </execution>
        </executions>
     </plugin>
  </plugins>
</build>
Herefter er det en god idé at genimportere fra Maven og køre mvn clean compile . Lad os nu gå direkte til eksemplerne.

Eksempel nr. 1

Lad os skabe en hovedklasse . I den vil vi have et indgangspunkt og en metode, der udskriver et bestået navn på konsollen:

public class Main {
 
  public static void main(String[] args) {
  printName("Tanner");
  printName("Victor");
  printName("Sasha");
  }
 
  public static void printName(String name) {
     System.out.println(name);
  }
}
Der er ikke noget kompliceret her. Vi har givet et navn og vise det på konsollen. Hvis vi kører programmet nu, vil vi se følgende på konsollen:
Tanner Victor Sasha
Så er det tid til at drage fordel af kraften i AOP. Nu skal vi oprette en aspektfil . De er af to slags: den første har filtypenavnet .aj . Den anden er en almindelig klasse, der bruger annoteringer til at implementere AOP- funktioner. Lad os først se på filen med filtypenavnet .aj :

public aspect GreetingAspect {
 
  pointcut greeting() : execution(* Main.printName(..));
 
  before() : greeting() {
     System.out.print("Hi, ");
  }
}
Denne fil er lidt som en klasse. Lad os se, hvad der sker her: pointcut er et sæt sammenføjningspunkter; greeting() er navnet på denne pointcut; : execution angiver at anvende det under udførelsen af ​​alle ( * ) kald af Main.printName(...) metoden. Dernæst kommer et specifikt råd — before() — som udføres før målmetoden kaldes. : greeting() er skæringspunktet, som dette råd reagerer på. Nå, og nedenfor ser vi selve metoden, som er skrevet på Java-sproget, som vi forstår. Når vi kører main med dette aspekt til stede, får vi dette konsoloutput:
Hej Tanner Hej Victor Hej Sasha
Vi kan se, at hvert kald til printName- metoden er blevet ændret takket være et aspekt. Lad os nu tage et kig på, hvordan aspektet ville se ud som en Java-klasse med annoteringer:

@Aspect
public class GreetingAspect{
 
  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }
 
  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Hi, ");
  }
}
Efter .aj aspect-filen bliver alt mere indlysende her:
  • @Aspect angiver, at denne klasse er et aspekt;
  • @Pointcut("execution(* Main.printName(String))") er cutpointet, der udløses for alle kald til Main.printName med et input-argument, hvis type er String ;
  • @Before("greeting()") er råd, der anvendes før kald af koden specificeret i greeting() cutpoint.
At køre main med dette aspekt ændrer ikke konsoludgangen:
Hej Tanner Hej Victor Hej Sasha

Eksempel nr. 2

Antag, at vi har en metode, der udfører nogle operationer for klienter, og vi kalder denne metode fra hoved :

public class Main {
 
  public static void main(String[] args) {
  performSomeOperation("Tanner");
  }
 
  public static void performSomeOperation(String clientName) {
     System.out.println("Performing some operations for Client " + clientName);
  }
}
Lad os bruge @Around- annotationen til at oprette en "pseudo-transaktion":

@Aspect
public class TransactionAspect{
 
  @Pointcut("execution(* Main.performSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Opening a transaction...");
     try {
        joinPoint.proceed();
        System.out.println("Closing a transaction...");
     }
     catch (Throwable throwable) {
        System.out.println("The operation failed. Rolling back the transaction...");
     }
  }
  }
Med fortsæt -metoden for ProceedingJoinPoint- objektet kalder vi indpakningsmetoden for at bestemme dens placering i rådgivningen. Derfor er koden i metoden ovenfor joinPoint.proceed(); er Før , mens koden nedenfor er Efter . Hvis vi kører main , får vi dette i konsollen:
Åbner en transaktion... Udfører nogle handlinger for Client Tanner Afslutter en transaktion...
Men hvis vi kaster og undtagelse i vores metode (for at simulere en mislykket operation):

public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
Så får vi denne konsoludgang:
Åbner en transaktion... Udfører nogle handlinger for Client Tanner. Handlingen mislykkedes. Tilbagefører transaktionen...
Så det, vi endte med her, er en slags fejlhåndteringsevne.

Eksempel nr. 3

I vores næste eksempel, lad os gøre noget som at logge på konsollen. Tag først et kig på Main , hvor vi har tilføjet noget pseudo-forretningslogik:

public class Main {
  private String value;
 
  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<some value>");
     String valueForCheck = main.getValue();
     main.checkValue(valueForCheck);
  }
 
  public void setValue(String value) {
     this.value = value;
  }
 
  public String getValue() {
     return this.value;
  }
 
  public void checkValue(String value) throws Exception {
     if (value.length() > 10) {
        throw new Exception();
     }
  }
}
I main bruger vi setValue til at tildele en værdi til værdiforekomstvariablen . Så bruger vi getValue til at få værdien, og så kalder vi checkValue for at se om den er længere end 10 tegn. Hvis det er tilfældet, vil en undtagelse blive kastet. Lad os nu se på det aspekt, vi vil bruge til at logge arbejdet med metoderne:

@Aspect
public class LogAspect {
 
  @Pointcut("execution(* *(..))")
  public void methodExecuting() {
  }
 
  @AfterReturning(value = "methodExecuting()", returning = "returningValue")
  public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
     if (returningValue != null) {
        System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Successful execution: method — %s, class — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }
 
  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
Hvad sker der her? @Pointcut("execution(* *(..))") vil forbinde alle kald af alle metoder. @AfterReturning(value = "methodExecuting()", returning = "returningValue") er et råd, der vil blive eksekveret efter vellykket udførelse af målmetoden. Vi har to sager her:
  1. Når metoden har en returværdi — if (returningValue! = Null) {
  2. Når der ikke er nogen returværdi — ellers {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") er et råd, der vil blive udløst i tilfælde af en fejl, dvs. når metoden kaster en undtagelse. Og følgelig vil vi ved at køre main , få en slags konsolbaseret logning:
Vellykket eksekvering: metode — setValue, klasse — Main Vellykket eksekvering: metode — getValue, klasse — Hoved, returværdi — <en vis værdi> Exception thrown: method — checkValue, class — Main exception — java.lang.Exception Exception thrown: method — hoved, klasse — Hoved, undtagelse — java.lang.Undtagelse
Og da vi ikke håndterede undtagelserne, får vi stadig et stakspor: Hvad er AOP?  Principper for aspektorienteret programmering - 3Du kan læse om undtagelser og undtagelseshåndtering i disse artikler: Undtagelser i Java og Undtagelser: fangst og håndtering . Det var alt for mig i dag. I dag stiftede vi bekendtskab med AOP , og du kunne se, at dette udyr ikke er så skræmmende, som nogle mennesker gør det til. Farvel, alle sammen!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION