
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:-
Il codice per il logging è facile da aggiungere e rimuovere: basta aggiungere o rimuovere un paio di configurazioni di qualche aspetto.
-
Tutto il codice sorgente per la registrazione è conservato in un unico posto, quindi non è necessario scovare manualmente tutti i luoghi in cui viene utilizzato.
-
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.
- Gli aspetti sono codice separato che può essere migliorato e utilizzato più e più volte.

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 :-
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() .
- 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() .
-
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) .
-
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) .
-
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() .
-
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".
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:
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:
@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() .
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:
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:
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:
- Quando il metodo ha un valore restituito — if (returningValue! = Null) {
- Quando non c'è alcun valore di ritorno — else {

GO TO FULL VERSION