CodeGym /Java Blog /Willekeurig /Wat is AOP? Principes van aspectgericht programmeren
John Squirrels
Niveau 41
San Francisco

Wat is AOP? Principes van aspectgericht programmeren

Gepubliceerd in de groep Willekeurig
Hoi, jongens en meiden! Zonder de basisconcepten te begrijpen, is het vrij moeilijk om je te verdiepen in kaders en benaderingen voor het bouwen van functionaliteit. Dus vandaag zullen we het hebben over zo'n concept: AOP, ook wel aspectgeoriënteerd programmeren genoemd . Wat is AOP?  Principes van aspectgericht programmeren - 1Dit onderwerp is niet gemakkelijk en wordt zelden direct gebruikt, maar veel frameworks en technologieën gebruiken het onder de motorkap. En natuurlijk wordt je soms tijdens interviews gevraagd om in algemene termen te beschrijven wat voor soort beest dit is en waar het kan worden toegepast. Laten we dus eens kijken naar de basisconcepten en enkele eenvoudige voorbeelden van AOP in Java . Welnu, AOP staat voor aspect-georiënteerd programmeren, wat een paradigma is dat bedoeld is om de modulariteit van de verschillende onderdelen van een applicatie te vergroten door transversale zorgen te scheiden. Om dit te bereiken, wordt extra gedrag toegevoegd aan de bestaande code zonder wijzigingen aan te brengen in de originele code. Met andere woorden, we kunnen het zien als het ophangen van extra functionaliteit bovenop methoden en klassen zonder de gewijzigde code te wijzigen. Waarom is dit nodig? Vroeg of laat komen we tot de conclusie dat de typische objectgeoriënteerde benadering bepaalde problemen niet altijd effectief kan oplossen. En wanneer dat moment aanbreekt, schiet AOP te hulp en geeft ons extra tools voor het bouwen van applicaties. En extra tools zorgen voor meer flexibiliteit in softwareontwikkeling, wat meer opties betekent voor het oplossen van een bepaald probleem.

AOP toepassen

Aspect-georiënteerd programmeren is ontworpen om transversale taken uit te voeren, wat elke code kan zijn die vele malen kan worden herhaald door verschillende methoden, die niet volledig in een afzonderlijke module kunnen worden gestructureerd. Dienovereenkomstig laat AOP ons dit buiten de hoofdcode houden en verticaal declareren. Een voorbeeld is het gebruik van een beveiligingsbeleid in een applicatie. Doorgaans loopt de beveiliging door veel elementen van een applicatie. Bovendien moet het beveiligingsbeleid van de applicatie in gelijke mate worden toegepast op alle bestaande en nieuwe onderdelen van de applicatie. Tegelijkertijd kan een in gebruik zijnd beveiligingsbeleid zelf evolueren. Dit is de perfecte plaats om AOP te gebruiken . Een ander voorbeeld is loggen. Er zijn verschillende voordelen aan het gebruik van de AOP-benadering voor logboekregistratie in plaats van handmatig logboekfunctionaliteit toe te voegen:
  1. De code voor het loggen is eenvoudig toe te voegen en te verwijderen: u hoeft alleen maar een paar configuraties van een bepaald aspect toe te voegen of te verwijderen.

  2. Alle broncode voor logboekregistratie wordt op één plaats bewaard, dus u hoeft niet handmatig alle plaatsen op te sporen waar deze wordt gebruikt.

  3. Logging-code kan overal worden toegevoegd, of het nu gaat om methoden en klassen die al zijn geschreven of om nieuwe functionaliteit. Dit vermindert het aantal codeerfouten.

    Ook wanneer u een aspect uit een ontwerpconfiguratie verwijdert, kunt u er zeker van zijn dat alle traceercode is verdwenen en dat er niets is gemist.

  4. Aspecten zijn aparte code die steeds weer kan worden verbeterd en gebruikt.
Wat is AOP?  Principes van aspectgericht programmeren - 2AOP wordt ook gebruikt voor het afhandelen van uitzonderingen, caching en het extraheren van bepaalde functionaliteit om het herbruikbaar te maken.

Basisprincipes van AOP

Om verder te gaan in dit onderwerp, laten we eerst de belangrijkste concepten van AOP leren kennen. Advies — Aanvullende logica of code aangeroepen vanaf een join-punt. Advies kan worden uitgevoerd vóór, na of in plaats van een verbindingspunt (meer hierover hieronder). Mogelijke soorten advies :
  1. Voor — dit type advies wordt gelanceerd voordat doelmethoden, dwz verbindingspunten, worden uitgevoerd. Wanneer we aspecten als klassen gebruiken, gebruiken we de @Before annotatie om het advies te markeren als komend ervoor. Wanneer aspecten als .aj- bestanden worden gebruikt, is dit de methode before() .

  2. After — advies dat wordt uitgevoerd nadat de uitvoering van methoden (join points) is voltooid, zowel bij normale uitvoering als bij het genereren van een uitzondering.

    Wanneer we aspecten als klassen gebruiken, kunnen we de annotatie @After gebruiken om aan te geven dat dit een advies is dat erna komt.

    Bij het gebruik van aspecten als .aj- bestanden is dit de methode after() .

  3. Na terugkeer - dit advies wordt alleen uitgevoerd als de doelmethode normaal en zonder fouten wordt voltooid.

    Wanneer aspecten worden weergegeven als klassen, kunnen we de annotatie @AfterReturning gebruiken om het advies na succesvolle voltooiing te markeren als uitvoerend.

    Wanneer aspecten als .aj- bestanden worden gebruikt, is dit de methode after() die terugkeert (Object obj) .

  4. After Throwing — dit advies is bedoeld voor gevallen waarin een methode, dat wil zeggen een join-punt, een exception genereert. We kunnen dit advies gebruiken om bepaalde soorten mislukte uitvoering af te handelen (bijvoorbeeld om een ​​volledige transactie of log terug te draaien met het vereiste traceerniveau).

    Voor klasse-aspecten wordt de annotatie @AfterThrowing gebruikt om aan te geven dat dit advies wordt gebruikt na het genereren van een uitzondering.

    Wanneer aspecten als .aj- bestanden worden gebruikt, is dit de after() throwing-methode (uitzondering e) .

  5. Rond - misschien wel een van de belangrijkste soorten advies. Het omringt een methode, dat wil zeggen een verbindingspunt dat we kunnen gebruiken om bijvoorbeeld te kiezen of we een bepaalde verbindingspuntmethode al dan niet willen uitvoeren.

    U kunt adviescode schrijven die wordt uitgevoerd voordat en nadat de join-puntmethode wordt uitgevoerd.

    Het advies rond is verantwoordelijk voor het aanroepen van de join-puntmethode en de retourwaarden als de methode iets retourneert. Met andere woorden, in dit advies kunt u eenvoudig de werking van een doelmethode simuleren zonder deze aan te roepen, en als resultaat retourneren wat u maar wilt.

    Gegeven aspecten als klassen, gebruiken we de @Around- annotatie om advies te maken dat een join-punt omsluit. Bij gebruik van aspecten in de vorm van .aj- bestanden, is deze methode de around()- methode.

