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?
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;
@SomeAnnotation
on the type C
.declare @method: * C.foo*(..) : @SomeAnnotation;
@SomeAnnotation
on all methods declared in C
starting with foo
.declare @constructor: C.new(..) : @SomeAnnotation;
@SomeAnnotation
on all constructors declared in C
.declare @field: * C.* : @SomeAnnotation;
@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
@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),+
subclass specifier, e.g. in MyInterface+
,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.