Search code examples
javaaop

Can annotations be added to methods or members using AOP frameworks?


Can I use AOP (AspectJ, Guice or other) to add annotations to classes that I cannot modify? My first use case would be adding persistence and binding annotations. I know I can write XML configuration files for JPA and JAXB but I am not aware of any way to do this for JSON binding.

I have looked for some examples/tutorials but have not found anything demonstrating this. IS it possible and if so, can someone provide a good example or better yet point me to some resources?


Solution

  • Yes, you can use AspectJ for that purpose (quoting the AspectJ cheat sheet, see also AspectJ Development Kit Developer's Notebook):

    • declare @type: C : @SomeAnnotation;
      declares the annotation @SomeAnnotation on the type C.
    • declare @method: * C.foo*(..) : @SomeAnnotation;
      declares the annotation @SomeAnnotation on all methods declared in C starting with foo.
    • declare @constructor: C.new(..) : @SomeAnnotation;
      declares the annotation @SomeAnnotation on all constructors declared in C.
    • declare @field: * C.* : @SomeAnnotation;
      declares the annotation @SomeAnnotation on all fields declared in C.

    Just in case you wanted to ask: This feature is only supported in native AspectJ syntax, not in annotation-style @AspectJ syntax.


    Update: Here is some sample code showing

    • how to add annotations to classes, interfaces and methods,
    • that even annotations marked as @Inherited are only inherited from class to subclass, never from interface to class and never to subclass methods (a typical Java caveat many developers are unaware of, see Emulate annotation inheritance for interfaces and methods with AspectJ for an explanation and a possible workaround),
    • how via AspectJ you can still apply an annotation to classes implementing an interface via + subclass specifier, e.g. in MyInterface+,
    • how another aspect can immediately see and utilise annotations added via declare @type, declare @method etc.

    Class hierarchy including abstract base classes and an interface:

    package de.scrum_master.app;
    
    public interface MyInterface {
        void doSomething();
        int doSomethingElse(int a, int b);
        String sayHelloTo(String name);
    }
    
    package de.scrum_master.app;
    
    public abstract class NormalBase implements MyInterface {
        @Override
        public abstract void doSomething();
    
        @Override
        public int doSomethingElse(int a, int b) {
            return a + b;
        }
    
        @Override
        public abstract String sayHelloTo(String name);
    }
    
    package de.scrum_master.app;
    
    public class Normal extends NormalBase {
        @Override
        public void doSomething() {
            //System.out.println("Doing something normal");
        }
    
        @Override
        public String sayHelloTo(String name) {
            return "A normal hello to " + name;
        }
    
        public void doNothing() {
            //System.out.println("Being lazy in a normal way");
        }
    }
    
    package de.scrum_master.app;
    
    public abstract class SpecialBase {
        public abstract void doFoo();
        public abstract void makeBar();
    }
    
    package de.scrum_master.app;
    
    public class Special extends SpecialBase implements MyInterface {
        @Override
        public void doSomething() {
            //System.out.println("Doing something special");
        }
    
        @Override
        public int doSomethingElse(int a, int b) {
            return a * b;
        }
    
        @Override
        public String sayHelloTo(String name) {
            return "A special hello to " + name;
        }
    
        @Override
        public void doFoo() {
            //System.out.println("Doing foo");
        }
    
        @Override
        public void makeBar() {
            //System.out.println("Making bar");
        }
    
        public int doZot() {
            return 11;
        }
    
        public String makeBlah() {
            return "Blah";
        }
    }
    
    package de.scrum_master.app;
    
    public class SpecialTwo extends SpecialBase {
        @Override
        public void doFoo() {
            //System.out.println("Doing foo");
        }
    
        @Override
        public void makeBar() {
            //System.out.println("Making bar");
        }
    
        public String doXxx() {
            return "Xxx";
        }
    
        public int makeBlah() {
            return 22;
        }
    }
    

    Driver application creating all kinds of object, calling all kinds of methods:

    package de.scrum_master.app;
    
    public class Application {
        public static void main(String[] args) {
            System.out.println("Normal instance");
            Normal normal = new Normal();
            normal.doSomething();
            normal.doSomethingElse(3, 5);
            normal.sayHelloTo("John");
            normal.doNothing();
    
            System.out.println("\nNormal instance as NormalBase");
            NormalBase normalBase = normal;
            normalBase.doSomething();
            normalBase.doSomethingElse(3, 5);
            normalBase.sayHelloTo("John");
    
            System.out.println("\nNormal instance as MyInterface");
            MyInterface myInterface = normal;
            myInterface.doSomething();
            myInterface.doSomethingElse(3, 5);
            myInterface.sayHelloTo("John");
    
            System.out.println("\nSpecial instance");
            Special special = new Special();
            special.doSomething();
            special.doSomethingElse(7, 8);
            special.doFoo();
            special.doZot();
            special.makeBar();
            special.makeBlah();
            special.sayHelloTo("Jane");
    
            System.out.println("\nSpecial instance as SpecialBase");
            SpecialBase specialBase = special;
            specialBase.doFoo();
            specialBase.makeBar();
    
            System.out.println("\nSpecial instance as MyInterface");
            myInterface = special;
            myInterface.doSomething();
            myInterface.doSomethingElse(7, 8);
            myInterface.sayHelloTo("Jane");
    
            System.out.println("\nSpecialTwo instance");
            SpecialTwo specialTwo = new SpecialTwo();
            specialTwo.doFoo();
            specialTwo.makeBar();
            specialTwo.makeBlah();
            specialTwo.doXxx();
    
            System.out.println("\nSpecialTwo instance as SpecialBase");
            specialBase = specialTwo;
            specialBase.doFoo();
            specialBase.makeBar();
        }
    }
    

    Some marker annotations later to be added to interfaces, classes, methods by an aspect:

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

    Aspect adding annotations to interfaces, classes, methods:

    package de.scrum_master.aspect;
    
    import de.scrum_master.app.ClassMarker;
    import de.scrum_master.app.InterfaceMarker;
    import de.scrum_master.app.MethodMarker;
    import de.scrum_master.app.MyInterface;
    import de.scrum_master.app.SpecialBase;
    
    public aspect AnnotationGenerator {
        declare @type : MyInterface+ : @InterfaceMarker;
        declare @type : SpecialBase : @ClassMarker;
        declare @method : * say*(..) : @MethodMarker;
    }
    

    Aspect logging initialisation of annotated classes and execution of annotated methods:

    package de.scrum_master.aspect;
    
    import de.scrum_master.app.ClassMarker;
    import de.scrum_master.app.InterfaceMarker;
    import de.scrum_master.app.MethodMarker;
    
    public aspect MarkedObjectLogger {
        before() : @annotation(InterfaceMarker) {
            System.out.println(thisJoinPoint + " -> @InterfaceMarker");
        }
    
        before() : @annotation(ClassMarker) {
            System.out.println(thisJoinPoint + " -> @ClassMarker");
        }
    
        before() : @annotation(MethodMarker) && execution(* *(..)) {
            System.out.println(thisJoinPoint + " -> @MethodMarker");
        }
    }
    

    Console log:

    Normal instance
    staticinitialization(de.scrum_master.app.NormalBase.<clinit>) -> @InterfaceMarker
    staticinitialization(de.scrum_master.app.Normal.<clinit>) -> @InterfaceMarker
    execution(String de.scrum_master.app.Normal.sayHelloTo(String)) -> @MethodMarker
    
    Normal instance as NormalBase
    execution(String de.scrum_master.app.Normal.sayHelloTo(String)) -> @MethodMarker
    
    Normal instance as MyInterface
    execution(String de.scrum_master.app.Normal.sayHelloTo(String)) -> @MethodMarker
    
    Special instance
    staticinitialization(de.scrum_master.app.SpecialBase.<clinit>) -> @ClassMarker
    staticinitialization(de.scrum_master.app.Special.<clinit>) -> @InterfaceMarker
    staticinitialization(de.scrum_master.app.Special.<clinit>) -> @ClassMarker
    execution(String de.scrum_master.app.Special.sayHelloTo(String)) -> @MethodMarker
    
    Special instance as SpecialBase
    
    Special instance as MyInterface
    execution(String de.scrum_master.app.Special.sayHelloTo(String)) -> @MethodMarker
    
    SpecialTwo instance
    staticinitialization(de.scrum_master.app.SpecialTwo.<clinit>) -> @ClassMarker
    
    SpecialTwo instance as SpecialBase
    

    Try removing the + from declare @type : MyInterface+ and see how all log lines mentioning @InterfaceMarker vanish because interface annotations really are not inherited by implementing classes.