Join Point — het punt in een lopend programma (dwz methodeaanroep, objectcreatie, variabele toegang) waar het advies moet worden toegepast. Met andere woorden, dit is een soort reguliere expressie die wordt gebruikt om plaatsen te vinden voor code-injectie (plaatsen waar advies moet worden toegepast). Pointcut — een reeks verbindingspunten . Een pointcut bepaalt of een gegeven advies van toepassing is op een bepaald joinpunt. Aspect — een module of klasse die transversale functionaliteit implementeert. Aspect verandert het gedrag van de resterende code door advies toe te passen op verbindingspunten die zijn gedefinieerd door een pointcut . Met andere woorden, het is een combinatie van advies en joinpoints. Invoering— de structuur van een klasse wijzigen en/of de overervingshiërarchie wijzigen om de functionaliteit van het aspect toe te voegen aan buitenlandse code. Doel — het object waarop het advies wordt toegepast. Weaving - het proces van het koppelen van aspecten aan andere objecten om geadviseerde proxy-objecten te creëren. Dit kan worden gedaan tijdens het compileren, tijdens het laden of tijdens de uitvoering. Er zijn drie soorten weven:
  • Compileer-time weaving — als je de broncode van het aspect hebt en de code waarin je het aspect gebruikt, dan kun je de broncode en het aspect rechtstreeks compileren met behulp van de AspectJ-compiler;

  • Post-compile weaving (binary weaving) — als je geen broncodetransformaties kunt of wilt gebruiken om aspecten in de code te weven, kun je eerder gecompileerde klassen of jar-bestanden nemen en aspecten erin injecteren;

  • Load-time weaving - dit is gewoon binair weven dat wordt uitgesteld totdat de classloader het klassenbestand laadt en de klasse voor de JVM definieert.

    Om dit te ondersteunen zijn een of meer weefklasseladers vereist. Ze worden expliciet geleverd door de runtime of geactiveerd door een 'weefmiddel'.

AspectJ - Een specifieke implementatie van het AOP- paradigma dat de mogelijkheid implementeert om transversale taken uit te voeren. De documentatie is hier te vinden .

Voorbeelden in Java

Vervolgens zullen we voor een beter begrip van AOP kijken naar kleine voorbeelden in "Hallo wereld"-stijl. Meteen merk ik op dat onze voorbeelden compile-time weaving zullen gebruiken . Eerst moeten we de volgende afhankelijkheid toevoegen aan ons pom.xml- bestand:

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
In de regel is de speciale ajc- compiler hoe we aspecten gebruiken. IntelliJ IDEA bevat het niet standaard, dus wanneer u het kiest als de toepassingscompiler, moet u het pad naar de 5168 75 AspectJ- distributie specificeren. Dit was de eerste manier. De tweede, die ik heb gebruikt, is om de volgende plug-in te registreren in het pom.xml- bestand:

<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>
Hierna is het een goed idee om opnieuw te importeren vanuit Maven en mvn clean compile uit te voeren . Laten we nu direct doorgaan naar de voorbeelden.

Voorbeeld nr. 1

Laten we een hoofdklasse maken . Daarin hebben we een toegangspunt en een methode die een doorgegeven naam op de console afdrukt:

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);
  }
}
Er is hier niets ingewikkelds. We hebben een naam doorgegeven en deze op de console weergegeven. Als we het programma nu uitvoeren, zien we het volgende op de console:
Tanner Victor Sasha
Nu is het tijd om te profiteren van de kracht van AOP. Nu moeten we een aspectbestand maken . Er zijn twee soorten: de eerste heeft de bestandsextensie .aj . De tweede is een gewone klasse die annotaties gebruikt om AOP- mogelijkheden te implementeren. Laten we eerst eens kijken naar het bestand met de extensie .aj :

public aspect GreetingAspect {
 
  pointcut greeting() : execution(* Main.printName(..));
 
  before() : greeting() {
     System.out.print("Hi, ");
  }
}
Dit bestand is een soort klasse. Laten we eens kijken wat hier gebeurt: pointcut is een set join-punten; begroeting() is de naam van deze pointcut; : uitvoering geeft aan om het toe te passen tijdens de uitvoering van alle ( * ) aanroepen van de methode Main.printName(...) . Vervolgens komt er een specifiek advies — before() — dat wordt uitgevoerd voordat de doelmethode wordt aangeroepen. : begroeting() is het snijpunt waarop dit advies reageert. Nou, en hieronder zien we de body van de methode zelf, die is geschreven in de Java-taal, die we begrijpen. Wanneer we main uitvoeren met dit aspect aanwezig, krijgen we deze console-uitvoer:
Hallo, Tanner Hallo, Victor Hallo, Sasha
We kunnen zien dat elke aanroep van de methode printName is gewijzigd dankzij een aspect. Laten we nu eens kijken hoe het aspect eruit zou zien als een Java-klasse met annotaties:

@Aspect
public class GreetingAspect{
 
  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }
 
  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Hi, ");
  }
}
Na het .aj- aspectbestand wordt alles hier duidelijker:
  • @Aspect geeft aan dat deze klasse een aspect is;
  • @Pointcut("execution(* Main.printName(String))") is het cutpoint dat wordt geactiveerd voor alle oproepen naar Main.printName met een invoerargument waarvan het type String is ;
  • @Before("greeting()") is een advies dat wordt toegepast voordat de code wordt aangeroepen die is opgegeven in het begroeting()- afkappunt.
Het uitvoeren van main met dit aspect verandert niets aan de console-uitvoer:
Hallo, Tanner Hallo, Victor Hallo, Sasha

Voorbeeld nr. 2

Stel dat we een methode hebben die bepaalde bewerkingen voor clients uitvoert, en we noemen deze methode vanuit main :

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);
  }
}
Laten we de @Around- annotatie gebruiken om een ​​"pseudo-transactie" te maken:

@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...");
     }
  }
  }
Met de methode doorgaan van het object ProceedingJoinPoint roepen we de methode wrap aan om de locatie in het advies te bepalen. Daarom is de code in de bovenstaande methode joinPoint.proceed(); is Before , terwijl de onderstaande code After is . Als we main uitvoeren , krijgen we dit in de console:
Een transactie openen... Enkele bewerkingen uitvoeren voor klant Tanner Een transactie sluiten...
Maar als we een uitzondering in onze methode gooien (om een ​​mislukte bewerking te simuleren):

public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
Dan krijgen we deze console-uitvoer:
Een transactie openen... Bezig met het uitvoeren van enkele bewerkingen voor cliënt Tanner De bewerking is mislukt. De transactie ongedaan maken...
Dus wat we hier hebben gevonden, is een soort foutafhandelingsvermogen.

Voorbeeld nr. 3

Laten we in ons volgende voorbeeld bijvoorbeeld inloggen op de console. Kijk eerst eens naar Main , waar we wat pseudo-bedrijfslogica hebben toegevoegd:

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();
     }
  }
}
In main gebruiken we setValue om een ​​waarde toe te wijzen aan de waarde- instantievariabele. Vervolgens gebruiken we getValue om de waarde op te halen en vervolgens bellen we checkValue om te zien of deze langer is dan 10 tekens. Als dat het geval is, wordt er een uitzondering gegenereerd. Laten we nu eens kijken naar het aspect dat we zullen gebruiken om het werk van de methoden te loggen:

@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);
  }
}
Wat is hier aan de hand? @Pointcut("execution(* *(..))") voegt zich bij alle aanroepen van alle methodes. @AfterReturning(value = "methodExecuting()", return = "returningValue") is een advies dat zal worden uitgevoerd na succesvolle uitvoering van de doelmethode. We hebben hier twee gevallen:
  1. Wanneer de methode een retourwaarde heeft — if (returningValue! = Null) {
  2. Als er geen retourwaarde is — else {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") is een advies dat wordt geactiveerd in geval van een fout, dat wil zeggen wanneer de methode een exception genereert. En dienovereenkomstig krijgen we , door main uit te voeren, een soort console-gebaseerde logging:
Succesvolle uitvoering: methode — setValue, klasse — Main Succesvolle uitvoering: methode — getValue, klasse — Main, retourwaarde — <een waarde> Gegooide uitzondering: methode — checkValue, klasse — Hoofduitzondering — java.lang.Exception Gegooide uitzondering: methode — main, class — Main, exception — java.lang.Exception
En aangezien we de uitzonderingen niet hebben afgehandeld, krijgen we nog steeds een stacktracering: Wat is AOP?  Principes van aspectgericht programmeren - 3U kunt lezen over uitzonderingen en het afhandelen van uitzonderingen in deze artikelen: Uitzonderingen in Java en Uitzonderingen: vangen en afhandelen . Dat is alles voor mij vandaag. Vandaag hebben we kennis gemaakt met AOP , en je hebt kunnen zien dat dit beest niet zo eng is als sommige mensen doen voorkomen. Tot ziens iedereen!
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION