Java 6 annotation processing -- getting a class from an annotation
I have an custom annotation called @Pojo which I use for automatic wiki documentation generation:
package com.example.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementT开发者_StackOverflow社区ype.METHOD)
public @interface Pojo {
Class<?> value();
}
I use it like this:
@Pojo(com.example.restserver.model.appointment.Appointment.class)
to annotation a resource method so that the annotation processor can automatically generate a wiki page describing the resource and type that it expects.
I need to read the value of the value
field in an annotation processor, but I am getting a runtime error.
In the source code for my processor I have the following lines:
final Pojo pojo = element.getAnnotation(Pojo.class);
// ...
final Class<?> pojoJavaClass = pojo.value();
but the actual class in not available to the processor. I think I need a javax.lang.model.type.TypeMirror
instead as a surrogate for the real class. I'm not sure how to get one.
The error I am getting is:
javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror com.example.restserver.model.appointment.Appointment
The Appointment
is a class mentioned in one of my @Pojo
annotation.
Unfortunately, document and/or tutorials on Java annotation processing seems scarce. Tried googling.
I came here to ask the EXACT same question. ... and found the same blog link posted by Ralph.
It's a long article, but very rich. Summary of the story -- there's two ways to do it, the easy way, and the "more right" way.
This is the easy way:
private static TypeMirror getMyValue1(MyAnnotation annotation) {
try
{
annotation.myValue(); // this should throw
}
catch( MirroredTypeException mte )
{
return mte.getTypeMirror();
}
return null; // can this ever happen ??
}
The other more tedious way (without exceptions):
private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
String clazzName = clazz.getName();
for(AnnotationMirror m : typeElement.getAnnotationMirrors()) {
if(m.getAnnotationType().toString().equals(clazzName)) {
return m;
}
}
return null;
}
private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
for(Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet() ) {
if(entry.getKey().getSimpleName().toString().equals(key)) {
return entry.getValue();
}
}
return null;
}
public TypeMirror getMyValue2(TypeElement foo) {
AnnotationMirror am = getAnnotationMirror(foo, MyAnnotation.class);
if(am == null) {
return null;
}
AnnotationValue av = getAnnotationValue(am, "myValue");
if(av == null) {
return null;
} else {
return (TypeMirror)av.getValue();
}
}
Of course, once you get a TypeMirror, you (at least in my experience) pretty much always want a TypeElement instead:
private TypeElement asTypeElement(TypeMirror typeMirror) {
Types TypeUtils = this.processingEnv.getTypeUtils();
return (TypeElement)TypeUtils.asElement(typeMirror);
}
... that last little non-obvious bit took me an hour of hair pulling before I sorted it out the first time. These annotation processors are actually not that hard to write at all, the API's are just super confusing at first and mindbendingly verbose. I'm tempted to put out a helper class that makes all the basic operations obvious ... but that's a story for another day (msg me if you want it).
Have you read this article: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ ?
There the trick is to actually use getAnnotation() and catch the MirroredTypeException. Surprisingly the exception then provides the TypeMirror of the required class.
I don't know if this is a good solution, but it is one. In my personal opinion I would try to get the type behind the MirroredType, but I don't know if this is possible.
My answer is basically the same as that from Dave Dopson, only the code was changed a bit:
public default Optional<? extends AnnotationMirror> getAnnotationMirror( final Element element, final Class<? extends Annotation> annotationClass )
{
final var annotationClassName = annotationClass.getName();
final Optional<? extends AnnotationMirror> retValue = element.getAnnotationMirrors().stream()
.filter( m -> m.getAnnotationType().toString().equals( annotationClassName ) )
.findFirst();
return retValue;
}
His code used TypeElement
as the type for the first argument of getAnnotationMirror()
, limiting the use of the method for classes only. Changing that to Element
makes it useable for any annotated element.
Using an implementation based on streams instead of the original for
loop is just a matter of taste.
public default Optional<? extends AnnotationValue> getAnnotationValue( final AnnotationMirror annotationMirror, final String name )
{
final Elements elementUtils = this.processingEnv.getElementUtils();
final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = elementUtils.getElementValuesWithDefaults( annotationMirror );
final Optional<? extends AnnotationValue> retValue = elementValues.keySet().stream()
.filter( k -> k.getSimpleName().toString().equals( name ) )
.map( k -> elementValues.get( k ) )
.findAny();
return retValue;
}
Dave used Element.getElementValues()
to get the annotation values, but this returns only those values that were explicitly applied to the concrete annotation. The defaults will be omitted.
The method Elements.getElementValuesWithDefaults()
will return also the defaults (as you may have guessed already from the name of it).
This is my trick:
public class APUtils {
@FunctionalInterface
public interface GetClassValue {
void execute() throws MirroredTypeException, MirroredTypesException;
}
public static List<? extends TypeMirror> getTypeMirrorFromAnnotationValue(GetClassValue c) {
try {
c.execute();
}
catch(MirroredTypesException ex) {
return ex.getTypeMirrors();
}
return null;
}
}
Then I can use with any Annotation value:
public @interface MyAnnotationType {
public Class<?>[] value() default {};
}
Example
@MyAnnotationType(String.class)
public class Test { ... }
Usage in annotation processor
MyAnnotationType a = element.getAnnotation(MyAnnotationType.class);
List<? extends TypeMirror> types = APUtils.getTypeMirrorFromAnnotationValue(() -> a.value());
Tested with JDK 1.8.0_192
精彩评论