1

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?

user1723105
  • 121
  • 9
  • I am not sure I understand the syntax but according to the answer [here](http://stackoverflow.com/questions/4615333/how-to-write-an-aspectj-itd-to-add-an-annotation-to-a-method) it can be done in AspectJ using [Inter-Type Declarations](http://eclipse.org/aspectj/doc/next/progguide/language-interType.html) – user1723105 Jul 18 '16 at 05:06
  • Did you consider writing a custom adapter for controlling the JSON Binding, rather than trying to inject annotations? – Andreas Jul 18 '16 at 05:09
  • I have not. Do you have some reference I could look at? Also, this sort of thing seems to be just the type of thing AOP is meant for. It does seem there is a bit of complexity and learning curve to it. Other than that is there a reason NOT to use it? – user1723105 Jul 18 '16 at 13:45
  • The reason there is support for adapters, is that not everything can be done using annotations. – Andreas Jul 18 '16 at 14:12
  • Do you have examples of using an adapter? Are you referring to something like [XmlAdapter](https://docs.oracle.com/javase/7/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html) ? – user1723105 Jul 19 '16 at 13:27
  • No, I'm not talking about an *XML* adapter when I talk about *JSON* binding. What JSON annotations did you intend to inject? What JSON binding implementation did you intend to use? Look at what adapter support it has. – Andreas Jul 19 '16 at 17:28

1 Answers1

4

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.

Community
  • 1
  • 1
kriegaex
  • 63,017
  • 15
  • 111
  • 202