CodeGym/Java Blog/Random-IT/Cos'è l'AOP? Principi di programmazione orientata agli as...
John Squirrels
Livello 41
San Francisco

Cos'è l'AOP? Principi di programmazione orientata agli aspetti

Pubblicato nel gruppo Random-IT
membri
Ciao, ragazzi e ragazze! Senza comprendere i concetti di base, è piuttosto difficile approfondire i framework e gli approcci alla funzionalità degli edifici. Quindi oggi parleremo di uno di questi concetti: AOP, noto anche come programmazione orientata agli aspetti . Cos'è l'AOP?  Principi di programmazione orientata agli aspetti - 1Questo argomento non è facile e raramente viene utilizzato direttamente, ma molti framework e tecnologie lo utilizzano sotto il cofano. E naturalmente, a volte durante le interviste, ti potrebbe essere chiesto di descrivere in termini generali che tipo di bestia è e dove può essere applicata. Diamo quindi un'occhiata ai concetti di base e ad alcuni semplici esempi di AOP in Java . Allora, AOP sta per programmazione orientata agli aspetti, che è un paradigma inteso ad aumentare la modularità delle diverse parti di un'applicazione separando le preoccupazioni trasversali. A tale scopo, viene aggiunto un comportamento aggiuntivo al codice esistente senza apportare modifiche al codice originale. In altre parole, possiamo pensarlo come appendere funzionalità aggiuntive a metodi e classi senza alterare il codice modificato. Perché è necessario? Prima o poi, concludiamo che il tipico approccio orientato agli oggetti non può sempre risolvere efficacemente determinati problemi. E quando arriva quel momento, AOP viene in soccorso e ci offre strumenti aggiuntivi per la creazione di applicazioni. E strumenti aggiuntivi significano maggiore flessibilità nello sviluppo del software, il che significa più opzioni per risolvere un particolare problema.

Applicazione dell'AOP

La programmazione orientata agli aspetti è progettata per eseguire attività trasversali, che possono essere qualsiasi codice che può essere ripetuto molte volte con metodi diversi, che non possono essere completamente strutturati in un modulo separato. Di conseguenza, AOP ci consente di mantenerlo al di fuori del codice principale e di dichiararlo verticalmente. Un esempio è l'utilizzo di una politica di sicurezza in un'applicazione. In genere, la sicurezza attraversa molti elementi di un'applicazione. Inoltre, la politica di sicurezza dell'applicazione deve essere applicata in modo uguale a tutte le parti esistenti e nuove dell'applicazione. Allo stesso tempo, una politica di sicurezza in uso può essa stessa evolvere. Questo è il posto perfetto per usare AOP . Inoltre, un altro esempio è la registrazione. Ci sono diversi vantaggi nell'usare l'approccio AOP alla registrazione piuttosto che aggiungere manualmente funzionalità di registrazione:
  1. Il codice per il logging è facile da aggiungere e rimuovere: basta aggiungere o rimuovere un paio di configurazioni di qualche aspetto.

  2. Tutto il codice sorgente per la registrazione è conservato in un unico posto, quindi non è necessario scovare manualmente tutti i luoghi in cui viene utilizzato.

  3. Il codice di registrazione può essere aggiunto ovunque, sia in metodi e classi che sono già stati scritti o in nuove funzionalità. Ciò riduce il numero di errori di codifica.

    Inoltre, quando si rimuove un aspetto da una configurazione di progetto, si può essere certi che tutto il codice di tracciamento è stato eliminato e che non è stato perso nulla.

  4. Gli aspetti sono codice separato che può essere migliorato e utilizzato più e più volte.
Cos'è l'AOP?  Principi di programmazione orientata agli aspetti - 2AOP viene utilizzato anche per la gestione delle eccezioni, la memorizzazione nella cache e l'estrazione di determinate funzionalità per renderlo riutilizzabile.

Principi di base dell'AOP

Per andare oltre in questo argomento, conosciamo prima i concetti principali di AOP. Consigli — Logica o codice aggiuntivo richiamato da un punto di join. I consigli possono essere eseguiti prima, dopo o al posto di un punto di unione (più informazioni su di essi di seguito). Possibili tipi di consulenza :
  1. Prima : questo tipo di avviso viene lanciato prima che vengano eseguiti i metodi di destinazione, ad esempio i punti di unione. Quando si usano gli aspetti come classi, si usa l' annotazione @Before per contrassegnare il consiglio come precedente. Quando si usano gli aspetti come file .aj , questo sarà il metodo before() .

  2. Dopo : avviso che viene eseguito dopo che l'esecuzione dei metodi (punti di unione) è stata completata, sia durante l'esecuzione normale che quando si genera un'eccezione.

    Quando si usano gli aspetti come classi, possiamo usare l' annotazione @After per indicare che si tratta di un consiglio che viene dopo.

    Quando si usano gli aspetti come file .aj , questo è il metodo after() .

  3. Dopo il ritorno : questo consiglio viene eseguito solo quando il metodo di destinazione termina normalmente, senza errori.

    Quando gli aspetti sono rappresentati come classi, possiamo utilizzare l' annotazione @AfterReturning per contrassegnare il consiglio come in esecuzione dopo il completamento con successo.

    Quando si usano gli aspetti come file .aj , questo sarà il metodo after() return (Object obj) .

  4. Dopo il lancio : questo consiglio è destinato ai casi in cui un metodo, ovvero il punto di unione, genera un'eccezione. Possiamo utilizzare questo consiglio per gestire determinati tipi di esecuzione non riuscita (ad esempio, per eseguire il rollback di un'intera transazione o registrare con il livello di traccia richiesto).

    Per gli aspetti della classe, l' annotazione @AfterThrowing viene utilizzata per indicare che questo avviso viene utilizzato dopo aver generato un'eccezione.

    Quando si utilizzano gli aspetti come file .aj , questo sarà il metodo after() throwing (Exception e) .

  5. Intorno — forse uno dei più importanti tipi di consigli. Racchiude un metodo, ovvero un punto di unione che possiamo utilizzare, ad esempio, per scegliere se eseguire o meno un determinato metodo del punto di unione.

    È possibile scrivere codice di avviso che viene eseguito prima e dopo l'esecuzione del metodo del punto di join.

    Il consiglio around è responsabile della chiamata del metodo del punto di join e dei valori restituiti se il metodo restituisce qualcosa. In altre parole, in questo consiglio, puoi semplicemente simulare l'operazione di un metodo target senza chiamarlo e restituire quello che vuoi come risultato di ritorno.

    Dati gli aspetti come classi, usiamo l' annotazione @Around per creare consigli che racchiudono un punto di unione. Quando si usano gli aspetti sotto forma di file .aj , questo metodo sarà il metodo around() .

Punto di unione — il punto in un programma in esecuzione (ad es. chiamata al metodo, creazione dell'oggetto, accesso alla variabile) in cui deve essere applicato il consiglio. In altre parole, questo è un tipo di espressione regolare usata per trovare posti per l'iniezione di codice (posti in cui dovrebbero essere applicati i consigli). Pointcut : un insieme di punti di unione . Un pointcut determina se un determinato consiglio è applicabile a un dato punto di unione. Aspetto : un modulo o una classe che implementa funzionalità trasversali. Aspect cambia il comportamento del codice rimanente applicando consigli nei punti di join definiti da alcuni pointcut . In altre parole, è una combinazione di consigli e punti di unione. introduzione— modifica della struttura di una classe e/o modifica della gerarchia di ereditarietà per aggiungere la funzionalità dell'aspetto al codice esterno. Destinazione : l'oggetto a cui verrà applicato il consiglio. Tessitura : il processo di collegamento degli aspetti ad altri oggetti per creare oggetti proxy consigliati. Questo può essere fatto in fase di compilazione, caricamento o esecuzione. Esistono tre tipi di tessitura:
  • Tessitura in fase di compilazione : se si dispone del codice sorgente dell'aspetto e del codice in cui si utilizza l'aspetto, è possibile compilare il codice sorgente e l'aspetto direttamente utilizzando il compilatore AspectJ;

  • Tessitura post-compilazione (tessitura binaria) : se non puoi o non vuoi utilizzare le trasformazioni del codice sorgente per tessere aspetti nel codice, puoi prendere classi o file jar compilati in precedenza e iniettare aspetti in essi;

  • Tessitura del tempo di caricamento : si tratta solo di tessitura binaria che viene ritardata fino a quando il caricatore di classi non carica il file di classe e definisce la classe per la JVM.

    Per supportarlo sono necessari uno o più caricatori di classi di tessitura. Sono forniti esplicitamente dal runtime o attivati ​​da un "agente di tessitura".

AspectJ — Un'implementazione specifica del paradigma AOP che implementa la capacità di eseguire attività trasversali. La documentazione può essere trovata qui .

Esempi in Java

Successivamente, per una migliore comprensione di AOP , esamineremo piccoli esempi in stile "Hello World". A destra della mazza, noterò che i nostri esempi useranno la tessitura in fase di compilazione . Innanzitutto, dobbiamo aggiungere la seguente dipendenza nel nostro file pom.xml :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Di norma, lo speciale compilatore ajc è il modo in cui usiamo gli aspetti. IntelliJ IDEA non lo include per impostazione predefinita, quindi quando lo si sceglie come compilatore dell'applicazione, è necessario specificare il percorso della distribuzione 5168 75 AspectJ . Questo è stato il primo modo. Il secondo, che è quello che ho usato, è registrare il seguente plugin nel file pom.xml :
<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>
Dopo questo, è una buona idea reimportare da Maven ed eseguire mvn clean compile . Ora passiamo direttamente agli esempi.

Esempio n. 1

Creiamo una classe principale . In esso avremo un punto di ingresso e un metodo che stampa un nome passato sulla console:
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);
  }
}
Non c'è niente di complicato qui. Abbiamo passato un nome e lo abbiamo visualizzato sulla console. Se eseguiamo il programma ora, vedremo quanto segue sulla console:
Conciatore Victor Sasha
Allora, è il momento di sfruttare la potenza di AOP. Ora dobbiamo creare un file di aspetto . Sono di due tipi: il primo ha estensione .aj . La seconda è una classe ordinaria che utilizza le annotazioni per implementare le funzionalità AOP . Diamo prima un'occhiata al file con l' estensione .aj :
public aspect GreetingAspect {

  pointcut greeting() : execution(* Main.printName(..));

  before() : greeting() {
     System.out.print("Hi, ");
  }
}
Questo file è un po' come una classe. Vediamo cosa sta succedendo qui: pointcut è un insieme di punti di join; greeting() è il nome di questo pointcut; : execution indica di applicarlo durante l'esecuzione di tutte le ( * ) chiamate del metodo Main.printName(...) . Segue un consiglio specifico — before() — che viene eseguito prima che venga chiamato il metodo target. : greeting() è il punto di divisione a cui risponde questo consiglio. Bene, e sotto vediamo il corpo del metodo stesso, che è scritto nel linguaggio Java, che comprendiamo. Quando eseguiamo main con questo aspetto presente, otterremo questo output della console:
Ciao, Tanner Ciao, Victor Ciao, Sasha
Possiamo vedere che ogni chiamata al metodo printName è stata modificata grazie ad un aspetto. Ora diamo un'occhiata a come sarebbe l'aspetto di una classe Java con annotazioni:
@Aspect
public class GreetingAspect{

  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Hi, ");
  }
}
Dopo il file di aspetto .aj , tutto diventa più ovvio qui:
  • @Aspect indica che questa classe è un aspetto;
  • @Pointcut("execution(* Main.printName(String))") è il punto di divisione attivato per tutte le chiamate a Main.printName con un argomento di input il cui tipo è String ;
  • @Before("greeting()") è un consiglio che viene applicato prima di chiamare il codice specificato nel punto di divisione greeting() .
L'esecuzione di main con questo aspetto non modifica l'output della console:
Ciao, Tanner Ciao, Victor Ciao, Sasha

Esempio n. 2

Supponiamo di avere un metodo che esegue alcune operazioni per i client e chiamiamo questo metodo da 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);
  }
}
Usiamo l' annotazione @Around per creare una "pseudo-transazione":
@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...");
     }
  }
  }
Con il metodo procede dell'oggetto ProceedingJoinPoint , chiamiamo il metodo wrapping per determinarne la posizione nell'avviso. Pertanto, il codice nel metodo precedente joinPoint.proceed(); è Before , mentre il codice sottostante è After . Se eseguiamo main , otteniamo questo nella console:
Apertura di una transazione... Esecuzione di alcune operazioni per il cliente Tanner Chiusura di una transazione...
Ma se lanciamo un'eccezione nel nostro metodo (per simulare un'operazione fallita):
public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
Quindi otteniamo questo output della console:
Apertura di una transazione... Esecuzione di alcune operazioni per il cliente Tanner L'operazione non è riuscita. Rollback della transazione...
Quindi quello che abbiamo ottenuto qui è una sorta di capacità di gestione degli errori.

Esempio n. 3

Nel nostro prossimo esempio, facciamo qualcosa come accedere alla console. Innanzitutto, dai un'occhiata a Main , dove abbiamo aggiunto alcune pseudo logiche di business:
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 , usiamo setValue per assegnare un valore alla variabile di istanza del valore . Quindi usiamo getValue per ottenere il valore, quindi chiamiamo checkValue per vedere se è più lungo di 10 caratteri. In tal caso, verrà generata un'eccezione. Ora diamo un'occhiata all'aspetto che useremo per registrare il lavoro dei metodi:
@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);
  }
}
Cosa sta succedendo qui? @Pointcut("execution(* *(..))") unirà tutte le chiamate di tutti i metodi. @AfterReturning(value = "methodExecuting()", return = "returningValue") è un consiglio che verrà eseguito dopo l'esecuzione corretta del metodo di destinazione. Abbiamo qui due casi:
  1. Quando il metodo ha un valore restituito — if (returningValue! = Null) {
  2. Quando non c'è alcun valore di ritorno — else {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") è un avviso che verrà attivato in caso di errore, ovvero quando il metodo genera un'eccezione. E di conseguenza, eseguendo main , otterremo una sorta di registrazione basata su console:
Esecuzione riuscita: metodo — setValue, classe — Main Esecuzione riuscita: metodo — getValue, classe — Main, valore restituito — <valore> Eccezione generata: metodo — checkValue, classe — Eccezione principale — java.lang.Exception Eccezione generata: metodo — main, class — Main, exception — java.lang.Exception
E poiché non abbiamo gestito le eccezioni, otterremo comunque una traccia dello stack: Cos'è l'AOP?  Principi di programmazione orientata agli aspetti - 3puoi leggere le eccezioni e la gestione delle eccezioni in questi articoli: Eccezioni in Java ed Eccezioni: cattura e gestione . Questo è tutto per me oggi. Oggi abbiamo fatto conoscenza con AOP , e avete potuto vedere che questa bestia non è così spaventosa come alcune persone pensano che sia. Arrivederci a tutti!
Commenti
  • Popolari
  • Nuovi
  • Vecchi
Devi avere effettuato l'accesso per lasciare un commento
Questa pagina non ha ancora commenti