Aplicando POA
La programación orientada a aspectos está diseñada para realizar tareas transversales, que pueden ser cualquier código que se puede repetir muchas veces por diferentes métodos, que no se pueden estructurar completamente en un módulo separado. En consecuencia, AOP nos permite mantener esto fuera del código principal y declararlo verticalmente. Un ejemplo es el uso de una política de seguridad en una aplicación. Por lo general, la seguridad se ejecuta a través de muchos elementos de una aplicación. Además, la política de seguridad de la aplicación debe aplicarse por igual a todas las partes nuevas y existentes de la aplicación. Al mismo tiempo, una política de seguridad en uso puede evolucionar. Este es el lugar perfecto para usar AOP . Además, otro ejemplo es el registro. Hay varias ventajas al usar el enfoque AOP para el registro en lugar de agregar funciones de registro manualmente:-
El código para iniciar sesión es fácil de agregar y quitar: todo lo que necesita hacer es agregar o quitar un par de configuraciones de algún aspecto.
-
Todo el código fuente para el registro se mantiene en un solo lugar, por lo que no necesita buscar manualmente todos los lugares donde se usa.
-
El código de registro se puede agregar en cualquier lugar, ya sea en métodos y clases que ya se han escrito o en una nueva funcionalidad. Esto reduce el número de errores de codificación.
Además, al eliminar un aspecto de una configuración de diseño, puede estar seguro de que todo el código de seguimiento se ha ido y que no se ha perdido nada.
- Los aspectos son código separado que se puede mejorar y usar una y otra vez.
Principios básicos de AOP
Para avanzar más en este tema, primero conozcamos los conceptos principales de AOP. Consejo : Lógica adicional o código llamado desde un punto de unión. Los consejos se pueden realizar antes, después o en lugar de un punto de unión (más sobre ellos a continuación). Posibles tipos de consejos :-
Antes : este tipo de aviso se inicia antes de que se ejecuten los métodos de destino, es decir, los puntos de unión. Cuando usamos aspectos como clases, usamos la anotación @Before para marcar el consejo como anterior. Al usar aspectos como archivos .aj , este será el método before() .
- Después : consejo que se ejecuta después de que se completa la ejecución de los métodos (puntos de unión), tanto en la ejecución normal como cuando se lanza una excepción.
Cuando usamos aspectos como clases, podemos usar la anotación @After para indicar que este es un consejo que viene después.
Cuando se usan aspectos como archivos .aj , este es el método after() .
-
Después de regresar : este consejo se realiza solo cuando el método de destino finaliza normalmente, sin errores.
Cuando los aspectos se representan como clases, podemos usar la anotación @AfterReturning para marcar el consejo como ejecutándose después de completarse con éxito.
Al usar aspectos como archivos .aj , este será el método de retorno after() (Object obj) .
-
Después de lanzar : este consejo está destinado a instancias en las que un método, es decir, un punto de unión, lanza una excepción. Podemos usar este consejo para manejar ciertos tipos de ejecución fallida (por ejemplo, para revertir una transacción completa o iniciar sesión con el nivel de rastreo requerido).
Para aspectos de clase, la anotación @AfterThrowing se usa para indicar que este consejo se usa después de lanzar una excepción.
Al usar aspectos como archivos .aj , este será el método de lanzamiento after() (Excepción e) .
-
Alrededor : quizás uno de los tipos de consejos más importantes. Rodea un método, es decir, un punto de unión que podemos utilizar para, por ejemplo, elegir si realizar o no un método de punto de unión dado.
Puede escribir código de aviso que se ejecute antes y después de que se ejecute el método de punto de unión.
El consejo around es responsable de llamar al método de punto de unión y los valores devueltos si el método devuelve algo. En otras palabras, en este consejo, simplemente puede simular la operación de un método de destino sin llamarlo y devolver lo que quiera como resultado de devolución.
Dados los aspectos como clases, usamos la anotación @Around para crear consejos que envuelven un punto de unión. Al usar aspectos en forma de archivos .aj , este método será el método around() .
-
Tejido en tiempo de compilación : si tiene el código fuente del aspecto y el código donde usa el aspecto, entonces puede compilar el código fuente y el aspecto directamente usando el compilador AspectJ;
-
Tejido posterior a la compilación (tejido binario) : si no puede o no quiere usar transformaciones de código fuente para tejer aspectos en el código, puede tomar clases compiladas previamente o archivos jar e inyectar aspectos en ellos;
-
Tejido en tiempo de carga : este es solo tejido binario que se retrasa hasta que el cargador de clases carga el archivo de clase y define la clase para la JVM.
Se requieren uno o más cargadores de clase de tejido para admitir esto. Son proporcionados explícitamente por el tiempo de ejecución o activados por un "agente de tejido".
Ejemplos en Java
A continuación, para una mejor comprensión de AOP , veremos pequeños ejemplos de estilo "Hello World". De inmediato, notaré que nuestros ejemplos usarán tejido en tiempo de compilación . Primero, debemos agregar la siguiente dependencia en nuestro archivo pom.xml :
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
Como regla general, el compilador ajc especial es cómo usamos aspectos. IntelliJ IDEA no lo incluye de forma predeterminada, por lo que al elegirlo como compilador de la aplicación, debe especificar la ruta a la distribución 5168 75 AspectJ . Esta fue la primera forma. La segunda, que es la que yo utilicé, es registrar el siguiente complemento en el archivo 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>
Después de esto, es una buena idea volver a importar desde Maven y ejecutar mvn clean compile . Ahora pasemos directamente a los ejemplos.
Ejemplo No. 1
Vamos a crear una clase principal . En él, tendremos un punto de entrada y un método que imprime un nombre pasado en la consola:
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);
}
}
No hay nada complicado aquí. Le pasamos un nombre y lo mostramos en la consola. Si ejecutamos el programa ahora, veremos lo siguiente en la consola:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
Este archivo es como una clase. Veamos qué está pasando aquí: pointcut es un conjunto de puntos de unión; saludo() es el nombre de este punto de corte; : ejecución indica aplicarlo durante la ejecución de todas las ( * ) llamadas del método Main.printName(...) . Luego viene un consejo específico, before() , que se ejecuta antes de llamar al método de destino. : saludo() es el punto de corte al que responde este consejo. Bueno, y debajo vemos el cuerpo del método en sí, que está escrito en el lenguaje Java, que entendemos. Cuando ejecutamos main con este aspecto presente, obtendremos esta salida de consola:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
Después del archivo de aspecto .aj , todo se vuelve más obvio aquí:
- @Aspect indica que esta clase es un aspecto;
- @Pointcut("execution(* Main.printName(String))") es el punto de corte que se activa para todas las llamadas a Main.printName con un argumento de entrada cuyo tipo es String ;
- @Before("saludo()") es un consejo que se aplica antes de llamar al código especificado en el punto de corte de saludo() .
Ejemplo No. 2
Supongamos que tenemos algún método que realiza algunas operaciones para los clientes y llamamos a este método desde 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);
}
}
Usemos la anotación @Around para crear una "pseudotransacción":
@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 el método de proceder del objeto ProceedingJoinPoint , llamamos al método de ajuste para determinar su ubicación en el consejo. Por lo tanto, el código del método anterior joinPoint.proceed(); es Before , mientras que el código debajo es After . Si ejecutamos main , obtenemos esto en la consola:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
Entonces obtenemos esta salida de la consola:
Ejemplo No. 3
En nuestro siguiente ejemplo, hagamos algo como iniciar sesión en la consola. Primero, eche un vistazo a Main , donde hemos agregado algo de pseudológica empresarial:
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();
}
}
}
En main , usamos setValue para asignar un valor a la variable de instancia de valor . Luego usamos getValue para obtener el valor y luego llamamos a checkValue para ver si tiene más de 10 caracteres. Si es así, se lanzará una excepción. Ahora veamos el aspecto que usaremos para registrar el trabajo de los métodos:
@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);
}
}
¿Que está pasando aqui? @Pointcut("execution(* *(..))") se unirá a todas las llamadas de todos los métodos. @AfterReturning(value = "methodExecuting()", return = "returningValue") es un consejo que se ejecutará después de la ejecución exitosa del método de destino. Aquí tenemos dos casos:
- Cuando el método tiene un valor devuelto: if (returningValue! = Null) {
- Cuando no hay valor de retorno — else {
GO TO FULL VERSION