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:-
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.
-
Al kildekoden til logning opbevares ét sted, så du behøver ikke manuelt at jage alle de steder, hvor den bruges.
-
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.
- Aspekter er separat kode, der kan forbedres og bruges igen og igen.
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 :-
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.
- 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.
-
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.
-
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) .
-
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.
-
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".
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:
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:
@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.
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:
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:
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:
- Når metoden har en returværdi — if (returningValue! = Null) {
- Når der ikke er nogen returværdi — ellers {
GO TO FULL VERSION