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:
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: