Skip to content
Advertisement

Create custom method level annotation only available to specific return types [AOP]

I want to create an annotation which is only available to a specific type of return values.

For example this is my annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnnotation {

}

I also have an interface:

public interface MyInterface {
    String generateKey();
}

An example class that implements my interface:

public class ExampleClass implements MyInterface {

@Override
public String generateKey() {
   return "Whatever";
    }
}

So after these, I want to configure my annotation in a way that it won’t even compile if the return type is not implementing MyInterface.

In this case, I expect this to compile fine:

@MyAnnotation
public ExampleClass anExampleMethod() {
    return new ExampleClass();
}

And this to not compile:

@MyAnnotation
public String anotherMethod() {
    return "Whatever";
}

I wonder if this is possible in any way. Sure I can check if the parameters implements this interface in my Aspect class but it would be better to have this kind of protection in my library in order to prevent misuse of any annotation.

Advertisement

Answer

Helper classer:

These are directly from your example, just with package names and imports.

package de.scrum_master.app;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface MyAnnotation {}
package de.scrum_master.app;

public interface MyInterface {
  String generateKey();
}
package de.scrum_master.app;

public class ExampleClass implements MyInterface {
  @Override
  public String generateKey() {
    return "Whatever";
  }
}

Class which should not compile:

This class has some annotated and some non-annotated methods. One annotated method does not return MyInterface or any of its implementing classes. The goal is to fail compilation.

package de.scrum_master.app;

public class Application {
  @MyAnnotation
  public MyInterface annotatedMethodReturningInterface(int number) {
    return new ExampleClass();
  }

  @MyAnnotation
  public ExampleClass annotatedMethodReturningImplementingClass() {
    return new ExampleClass();
  }

  @MyAnnotation
  public String annotatedMethodReturningSomethingElse() {
    // This one should not compile!
    return "Whatever";
  }

  public MyInterface nonAnnotatedMethodReturningInterface(int number) {
    return new ExampleClass();
  }

  public ExampleClass nonAnnotatedMethodReturningImplementingClass() {
    return new ExampleClass();
  }

  public String nonAnnotatedMethodReturningSomethingElse() {
    return "Whatever";
  }
}

Convention-checking aspect (native AspectJ syntax):

package de.scrum_master.aspect;

import de.scrum_master.app.MyAnnotation;
import de.scrum_master.app.MyInterface;

public aspect AnnotationCheckerAspect {
  declare error :
    @annotation(MyAnnotation) && execution(* *(..)) && !execution(MyInterface+ *(..)) :
    "Method annotated with @MyAnnotation must return MyInterface type";
}

This aspect checks for

  • all method executions
  • where the method has @MyAnnotation
  • but where the return type is different from MyInterface or any subtype or implementing class.

This is what the result looks like in Eclipse:

Eclipse: compilation error

Of course the compilation error is just the same if you compile from command line or via AspectJ Maven plugin or similar.

If you do not like native syntax (I prefer it but for some incomprehensible reason other people seem to prefer @AspectJ style):

Convention-checking aspect (annotation-based @AspectJ syntax):

package de.scrum_master.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareError;

@Aspect
public class AnnotationCheckerAspect {
  @DeclareError(
    "@annotation(de.scrum_master.app.MyAnnotation) && " +
    "execution(* *(..)) && " +
    "!execution(de.scrum_master.app.MyInterface+ *(..))"
  )
  static final String wrongSignatureError =
  "Method annotated with @MyAnnotation must return MyInterface type";
}

See also my related answers here:

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement