Emular herencia de anotación para interfaces y métodos con AspectJ

Con frecuencia, las personas hacen preguntas de AspectJ como esta, por lo que quiero responderlas en un lugar al que pueda acceder fácilmente más adelante.

Tengo esta anotación de marcador:

package de.scrum_master.app; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Marker {} 

Ahora anoto una interfaz y / o métodos como este:

 package de.scrum_master.app; @Marker public interface MyInterface { void one(); @Marker void two(); } 

Aquí hay una pequeña aplicación de controlador que también implementa la interfaz:

 package de.scrum_master.app; public class Application implements MyInterface { @Override public void one() {} @Override public void two() {} public static void main(String[] args) { Application application = new Application(); application.one(); application.two(); } } 

Ahora cuando defino este aspecto, espero que se active

  • para cada ejecución del constructor de una clase anotada y
  • para cada ejecución de un método anotado.
 package de.scrum_master.aspect; import de.scrum_master.app.Marker; public aspect MarkerAnnotationInterceptor { after() : execution((@Marker *).new(..)) && !within(MarkerAnnotationInterceptor) { System.out.println(thisJoinPoint); } after() : execution(@Marker * *(..)) && !within(MarkerAnnotationInterceptor) { System.out.println(thisJoinPoint); } } 

Lamentablemente, el aspecto no imprime nada, al igual que si la Application clase y el método two() no tuvieran ninguna anotación @Marker . ¿Por qué AspectJ no los intercepta?

El problema aquí no es AspectJ sino la JVM. En Java, las anotaciones en

  • interfaces,
  • métodos o
  • otras anotaciones

nunca son heredados por

  • implementando clases,
  • métodos primordiales o
  • clases utilizando anotaciones anotadas.

La herencia de anotaciones solo funciona de clases a subclases, pero solo si el tipo de anotación utilizado en la superclase @Inherited , consulte JDK JavaDoc .

AspectJ es un lenguaje JVM y, por lo tanto, funciona dentro de las limitaciones de la JVM. No existe una solución general para este problema, pero para las interfaces o métodos específicos para los que desea emular la herencia de anotaciones, puede usar una solución alternativa como esta:

 package de.scrum_master.aspect; import de.scrum_master.app.Marker; import de.scrum_master.app.MyInterface; /** * It is a known JVM limitation that annotations are never inherited from interface * to implementing class or from method to overriding method, see explanation in * JDK API. * 

* Here is a little AspectJ trick which does it manually. * */ public aspect MarkerAnnotationInheritor { // Implementing classes should inherit marker annotation declare @type: MyInterface+ : @Marker; // Overriding methods 'two' should inherit marker annotation declare @method : void MyInterface+.two() : @Marker; }

Tenga en cuenta: con este aspecto, puede eliminar las anotaciones (literales) de la interfaz y del método anotado porque la mecánica ITD (definición entre tipos) de AspectJ las vuelve a agregar a la interfaz y a todas las clases / métodos de implementación / anulación .

Ahora el registro de la consola cuando se ejecuta la Application dice:

 execution(de.scrum_master.app.Application()) execution(void de.scrum_master.app.Application.two()) 

Por cierto, también puede incrustar el aspecto directamente en la interfaz para tener todo en un solo lugar. Solo tenga cuidado de cambiar el nombre de MyInterface.java a MyInterface.aj para ayudar al comstackdor de AspectJ a reconocer que tiene que hacer algo al respecto aquí.

 package de.scrum_master.app; public interface MyInterface { void one(); void two(); public static aspect MarkerAnnotationInheritor { // Implementing classes should inherit marker annotation declare @type: MyInterface+ : @Marker; // Overriding methods 'two' should inherit marker annotation declare @method : void MyInterface+.two() : @Marker; } }