Progtwigción orientada a aspectos de Java con anotaciones

En una publicación titulada “Fundamentos de AOP” , solicité una explicación de King’s English sobre qué es AOP y qué hace. Recibí algunas respuestas muy útiles y enlaces a artículos que me ayudaron a completar toda la teoría.

Pero ahora los AOP captaron toda mi atención, y todos estos artículos y extractos de capítulos son fantásticos, pero en todos los casos consisten en una teoría elevada, modelos vagos de UML y un orden de abstracción demasiado alto para mi gusto.

Aquí está mi comprensión de la teoría de AOP, solo para aclarar, así que si ves algo que parece estar mal, ¡házmelo saber !:

  1. Las preocupaciones transversales tales como Registro, Autenticación, Sincronización, Validación, Manejo de excepciones, etc., se combinan altamente en sistemas que no son AOP, ya que son utilizados universalmente por casi cada componente / módulo en la base de código.

  2. AOP define aspectos (clases / métodos) que resumen estas preocupaciones transversales con el uso de puntos de unión , consejos y puntos de corte .

    a. Consejo : ¿el código real (método de un aspecto, quizás?) Que implementa la preocupación transversal (es decir, hacer el registro real, validar, autenticar, etc.)

    segundo. Punto de unión : un evento que se desencadena en un código que no es AOP y que hace que se ejecute el consejo de un aspecto en particular (“entrelazado” con el código que no es AOP)

    do. Pointcut – En esencia, un mapeo de puntos de unión (eventos desencadenantes) para la ejecución de consejos

  3. Todos los aspectos están modulados (LoggingAspect, AuthenticationAspect, ValidationAspect, etc.) en componentes y registrados con AspectWeaver . Cuando un código que no es AOP / POJO encuentra un punto de unión, AspectWeaver “teje” (integra) el consejo mapeado alrededor del código que no es AOP:

 clase pública LoggingAspect
 {
     // ...

     registro de vacíos públicos (String msg) {...}
 }

 clase pública ExceptionHandlingAspect
 {
     // ..

     identificador de vacío público (Exception exc) {...}
 }

 clase pública NonAOPCode
 {
     // ...

     @LoggingAspect @ExceptionHandlingAspect
     public void foo ()
     {
         // hacer algunas cosas ...
     }
 }

 // Ahora en el driver
 public static static main void (String [] args)
 {
     NonAOPCode nonAOP = new NonAOPCode ();
     nonAOP.foo ();
 }

 // La AspectWeaver * mágicamente * podría tejer en llamadas a métodos, por lo que main ahora se convierte en:
 {
     NonAOPCode nonAOP = new NonAOPCode ();

     log (algunosMsg);
     nonAOP.foo ();
     manejar (algunosExc);
 }

La pregunta de $ 64,000: ¿Está mi comprensión del AOP basado en Java en el objective, o en el camino, y por qué? ¿Cómo podría uno usar correctamente las anotaciones para implementar aspectos, consejos, puntos de unión, puntos de corte y este llamado tejedor de aspectos?

Imaginemos que desea registrar el tiempo empleado por algunos métodos @LogExecTime utilizando una anotación @LogExecTime .

Primero creo una anotación LogExecTime :

 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LogExecTime { } 

Luego defino un aspecto:

 @Component // For Spring AOP @Aspect public class LogTimeAspect { @Around(value = "@annotation(annotation)") public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable { final long startMillis = System.currentTimeMillis(); try { System.out.println("Starting timed operation"); final Object retVal = joinPoint.proceed(); return retVal; } finally { final long duration = System.currentTimeMillis() - startMillis; System.out.println("Call to " + joinPoint.getSignature() + " took " + duration + " ms"); } } } 

Creo una clase LogExecTime con LogExecTime :

 @Component public class Operator { @LogExecTime public void operate() throws InterruptedException { System.out.println("Performing operation"); Thread.sleep(1000); } } 

Y una principal usando Spring AOP:

 public class SpringMain { public static void main(String[] args) throws InterruptedException { ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml"); final Operator bean = context.getBean(Operator.class); bean.operate(); } } 

Si ejecuto esta clase obtengo la siguiente salida en stdout:

 Starting timed operation Performing operation Call to void testaop.Operator.Operate() took 1044 ms 

Ahora con la magia . Como utilicé Spring AOP en lugar de AspectJ Weaver, la magia se produce en tiempo de ejecución utilizando mecanismos proxy-ish. Entonces los archivos .class quedan intactos. Por ejemplo, si depuro este progtwig y pongo un punto de interrupción en operate , verás cómo Spring ha realizado la magia:

Captura de pantalla de depuración

Como la implementación de Spring AOP no es intrusiva y utiliza los mecanismos de Spring, debe agregar la anotación @Component y crear el objeto utilizando el contexto de Spring en lugar de simplemente new .

AspectJ en el otro lado cambiará los archivos .class . Intenté este proyecto con AspectJ y descompilé la clase Operator con jad. Que conducen a:

 public void operate() throws InterruptedException { JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this); operate_aroundBody1$advice(this, joinpoint, LogTimeAspect.aspectOf(), (ProceedingJoinPoint)joinpoint, (LogExecTime)(ajc$anno$0 == null && (ajc$anno$0 = testaop/Operator.getDeclaredMethod("operate", new Class[0]).getAnnotation(testaop/LogExecTime)) == null ? ajc$anno$0 : ajc$anno$0)); } private static final void operate_aroundBody0(Operator ajc$this, JoinPoint joinpoint) { System.out.println("Performing operation"); Thread.sleep(1000L); } private static final Object operate_aroundBody1$advice(Operator ajc$this, JoinPoint thisJoinPoint, LogTimeAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint, LogExecTime annotation) { long startMillis = System.currentTimeMillis(); Object obj; System.out.println("Starting timed operation"); ProceedingJoinPoint proceedingjoinpoint = joinPoint; operate_aroundBody0(ajc$this, proceedingjoinpoint); Object retVal = null; obj = retVal; long duration = System.currentTimeMillis() - startMillis; System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString()); return obj; Exception exception; exception; long duration = System.currentTimeMillis() - startMillis; System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString()); throw exception; } private static void ajc$preClinit() { Factory factory = new Factory("Operator.java", testaop/Operator); ajc$tjp_0 = factory.makeSJP("method-execution", factory.makeMethodSig("1", "operate", "testaop.Operator", "", "", "java.lang.InterruptedException", "void"), 5); } private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0; /* synthetic field */ private static Annotation ajc$anno$0; /* synthetic field */ static { ajc$preClinit(); } 

Hace algunos meses escribí un artículo con un ejemplo sobre cómo implementé un caso práctico de combinación de aspectos Aspect / J con anotaciones de Java, que puede ser útil:

http://technomilk.wordpress.com/2010/11/06/combination-annotations-and-aspects-part-1/

Creo que los aspectos aplicados a las anotaciones son una buena combinación porque hacen que el aspecto sea más explícito en su código, pero de una manera limpia, y puede usar parámetros en sus anotaciones para mayor flexibilidad.

Por cierto, la forma en que Aspect / J funciona es modificando sus clases en tiempo de comstackción, no en tiempo de ejecución. Ejecuta sus fonts y aspectos a través del comstackdor Aspect / J y crea los archivos de clase modificados.

Spring AOP, por lo que yo entiendo, hace el entrelazado (manipulando los archivos de clase para incluir el procesamiento de aspecto) de una manera diferente, al crear objetos proxy, creo que en el momento de la instanciación (pero no tomo mi palabra) .

Encontré la respuesta yo mismo después de mucho excavar y grasa en el codo …

Sí, AOP debe estar basado en anotaciones en el mundo de Java, sin embargo, no puede procesar anotaciones relacionadas con aspectos como las anotaciones regulares (metadatos). Para interceptar una llamada de método etiquetada y “tejer” los métodos de asesoramiento antes / después de ella, necesita la ayuda de algunos muy ingeniosos motores centrados en AOP como AspectJ. Kristopher McCann ofreció una solución realmente agradable en otro hilo relacionado con las anotaciones, donde sugirió el uso de AOP Alliance junto con Google Guice. Después de leer la documentación de Guice sobre el soporte de AOP, esto es exactamente lo que estoy buscando: un marco de trabajo fácil de entender para entretejer en el “consejo” (llamadas a métodos) de preocupaciones transversales, como el registro, la validación, el almacenamiento en caché, etc.

Este era un douzy.

Cambiar el comentario

 // The AspectWeaver *magically* might weave in method calls so main now becomes 

a

 // The AspectWeaver *magically* might weave in method calls so main now // becomes effectively (the .class file is not changed) 

Me gusta la redacción de spring de AOP. Mira el Capítulo 7

Aquí está mi contribución a esta publicación tan útil.

Tomaremos un ejemplo muy simple: tenemos que tomar medidas sobre el procesamiento de algunos métodos. Se anotan con anotaciones personalizadas, que contienen datos para manejar. Teniendo en cuenta estos datos, queremos plantear una excepción o permitir que el proceso continúe como si el método no estuviera anotado.

El código de Java para definir nuestro aspecto:

 package com.example; public class AccessDeniedForCustomAnnotatedMethodsAspect { public Object checkAuthorizedAccess(ProceedingJoinPoint proceedingJointPoint) throws Throwable { final MethodSignature methodSignature = (MethodSignature) proceedingJointPoint .getSignature(); // how to get the method name final String methodName = methodSignature .getMethod() .getName(); // how to get the parameter types final Class[] parameterTypes = methodSignature .getMethod() .getParameterTypes(); // how to get the annotations setted on the method Annotation[] declaredAnnotations = proceedingJointPoint .getTarget() .getClass() .getMethod(methodName, parameterTypes) .getDeclaredAnnotations(); if (declaredAnnotations.length > 0) { for (Annotation declaredAnnotation : Arrays.asList(declaredAnnotations)) { // I just want to deal with the one that interests me if(declaredAnnotation instanceof CustomAnnotation) { // how to get the value contained in this annotation (CustomAnnotation) declaredAnnotation).value() if(test not OK) { throw new YourException("your exception message"); } // triggers the rest of the method process return proceedingJointPoint.proceed(); } } } } 

La configuración xml:

       

Espero eso ayude !