Document SynthesizedAnnotation support

Issue: SPR-11512
This commit is contained in:
Sam Brannen 2015-05-23 01:01:39 +02:00
parent 05d475a275
commit ab92f4ed3a
3 changed files with 66 additions and 41 deletions

View File

@ -932,7 +932,7 @@ public class AnnotatedElementUtils {
@Override @Override
public void postProcess(AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) { public void postProcess(AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) {
annotation = AnnotationUtils.synthesizeAnnotation(annotation); annotation = AnnotationUtils.synthesizeAnnotation(annotation, element);
Class<? extends Annotation> targetAnnotationType = attributes.annotationType(); Class<? extends Annotation> targetAnnotationType = attributes.annotationType();
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) { for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {

View File

@ -140,7 +140,7 @@ public abstract class AnnotationUtils {
} }
Class<? extends Annotation> annotatedElement = ann.annotationType(); Class<? extends Annotation> annotatedElement = ann.annotationType();
try { try {
return synthesizeAnnotation(annotatedElement, annotatedElement.getAnnotation(annotationType)); return synthesizeAnnotation(annotatedElement.getAnnotation(annotationType), annotatedElement);
} }
catch (Exception ex) { catch (Exception ex) {
// Assuming nested Class values not resolvable within annotation attributes... // Assuming nested Class values not resolvable within annotation attributes...
@ -163,16 +163,16 @@ public abstract class AnnotationUtils {
*/ */
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) { public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
try { try {
A ann = annotatedElement.getAnnotation(annotationType); A annotation = annotatedElement.getAnnotation(annotationType);
if (ann == null) { if (annotation == null) {
for (Annotation metaAnn : annotatedElement.getAnnotations()) { for (Annotation metaAnn : annotatedElement.getAnnotations()) {
ann = metaAnn.annotationType().getAnnotation(annotationType); annotation = metaAnn.annotationType().getAnnotation(annotationType);
if (ann != null) { if (annotation != null) {
break; break;
} }
} }
} }
return synthesizeAnnotation(annotatedElement, ann); return synthesizeAnnotation(annotation, annotatedElement);
} }
catch (Exception ex) { catch (Exception ex) {
// Assuming nested Class values not resolvable within annotation attributes... // Assuming nested Class values not resolvable within annotation attributes...
@ -319,8 +319,8 @@ public abstract class AnnotationUtils {
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) { public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
// Do NOT store result in the findAnnotationCache since doing so could break // Do NOT store result in the findAnnotationCache since doing so could break
// findAnnotation(Class, Class) and findAnnotation(Method, Class). // findAnnotation(Class, Class) and findAnnotation(Method, Class).
return synthesizeAnnotation(annotatedElement, return synthesizeAnnotation(findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>()),
findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>())); annotatedElement);
} }
/** /**
@ -411,7 +411,7 @@ public abstract class AnnotationUtils {
} }
} }
return synthesizeAnnotation(method, result); return synthesizeAnnotation(result, method);
} }
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) { private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
@ -487,7 +487,7 @@ public abstract class AnnotationUtils {
findAnnotationCache.put(cacheKey, result); findAnnotationCache.put(cacheKey, result);
} }
} }
return synthesizeAnnotation(clazz, result); return synthesizeAnnotation(result, clazz);
} }
/** /**
@ -820,7 +820,7 @@ public abstract class AnnotationUtils {
boolean defaultValuesAsPlaceholder, boolean synthesizeAnnotation) { boolean defaultValuesAsPlaceholder, boolean synthesizeAnnotation) {
if (synthesizeAnnotation) { if (synthesizeAnnotation) {
annotation = synthesizeAnnotation(annotatedElement, annotation); annotation = synthesizeAnnotation(annotation, annotatedElement);
} }
Class<? extends Annotation> annotationType = annotation.annotationType(); Class<? extends Annotation> annotationType = annotation.annotationType();
@ -854,7 +854,7 @@ public abstract class AnnotationUtils {
/** /**
* Adapt the given value according to the given class and nested annotation settings. * Adapt the given value according to the given class and nested annotation settings.
* <p>Nested annotations will be * <p>Nested annotations will be
* {@linkplain #synthesizeAnnotation(AnnotatedElement, Annotation) synthesized}. * {@linkplain #synthesizeAnnotation(Annotation, AnnotatedElement) synthesized}.
* @param annotatedElement the element that is annotated, used for contextual * @param annotatedElement the element that is annotated, used for contextual
* logging; may be {@code null} if unknown * logging; may be {@code null} if unknown
* @param value the annotation attribute value * @param value the annotation attribute value
@ -892,7 +892,7 @@ public abstract class AnnotationUtils {
nestedAnnotationsAsMap); nestedAnnotationsAsMap);
} }
else { else {
return synthesizeAnnotation(annotatedElement, annotation); return synthesizeAnnotation(annotation, annotatedElement);
} }
} }
@ -910,7 +910,7 @@ public abstract class AnnotationUtils {
else { else {
Annotation[] synthesizedAnnotations = new Annotation[annotations.length]; Annotation[] synthesizedAnnotations = new Annotation[annotations.length];
for (int i = 0; i < annotations.length; i++) { for (int i = 0; i < annotations.length; i++) {
synthesizedAnnotations[i] = synthesizeAnnotation(annotatedElement, annotations[i]); synthesizedAnnotations[i] = synthesizeAnnotation(annotations[i], annotatedElement);
} }
return synthesizedAnnotations; return synthesizedAnnotations;
} }
@ -1009,26 +1009,42 @@ public abstract class AnnotationUtils {
} }
/** /**
* TODO Document synthesizeAnnotation(). * <em>Synthesize</em> the supplied {@code annotation} by wrapping it in
* a dynamic proxy that transparently enforces <em>attribute alias</em>
* semantics for annotation attributes that are annotated with
* {@link AliasFor @AliasFor}.
* *
* @param annotation the annotation to synthesize * @param annotation the annotation to synthesize
* @return the synthesized annotation, if the supplied annotation is
* <em>synthesizable</em>; {@code null} if the supplied annotation is
* {@code null}; otherwise, the supplied annotation unmodified
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2 * @since 4.2
* @see #synthesizeAnnotation(AnnotatedElement, Annotation) * @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/ */
public static <A extends Annotation> A synthesizeAnnotation(A annotation) { static <A extends Annotation> A synthesizeAnnotation(A annotation) {
return synthesizeAnnotation(null, annotation); return synthesizeAnnotation(annotation, null);
} }
/** /**
* TODO Document synthesizeAnnotation(). * <em>Synthesize</em> the supplied {@code annotation} by wrapping it in
* a dynamic proxy that transparently enforces <em>attribute alias</em>
* semantics for annotation attributes that are annotated with
* {@link AliasFor @AliasFor}.
* *
* @param annotatedElement the element that is annotated with the supplied
* annotation, used for contextual logging; may be {@code null} if unknown
* @param annotation the annotation to synthesize * @param annotation the annotation to synthesize
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
* @return the synthesized annotation, if the supplied annotation is
* <em>synthesizable</em>; {@code null} if the supplied annotation is
* {@code null}; otherwise, the supplied annotation unmodified
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2 * @since 4.2
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(AnnotatedElement annotatedElement, A annotation) { public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
if (annotation == null) { if (annotation == null) {
return null; return null;
} }
@ -1438,7 +1454,7 @@ public abstract class AnnotationUtils {
for (Annotation ann : element.getAnnotations()) { for (Annotation ann : element.getAnnotations()) {
Class<? extends Annotation> currentAnnotationType = ann.annotationType(); Class<? extends Annotation> currentAnnotationType = ann.annotationType();
if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) { if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) {
this.result.add(synthesizeAnnotation(element, (A) ann)); this.result.add(synthesizeAnnotation((A) ann, element));
} }
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) { else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) {
this.result.addAll(getValue(element, ann)); this.result.addAll(getValue(element, ann));
@ -1463,7 +1479,7 @@ public abstract class AnnotationUtils {
List<A> synthesizedAnnotations = new ArrayList<A>(); List<A> synthesizedAnnotations = new ArrayList<A>();
for (A anno : annotations) { for (A anno : annotations) {
synthesizedAnnotations.add(synthesizeAnnotation(element, anno)); synthesizedAnnotations.add(synthesizeAnnotation(anno, element));
} }
return synthesizedAnnotations; return synthesizedAnnotations;
} }

View File

@ -26,10 +26,20 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
* TODO Document SynthesizedAnnotationInvocationHandler. * {@link InvocationHandler} for an {@link Annotation} that Spring has
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
* functionality.
*
* <p>{@code SynthesizedAnnotationInvocationHandler} transparently enforces
* attribute alias semantics for annotation attributes that are annotated
* with {@link AliasFor @AliasFor}. In addition, nested annotations and
* arrays of nested annotations will be synthesized upon first access (i.e.,
* <em>lazily</em>).
* *
* @author Sam Brannen * @author Sam Brannen
* @since 4.2 * @since 4.2
* @see AliasFor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/ */
class SynthesizedAnnotationInvocationHandler implements InvocationHandler { class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
@ -37,18 +47,17 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private final Annotation annotation; private final Annotation annotation;
private final Map<String, String> aliasPairs; private final Class<? extends Annotation> annotationType;
private final Map<String, String> aliasMap;
public SynthesizedAnnotationInvocationHandler(Annotation annotation, Map<String, String> aliasPairs) {
this(null, annotation, aliasPairs);
}
public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation, public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
Map<String, String> aliasPairs) { Map<String, String> aliasMap) {
this.annotatedElement = annotatedElement; this.annotatedElement = annotatedElement;
this.annotation = annotation; this.annotation = annotation;
this.aliasPairs = aliasPairs; this.annotationType = annotation.annotationType();
this.aliasMap = aliasMap;
} }
@Override @Override
@ -56,8 +65,8 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
String attributeName = method.getName(); String attributeName = method.getName();
Class<?> returnType = method.getReturnType(); Class<?> returnType = method.getReturnType();
boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType)); boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
String aliasedAttributeName = aliasPairs.get(attributeName); String aliasedAttributeName = aliasMap.get(attributeName);
boolean aliasPresent = aliasedAttributeName != null; boolean aliasPresent = (aliasedAttributeName != null);
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args); Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
@ -70,26 +79,26 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
if (aliasPresent) { if (aliasPresent) {
Method aliasedMethod = null; Method aliasedMethod = null;
try { try {
aliasedMethod = annotation.annotationType().getDeclaredMethod(aliasedAttributeName); aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName);
} }
catch (NoSuchMethodException e) { catch (NoSuchMethodException e) {
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], " String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], "
+ "but attribute [%s] does not exist.", annotation.annotationType().getName(), attributeName, + "but attribute [%s] does not exist.", this.annotationType.getName(), attributeName,
aliasedAttributeName, aliasedAttributeName); aliasedAttributeName, aliasedAttributeName);
throw new AnnotationConfigurationException(msg); throw new AnnotationConfigurationException(msg);
} }
ReflectionUtils.makeAccessible(aliasedMethod); ReflectionUtils.makeAccessible(aliasedMethod);
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args); Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
Object defaultValue = AnnotationUtils.getDefaultValue(annotation, attributeName); Object defaultValue = AnnotationUtils.getDefaultValue(this.annotation, attributeName);
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue) if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) { && !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
String elementAsString = (annotatedElement == null ? "unknown element" : annotatedElement.toString()); String elementName = (this.annotatedElement == null ? "unknown element" : this.annotatedElement.toString());
String msg = String.format( String msg = String.format(
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are " "In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
+ "declared with values of [%s] and [%s], but only one declaration is permitted.", + "declared with values of [%s] and [%s], but only one declaration is permitted.",
annotation.annotationType().getName(), elementAsString, attributeName, aliasedAttributeName, this.annotationType.getName(), elementName, attributeName, aliasedAttributeName,
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue)); ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
throw new AnnotationConfigurationException(msg); throw new AnnotationConfigurationException(msg);
} }
@ -103,12 +112,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
// Synthesize nested annotations before returning them. // Synthesize nested annotations before returning them.
if (value instanceof Annotation) { if (value instanceof Annotation) {
value = AnnotationUtils.synthesizeAnnotation(annotatedElement, (Annotation) value); value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.annotatedElement);
} }
else if (value instanceof Annotation[]) { else if (value instanceof Annotation[]) {
Annotation[] annotations = (Annotation[]) value; Annotation[] annotations = (Annotation[]) value;
for (int i = 0; i < annotations.length; i++) { for (int i = 0; i < annotations.length; i++) {
annotations[i] = AnnotationUtils.synthesizeAnnotation(annotatedElement, annotations[i]); annotations[i] = AnnotationUtils.synthesizeAnnotation(annotations[i], this.annotatedElement);
} }
} }