JavaRush /Java Blog /Random-IT /Cos'è l'AOP? Nozioni di base sulla programmazione orienta...

Cos'è l'AOP? Nozioni di base sulla programmazione orientata agli aspetti

Pubblicato nel gruppo Random-IT
Ciao ragazzi! Senza comprendere i concetti di base, è piuttosto difficile approfondire le strutture e gli approcci alla creazione di funzionalità. Quindi oggi parleremo di uno di questi concetti: AOP, o programmazione orientata agli aspetti . Cos'è l'AOP?  Fondamenti di programmazione orientata agli aspetti - 1Questo non è un argomento facile e spesso non viene utilizzato direttamente, ma molti framework e tecnologie lo utilizzano dietro le quinte. E, naturalmente, a volte durante le interviste ti potrebbe essere chiesto di dirti in termini generali che tipo di animale è questo e dove può essere utilizzato. Diamo quindi un'occhiata ai concetti di base e ad alcuni semplici esempi di AOP in Java . Cos'è l'AOP?  Fondamenti di programmazione orientata agli aspetti - 2Pertanto, l'AOP ( programmazione orientata agli aspetti ) è un paradigma volto ad aumentare la modularità delle varie parti di un'applicazione separando gli aspetti trasversali. Per fare ciò, viene aggiunto un comportamento aggiuntivo al codice esistente, senza modificare il codice originale. In altre parole, sembra che aggiungiamo funzionalità aggiuntive ai metodi e alle classi senza apportare modifiche al codice modificato. Perché è necessario? Prima o poi arriviamo alla conclusione che il consueto approccio orientato agli oggetti non può sempre risolvere efficacemente alcuni problemi. In un momento simile, AOP viene in soccorso e ci fornisce strumenti aggiuntivi per creare l'applicazione. E strumenti aggiuntivi significano maggiore flessibilità nello sviluppo, grazie alla quale ci sono più opzioni per risolvere un particolare problema.

Applicazione dell'AOP

La programmazione orientata agli aspetti è progettata per risolvere problemi trasversali, che possono essere qualsiasi codice ripetuto più volte in modi diversi, che non può essere completamente strutturato in un modulo separato. Di conseguenza, con AOP possiamo lasciarlo fuori dal codice principale e definirlo verticalmente. Un esempio è l'applicazione di una politica di sicurezza in un'applicazione. In genere, la sicurezza riguarda molti elementi di un'applicazione. Inoltre, la politica di sicurezza dell'applicazione deve essere applicata allo stesso modo a tutte le parti nuove ed esistenti dell'applicazione. Allo stesso tempo, anche la politica di sicurezza utilizzata può evolversi. È qui che l'uso di AOP può tornare utile . Anche un altro esempio è la registrazione . L'utilizzo di un approccio AOP alla registrazione presenta diversi vantaggi rispetto all'inserimento manuale della registrazione:
  1. Il codice di registrazione è facile da implementare e rimuovere: devi solo aggiungere o rimuovere un paio di configurazioni di qualche aspetto.
  2. Tutto il codice sorgente per la registrazione è archiviato in un unico posto e non è necessario trovare manualmente tutti i luoghi di utilizzo.
  3. Il codice destinato al logging può essere aggiunto ovunque, siano essi metodi e classi già scritti o nuove funzionalità. Ciò riduce il numero di errori degli sviluppatori.
    Inoltre, quando rimuovi un aspetto da una configurazione di progetto, puoi essere assolutamente sicuro che tutto il codice di traccia venga rimosso e che non manchi nulla.
  4. Gli aspetti sono codice autonomo che può essere riutilizzato e migliorato più e più volte.
Cos'è l'AOP?  Fondamenti di programmazione orientata agli aspetti - 3AOP viene utilizzato anche per la gestione delle eccezioni, la memorizzazione nella cache e la rimozione di alcune funzionalità per renderlo riutilizzabile.

Concetti base dell'AOP

Per approfondire l’analisi dell’argomento, familiarizziamo prima con i concetti principali di AOP. Il consiglio è una logica aggiuntiva, un codice, che viene chiamato dal punto di connessione. Il consiglio può essere eseguito prima, dopo o al posto del punto di connessione (ne parleremo più avanti). Possibili tipologie di consulenza :
  1. Prima (Prima) - i consigli di questo tipo vengono avviati prima dell'esecuzione dei metodi target - punti di connessione. Quando utilizziamo gli aspetti come classi, prendiamo l'annotazione @Before per contrassegnare il tipo di consiglio come precedente. Quando si utilizzano gli aspetti come file .aj , questo sarà il metodo before() .
  2. Dopo (After) - avviso che viene eseguito dopo il completamento dell'esecuzione dei metodi - punti di connessione, sia in casi normali che quando viene lanciata un'eccezione.
    Quando utilizziamo gli aspetti come classi, possiamo utilizzare l'annotazione @After per indicare che questo è un suggerimento che viene dopo.
    Quando si utilizzano aspetti come file .aj , questo sarà il metodo after() .
  3. Dopo la restituzione : questi suggerimenti vengono eseguiti solo se il metodo di destinazione funziona normalmente, senza errori.
    Quando gli aspetti sono rappresentati come classi, possiamo utilizzare l' annotazione @AfterReturning per contrassegnare il consiglio come eseguito una volta completato con successo.
    Quando si utilizzano aspetti come file .aj, questo sarà il metodo after() che restituisce (Object obj) .
  4. After Throwing : questo tipo di avviso è destinato ai casi in cui un metodo, ovvero un punto di connessione, genera un'eccezione. Possiamo utilizzare questo consiglio per gestire in parte l'esecuzione non riuscita (ad esempio, ripristinando l'intera transazione o registrando con il livello di traccia richiesto).
    Per le classi di aspetto, l' annotazione @AfterThrowing viene utilizzata per indicare che questo avviso viene utilizzato dopo che è stata lanciata un'eccezione.
    Quando si utilizzano aspetti sotto forma di file .aj , questo sarà il metodo - after() lanciando (Exception e) .
  5. Around è forse uno dei tipi di consigli più importanti che circondano un metodo, ovvero un punto di connessione, con il quale possiamo, ad esempio, scegliere se eseguire o meno un determinato metodo di punto di connessione.
    È possibile scrivere codice di avviso da eseguire prima e dopo l'esecuzione del metodo del punto di join.
    Le responsabilità di Advisor includono la chiamata al metodo del punto di unione e la restituzione dei valori se il metodo restituisce qualcosa. Cioè, in questo suggerimento puoi semplicemente imitare il funzionamento del metodo di destinazione senza chiamarlo e di conseguenza restituire qualcosa di tuo.
    Per gli aspetti sotto forma di classi, utilizziamo l'annotazione @Around per creare suggerimenti che avvolgono il punto di connessione. Quando si utilizzano gli aspetti come file .aj , questo sarà il metodo around() .
Punto di unione : un punto in un programma in esecuzione (chiamata di un metodo, creazione di un oggetto, accesso a una variabile) in cui dovrebbero essere applicati i consigli. In altre parole, questa è una sorta di espressione regolare, con l'aiuto della quale si trovano i luoghi per l'introduzione del codice (luoghi per l'applicazione dei suggerimenti). Un punto di taglio è un insieme di punti di connessione . Il taglio determina se un determinato punto di connessione si adatta a una determinata punta. Aspect è un modulo o una classe che implementa funzionalità end-to-end. Un aspetto modifica il comportamento del resto del codice applicando consigli ai punti di unione definiti da alcune porzioni . In altre parole, è una combinazione di punte e punti di connessione. Introduzione : modifica della struttura di una classe e/o modifica della gerarchia di ereditarietà per aggiungere funzionalità di aspetto al codice esterno. Target è l'oggetto a cui verrà applicato il consiglio. La tessitura è il processo di collegamento degli aspetti con altri oggetti per creare gli oggetti proxy consigliati. Questa operazione può essere eseguita in fase di compilazione, caricamento o esecuzione. Esistono tre tipi di tessitura:
  • Tessitura in fase di compilazione : se disponi del codice sorgente di un aspetto e del codice in cui utilizzi gli aspetti, puoi 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 intrecciare aspetti nel tuo codice, puoi prendere classi o jar già compilati e iniettare aspetti;
  • la tessitura in fase di caricamento è semplicemente una tessitura binaria rinviata fino a quando il caricatore di classi carica il file di classe e definisce la classe per la JVM.
    Per supportare ciò, sono necessari uno o più "caricatori di classi di tessitura". Sono forniti esplicitamente dal runtime o attivati ​​dall'"agente di tessitura".
AspectJ è un'implementazione specifica dei paradigmi AOP che implementa la capacità di risolvere problemi trasversali. La documentazione può essere trovata qui .

Esempi in Java

Successivamente, per comprendere meglio l'AOP, esamineremo piccoli esempi del livello Hello World. Cos'è l'AOP?  Fondamenti di programmazione orientata agli aspetti - 4Vorrei subito notare che nei nostri esempi utilizzeremo il compile-time weaving . Per prima cosa dobbiamo aggiungere la seguente dipendenza al nostro pom.xml :

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Di norma, per utilizzare gli aspetti viene utilizzato uno speciale compilatore Ajs . IntelliJ IDEA non lo ha per impostazione predefinita, quindi quando lo scegli come compilatore di applicazioni devi specificare il percorso della distribuzione AspectJ . Puoi leggere ulteriori informazioni sul metodo per scegliere Ajs come compilatore in questa pagina. Questo è stato il primo metodo e il secondo (che ho usato) è stato aggiungere il seguente plugin a 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>
Successivamente, è consigliabile reimportare da Maven ed eseguire mvn clean compile . Ora passiamo agli esempi.

Esempio n. 1

Creiamo una classe Main . In esso avremo un punto di lancio e un metodo che stampa i nomi passatigli nella console:

public class Main {
 
  public static void main(String[] args) {
  printName("Толя");
  printName("Вова");
  printName("Sasha");
  }
 
  public static void printName(String name) {
     System.out.println(name);
  }
}
Niente di complicato: passavano il nome e lo visualizzavano nella console. Se lo eseguiamo ora, la console mostrerà:
Tolya Vova Sasha
Bene, è il momento di sfruttare la potenza dell'AOP. Ora dobbiamo creare un file - aspetto . Sono di due tipi: il primo è un file con estensione .aj , la seconda è una classe normale che implementa le funzionalità AOP utilizzando le annotazioni. Diamo prima un'occhiata a un file con estensione .aj :

public aspect GreetingAspect {
 
  pointcut greeting() : execution(* Main.printName(..));
 
  before() : greeting() {
     System.out.print("Привет ");
  }
}
Questo file è in qualche modo simile a una classe. Scopriamo cosa sta succedendo qui: pointcut - un taglio o un insieme di punti di connessione; saluto() — il nome di questa porzione; : esecuzione - durante l'esecuzione * - tutto, chiama - Main.printName(..) - questo metodo. Poi arriva il consiglio specifico - before() - che viene eseguito prima che venga chiamato il metodo di destinazione, : greet() - la sezione a cui reagisce questo consiglio, e sotto vediamo il corpo del metodo stesso, che è scritto in Java lingua che comprendiamo. Quando eseguiamo main con questo aspetto presente, otterremo il seguente output sulla console:
Ciao Tolya Ciao Vova Ciao Sasha
Possiamo vedere che ogni chiamata al metodo printName è stata modificata da un aspetto. Ora diamo un'occhiata a come apparirà l'aspetto, ma come 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("Привет ");
  }
}
Dopo l' aspetto del file .aj , tutto è più ovvio:
  • @Aspect denota che la classe data è un aspetto;
  • @Pointcut("execution(* Main.printName(String))") è un punto di taglio che si attiva su tutte le chiamate a Main.printName con un argomento in entrata di tipo String ;
  • @Before("greeting()") - avviso che viene applicato prima di chiamare il codice descritto nel punto di taglio di greet() .
L'esecuzione di main con questo aspetto non modificherà l'output della console:
Ciao Tolya Ciao Vova Ciao Sasha

Esempio n.2

Diciamo che abbiamo un metodo che esegue alcune operazioni per i client e chiamiamo questo metodo da main :

public class Main {
 
  public static void main(String[] args) {
  makeSomeOperation("Толя");
  }
 
  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
Utilizzando l' annotazione @Around creeremo qualcosa come una "pseudo-transazione":

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

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Открытие транзакции...");
     try {
        joinPoint.proceed();
        System.out.println("Закрытие транзакции....");
     }
     catch (Throwable throwable) {
        System.out.println("Операция не удалась, откат транзакции...");
     }
  }
  }
Utilizzando il metodo procede dell'oggetto ProceedingJoinPoint , chiamiamo il metodo del wrapper per determinare la sua posizione nella scheda e, di conseguenza, il codice nel metodo sopra joinPoint.proceed(); - questo è Prima , che è sotto - Dopo . Se eseguiamo main, entreremo nella console:
Apertura di una transazione... Esecuzione di alcune operazioni per il cliente - Tolya Chiusura di una transazione....
Se aggiungiamo un'eccezione al nostro metodo (improvvisamente l'operazione fallisce):

public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
Quindi otterremo l'output nella console:
Apertura di una transazione... Esecuzione di alcune operazioni per il client - Tolya L'operazione non è riuscita, la transazione è stata ripristinata...
Si è rivelata una pseudo-elaborazione del fallimento.

Esempio n.3

Come esempio successivo, facciamo qualcosa come accedere alla console. Innanzitutto, diamo un'occhiata a Main , dove si verifica la nostra pseudo logica aziendale:

public class Main {
  private String value;
 
  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое meaning>");
     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 , utilizzando setValue, imposteremo il valore della variabile interna - value , quindi utilizzando getValue, prenderemo questo valore e in checkValue controlleremo se questo valore è più lungo di 10 caratteri. In caso affermativo, verrà generata un'eccezione. Vediamo ora l'aspetto con cui registreremo il funzionamento 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("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }
 
  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
Cosa sta succedendo qui? @Pointcut("execution(* *(..))") - si connetterà a tutte le chiamate a tutti i metodi; @AfterReturning(value = "methodExecuting()", return = "returningValue") - avviso che verrà eseguito dopo che il metodo di destinazione sarà stato completato con successo. Qui abbiamo due casi:
  1. Quando un metodo ha un valore restituito if (returningValue != null) {
  2. Quando non è presente alcun valore restituito else {
@AfterThrowing(value = "methodExecuting()", Throwing = "exception") - avviso che verrà attivato in caso di errore, ovvero quando viene generata un'eccezione dal metodo. E di conseguenza, eseguendo main , otterremo una sorta di login nella console:
Il metodo - setValue, della classe - Main è stato eseguito con successo. Il metodo - getValue, della classe - Main, è stato eseguito con successo, con il risultato dell'esecuzione - <some value> Il metodo - checkValue, della classe - Main, è stato terminato in modo anomalo con un'eccezione - Metodo java.lang.Exception - main, class-Main, si è bloccato con un'eccezione - java.lang.Exception
Bene, poiché non abbiamo gestito l'eccezione, otterremo anche il suo stacktrace: Cos'è l'AOP?  Fondamenti di programmazione orientata agli aspetti - 5puoi leggere le eccezioni e la loro gestione in questi articoli: Eccezioni in Java e Eccezioni e loro gestione . Questo è tutto per me oggi. Oggi abbiamo conosciuto AOP e hai potuto vedere che questa bestia non è così spaventosa come viene descritta. Arrivederci a tutti!Cos'è l'AOP?  Fondamenti di programmazione orientata agli aspetti - 6
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION