Search code examples
javaspringgenericsaop

Generic types in @DeclareParents annotation in Spring AOP


I'm trying to use a generic interface and generic class in an aspect for Spring framework using spring aop, i want to set the parent interface for any class annotated with @EntityController:

@Component
@MongoProfile
@Aspect
public class EntityMongoControllerAspect<T> {

    @DeclareParents(value="@EntityController *",defaultImpl=EntityMongoController.class)
    private IEntityController<T> iEntityController;
}

but eclipse always throws me the exception:

java.lang.IllegalStateException
at org.aspectj.weaver.ResolvedTypeMunger.<init>(ResolvedTypeMunger.java:69)
at org.aspectj.weaver.MethodDelegateTypeMunger.<init>(MethodDelegateTypeMunger.java:61)
at org.aspectj.weaver.bcel.AtAjAttributes.handleDeclareParentsAnnotation(AtAjAttributes.java:852)
at org.aspectj.weaver.bcel.AtAjAttributes.readAj5ClassAttributes(AtAjAttributes.java:384)
at org.aspectj.weaver.bcel.BcelObjectType.ensureAspectJAttrib ... ob.java:241)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:53)

Compile error: IllegalStateException thrown: Use generic type, not raw type

my entityMongoController code is:

public class EntityMongoController<T1> {
 ...
}

so how i can achieve this? or there is another alternative? you must take into mind that i'm using spring profiles, so using native aspectj is not an alternative.


Solution

  • Let us for a moment assume that native AspectJ is not a show-stopper for you if you follow Spring's documentation on how to integrate it into your Spring project. In this case you could do something like this (I am using native AspectJ syntax because the annotation-style @AspectJ syntax has many disadvantages from a readability/expressivity point of view):

    Interface with embedded aspect providing default implementation:

    package de.scrum_master.app;
    
    public interface IEntityController<T> {
        public void setEntity(T entity);
        public T getEntity();
    
        static aspect EntityControllerAspect {
            private T IEntityController.entity;
            public void IEntityController.setEntity(T entity) { this.entity = entity; }
            public T IEntityController.getEntity() { return entity; }
    
            declare parents : @EntityController * implements IEntityController;
        }
    }
    

    Annotated class with sample main method showing the effect:

    package de.scrum_master.app;
    
    import java.lang.reflect.Method;
    
    @EntityController
    public class MyAnnotatedController<T> {
        public void doSomething() {
            System.out.println("Doing something");
        }
    
        public static void main(String[] args) {
            // Use class type directly so as to call its method
            MyAnnotatedController<String> annotatedTextController = new MyAnnotatedController<>();
            annotatedTextController.doSomething();
    
            // Print all declared methods (should also show interface methods introduced via ITD)
            for (Method method : annotatedTextController.getClass().getDeclaredMethods()) {
                if (!method.getName().startsWith("ajc$"))
                    System.out.println(method);
            }
    
            // Prove that class type is compatible with interface type
            IEntityController<String> entityTextController = annotatedTextController;
            entityTextController.setEntity("foo");
            // Would not work here because generic interface type is type-safe:
            // entityTextController.setEntity(123);
            System.out.println("Entity value = " + entityTextController.getEntity());
    
            // Create another object and directly assign it to interface type
            IEntityController<Integer> entityNumberController = new MyAnnotatedController<>();
            entityNumberController.setEntity(123);
            // Would not work here because generic interface type is type-safe:
            // entityNumberController.setEntity("foo");
            System.out.println("Entity value = " + entityNumberController.getEntity());
        }
    }
    

    Console output:

    Doing something
    public static void de.scrum_master.app.MyAnnotatedController.main(java.lang.String[])
    public java.lang.Object de.scrum_master.app.MyAnnotatedController.getEntity()
    public void de.scrum_master.app.MyAnnotatedController.setEntity(java.lang.Object)
    public void de.scrum_master.app.MyAnnotatedController.doSomething()
    Entity value = foo
    Entity value = 123
    

    Feel free to ask further questions if you do not understand my code snippets.