Merge from sbrannen/SPR-13067

* SPR-13067:
  Synthesize annotation from a map of attributes
This commit is contained in:
Sam Brannen 2015-05-29 01:43:50 +02:00
commit a31d1bdf60
25 changed files with 881 additions and 358 deletions

View File

@ -32,7 +32,6 @@ import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver; import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
@ -248,11 +247,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
*/ */
protected String getCondition() { protected String getCondition() {
if (this.condition == null) { if (this.condition == null) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils EventListener eventListener = AnnotatedElementUtils.findAnnotation(this.method, EventListener.class);
.findAnnotationAttributes(this.method, EventListener.class); if (eventListener != null) {
if (annotationAttributes != null) { this.condition = eventListener.condition();
String value = annotationAttributes.getString("condition");
this.condition = (value != null ? value : "");
} }
} }
return this.condition; return this.condition;

View File

@ -0,0 +1,135 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Abstract base class for {@link AnnotationAttributeExtractor} implementations
* that transparently enforce attribute alias semantics for annotation
* attributes that are annotated with {@link AliasFor @AliasFor}.
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/
abstract class AbstractAliasAwareAnnotationAttributeExtractor implements AnnotationAttributeExtractor {
private final Class<? extends Annotation> annotationType;
private final AnnotatedElement annotatedElement;
private final Object source;
private final Map<String, String> attributeAliasMap;
/**
* Construct a new {@code AbstractAliasAwareAnnotationAttributeExtractor}.
* @param annotationType the annotation type to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the annotation
* of the supplied type; may be {@code null} if unknown
* @param source the underlying source of annotation attributes; never {@code null}
*/
AbstractAliasAwareAnnotationAttributeExtractor(Class<? extends Annotation> annotationType,
AnnotatedElement annotatedElement, Object source) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(source, "source must not be null");
this.annotationType = annotationType;
this.annotatedElement = annotatedElement;
this.source = source;
this.attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType);
}
@Override
public final Class<? extends Annotation> getAnnotationType() {
return this.annotationType;
}
@Override
public final AnnotatedElement getAnnotatedElement() {
return this.annotatedElement;
}
@Override
public Object getSource() {
return this.source;
}
@Override
public final Object getAttributeValue(Method attributeMethod) {
String attributeName = attributeMethod.getName();
Object attributeValue = getRawAttributeValue(attributeMethod);
String aliasName = this.attributeAliasMap.get(attributeName);
if ((aliasName != null)) {
Object aliasValue = getRawAttributeValue(aliasName);
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
if (!nullSafeEquals(attributeValue, aliasValue) && !nullSafeEquals(attributeValue, defaultValue)
&& !nullSafeEquals(aliasValue, defaultValue)) {
String elementName = (getAnnotatedElement() == null ? "unknown element"
: getAnnotatedElement().toString());
String msg = String.format("In annotation [%s] declared on [%s] and synthesized from [%s], "
+ "attribute [%s] and its alias [%s] are present with values of [%s] and [%s], "
+ "but only one is permitted.", getAnnotationType().getName(), elementName, getSource(),
attributeName, aliasName, nullSafeToString(attributeValue), nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);
}
// If the user didn't declare the annotation with an explicit value,
// return the value of the alias.
if (nullSafeEquals(attributeValue, defaultValue)) {
attributeValue = aliasValue;
}
}
return attributeValue;
}
/**
* Get the raw, unmodified attribute value from the underlying
* {@linkplain #getSource source} that corresponds to the supplied
* attribute method.
*/
protected abstract Object getRawAttributeValue(Method attributeMethod);
/**
* Get the raw, unmodified attribute value from the underlying
* {@linkplain #getSource source} that corresponds to the supplied
* attribute name.
*/
protected abstract Object getRawAttributeValue(String attributeName);
private static boolean nullSafeEquals(Object o1, Object o2) {
return ObjectUtils.nullSafeEquals(o1, o2);
}
private static String nullSafeToString(Object obj) {
return ObjectUtils.nullSafeToString(obj);
}
}

View File

@ -48,8 +48,8 @@ import org.springframework.util.StringUtils;
* <h3>Annotation Attribute Overrides</h3> * <h3>Annotation Attribute Overrides</h3>
* <p>Support for meta-annotations with <em>attribute overrides</em> in * <p>Support for meta-annotations with <em>attribute overrides</em> in
* <em>composed annotations</em> is provided by all variants of the * <em>composed annotations</em> is provided by all variants of the
* {@code getAnnotationAttributes()} and {@code findAnnotationAttributes()} * {@code getAnnotationAttributes()}, {@code findAnnotation()}, and
* methods. * {@code findAnnotationAttributes()} methods.
* *
* <h3>Find vs. Get Semantics</h3> * <h3>Find vs. Get Semantics</h3>
* <p>The search algorithms used by methods in this class follow either * <p>The search algorithms used by methods in this class follow either
@ -224,6 +224,9 @@ public class AnnotatedElementUtils {
* merge that annotation's attributes with <em>matching</em> attributes from * merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy.
* *
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
*
* <p>This method delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, * <p>This method delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
* *
@ -233,8 +236,8 @@ public class AnnotatedElementUtils {
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged {@code AnnotationAttributes}, or {@code null} if
* not found * not found
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #findAnnotationAttributes(AnnotatedElement, String) * @see #findAnnotation(AnnotatedElement, Class)
* @see #getAllAnnotationAttributes(AnnotatedElement, String) * @see #getAllAnnotationAttributes(AnnotatedElement, String)
*/ */
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) { public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
@ -248,7 +251,9 @@ public class AnnotatedElementUtils {
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy.
* *
* <p>Attributes from lower levels in the annotation hierarchy override * <p>Attributes from lower levels in the annotation hierarchy override
* attributes of the same name from higher levels. * attributes of the same name from higher levels, and
* {@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>In contrast to {@link #getAllAnnotationAttributes}, the search * <p>In contrast to {@link #getAllAnnotationAttributes}, the search
* algorithm used by this method will stop searching the annotation * algorithm used by this method will stop searching the annotation
@ -269,8 +274,7 @@ public class AnnotatedElementUtils {
* as Annotation instances * as Annotation instances
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged {@code AnnotationAttributes}, or {@code null} if
* not found * not found
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class)
* @see #findAnnotationAttributes(AnnotatedElement, String)
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
@ -286,47 +290,59 @@ public class AnnotatedElementUtils {
/** /**
* Find the first annotation of the specified {@code annotationType} within * Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element} and * the annotation hierarchy <em>above</em> the supplied {@code element},
* merge that annotation's attributes with <em>matching</em> attributes from * merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy, and synthesize
* the result back into an annotation of the specified {@code annotationType}.
*
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} * <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
* and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* *
* @param element the annotated element; never {@code null} * @param element the annotated element; never {@code null}
* @param annotationType the annotation type to find; never {@code null} * @param annotationType the annotation type to find; never {@code null}
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* not found
* @since 4.2 * @since 4.2
* @see #findAnnotationAttributes(AnnotatedElement, String) * @see #findAnnotation(AnnotatedElement, String)
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, public static <A extends Annotation> A findAnnotation(AnnotatedElement element, Class<A> annotationType) {
Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null"); Assert.notNull(annotationType, "annotationType must not be null");
return findAnnotationAttributes(element, annotationType.getName()); return findAnnotation(element, annotationType.getName());
} }
/** /**
* Find the first annotation of the specified {@code annotationType} within * Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element} and * the annotation hierarchy <em>above</em> the supplied {@code element},
* merge that annotation's attributes with <em>matching</em> attributes from * merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy, and synthesize
* the result back into an annotation of the specified {@code annotationType}.
*
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} * <p>This method delegates to {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
* and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* *
* @param element the annotated element; never {@code null} * @param element the annotated element; never {@code null}
* @param annotationType the fully qualified class name of the annotation * @param annotationType the fully qualified class name of the annotation
* type to find; never {@code null} or empty * type to find; never {@code null} or empty
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* not found
* @since 4.2 * @since 4.2
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class)
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) { @SuppressWarnings("unchecked")
return findAnnotationAttributes(element, annotationType, false, false); public static <A extends Annotation> A findAnnotation(AnnotatedElement element, String annotationType) {
AnnotationAttributes attributes = findAnnotationAttributes(element, annotationType, false, false);
return ((attributes != null) ? AnnotationUtils.synthesizeAnnotation(attributes,
(Class<A>) attributes.annotationType(), element) : null);
} }
/** /**
@ -336,7 +352,9 @@ public class AnnotatedElementUtils {
* annotations in lower levels of the annotation hierarchy. * annotations in lower levels of the annotation hierarchy.
* *
* <p>Attributes from lower levels in the annotation hierarchy override * <p>Attributes from lower levels in the annotation hierarchy override
* attributes of the same name from higher levels. * attributes of the same name from higher levels, and
* {@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* *
* <p>In contrast to {@link #getAllAnnotationAttributes}, the search * <p>In contrast to {@link #getAllAnnotationAttributes}, the search
* algorithm used by this method will stop searching the annotation * algorithm used by this method will stop searching the annotation
@ -358,8 +376,7 @@ public class AnnotatedElementUtils {
* @return the merged {@code AnnotationAttributes}, or {@code null} if * @return the merged {@code AnnotationAttributes}, or {@code null} if
* not found * not found
* @since 4.2 * @since 4.2
* @see #findAnnotationAttributes(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class)
* @see #findAnnotationAttributes(AnnotatedElement, String)
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/ */
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
/**
* An {@code AnnotationAttributeExtractor} is responsible for
* {@linkplain #getAttributeValue extracting} annotation attribute values
* from an underlying {@linkplain #getSource source} such as an
* {@code Annotation} or a {@code Map}.
*
* @author Sam Brannen
* @since 4.2
* @see SynthesizedAnnotationInvocationHandler
*/
interface AnnotationAttributeExtractor {
/**
* Get the type of annotation that this extractor extracts attribute
* values for.
*/
Class<? extends Annotation> getAnnotationType();
/**
* Get the element that is annotated with an annotation of the annotation
* type supported by this extractor.
* @return the annotated element, or {@code null} if unknown
*/
AnnotatedElement getAnnotatedElement();
/**
* Get the underlying source of annotation attributes.
*/
Object getSource();
/**
* Get the attribute value from the underlying {@linkplain #getSource source}
* that corresponds to the supplied attribute method.
* @param attributeMethod an attribute method from the annotation type
* supported by this extractor
* @return the value of the annotation attribute
*/
Object getAttributeValue(Method attributeMethod);
}

View File

@ -809,8 +809,10 @@ public abstract class AnnotationUtils {
* merging attributes within an annotation hierarchy. When running in <em>merge mode</em>, * merging attributes within an annotation hierarchy. When running in <em>merge mode</em>,
* the following special rules apply: * the following special rules apply:
* <ol> * <ol>
* <li>The supplied annotation will <strong>not</strong> be * <li>The supplied annotation will <em>not</em> be
* {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes.</li> * {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes;
* however, nested annotations and arrays of nested annotations <em>will</em> be
* synthesized.</li>
* <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li> * <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li>
* <li>The resulting, merged annotation attributes should eventually be * <li>The resulting, merged annotation attributes should eventually be
* {@linkplain #postProcessAnnotationAttributes post-processed} in order to * {@linkplain #postProcessAnnotationAttributes post-processed} in order to
@ -1051,12 +1053,13 @@ public abstract class AnnotationUtils {
* @param annotation the annotation to synthesize * @param annotation the annotation to synthesize
* @param annotatedElement the element that is annotated with the supplied * @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown * annotation; may be {@code null} if unknown
* @return the synthesized annotation, if the supplied annotation is * @return the synthesized annotation if the supplied annotation is
* <em>synthesizable</em>; {@code null} if the supplied annotation is * <em>synthesizable</em>; {@code null} if the supplied annotation is
* {@code null}; otherwise, the supplied annotation unmodified * {@code null}; otherwise the supplied annotation unmodified
* @throws AnnotationConfigurationException if invalid configuration of * @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected * {@code @AliasFor} is detected
* @since 4.2 * @since 4.2
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) { public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
@ -1068,20 +1071,58 @@ public abstract class AnnotationUtils {
} }
Class<? extends Annotation> annotationType = annotation.annotationType(); Class<? extends Annotation> annotationType = annotation.annotationType();
// No need to synthesize?
if (!isSynthesizable(annotationType)) { if (!isSynthesizable(annotationType)) {
return annotation; return annotation;
} }
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(annotation, annotatedElement, AnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation,
getAttributeAliasMap(annotationType)); annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] { A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
(Class<A>) annotationType, SynthesizedAnnotation.class }, handler); (Class<A>) annotationType, SynthesizedAnnotation.class }, handler);
return synthesizedAnnotation; return synthesizedAnnotation;
} }
/**
* <em>Synthesize</em> the supplied map of annotation attributes by
* wrapping it in a dynamic proxy that implements an annotation of type
* {@code annotationType} and transparently enforces <em>attribute alias</em>
* semantics for annotation attributes that are annotated with
* {@link AliasFor @AliasFor}.
* <p>The supplied map must contain key-value pairs for every attribute
* defined by the supplied {@code annotationType}.
* <p>Note that {@link AnnotationAttributes} is a specialized type of
* {@link Map} that is a suitable candidate for this method's
* {@code attributes} argument.
*
* @param attributes the map of annotation attributes to synthesize
* @param annotationType the type of annotation to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the annotation
* corresponding to the supplied attributes; may be {@code null} if unknown
* @return the synthesized annotation, or {@code null} if the supplied attributes
* map is {@code null}
* @throws AnnotationConfigurationException if invalid configuration is detected
* @since 4.2
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(Map<String, Object> attributes,
Class<A> annotationType, AnnotatedElement annotatedElement) {
Assert.notNull(annotationType, "annotationType must not be null");
if (attributes == null) {
return null;
}
AnnotationAttributeExtractor attributeExtractor = new MapAnnotationAttributeExtractor(attributes,
annotationType, annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
annotationType, SynthesizedAnnotation.class }, handler);
return synthesizedAnnotation;
}
/** /**
* Get a map of all attribute alias pairs, declared via {@code @AliasFor} * Get a map of all attribute alias pairs, declared via {@code @AliasFor}
@ -1098,7 +1139,7 @@ public abstract class AnnotationUtils {
* @return a map containing attribute alias pairs; never {@code null} * @return a map containing attribute alias pairs; never {@code null}
* @since 4.2 * @since 4.2
*/ */
private static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) { static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) {
if (annotationType == null) { if (annotationType == null) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
@ -1334,7 +1375,7 @@ public abstract class AnnotationUtils {
methods = new ArrayList<Method>(); methods = new ArrayList<Method>();
for (Method method : annotationType.getDeclaredMethods()) { for (Method method : annotationType.getDeclaredMethods()) {
if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) { if (isAttributeMethod(method)) {
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
methods.add(method); methods.add(method);
} }
@ -1345,6 +1386,24 @@ public abstract class AnnotationUtils {
return methods; return methods;
} }
/**
* Determine if the supplied {@code method} is an annotation attribute method.
* @param method the method to check
* @return {@code true} if the method is an attribute method
*/
static boolean isAttributeMethod(Method method) {
return ((method != null) && (method.getParameterTypes().length == 0) && (method.getReturnType() != void.class));
}
/**
* Determine if the supplied method is an "annotationType" method.
* @return {@code true} if the method is an "annotationType" method
* @see Annotation#annotationType()
*/
static boolean isAnnotationTypeMethod(Method method) {
return ((method != null) && method.getName().equals("annotationType") && (method.getParameterTypes().length == 0));
}
/** /**
* Post-process the supplied {@link AnnotationAttributes}. * Post-process the supplied {@link AnnotationAttributes}.
* *

View File

@ -0,0 +1,65 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import org.springframework.util.ReflectionUtils;
/**
* Default implementation of the {@link AnnotationAttributeExtractor} strategy
* that is backed by an {@link Annotation}.
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see MapAnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor {
/**
* Construct a new {@code DefaultAnnotationAttributeExtractor}.
* @param annotation the annotation to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
*/
DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) {
super(annotation.annotationType(), annotatedElement, annotation);
}
@Override
protected Object getRawAttributeValue(Method attributeMethod) {
ReflectionUtils.makeAccessible(attributeMethod);
return ReflectionUtils.invokeMethod(attributeMethod, getAnnotation());
}
@Override
protected Object getRawAttributeValue(String attributeName) {
Method attributeMethod = ReflectionUtils.findMethod(getAnnotation().annotationType(), attributeName);
return getRawAttributeValue(attributeMethod);
}
private Annotation getAnnotation() {
return (Annotation) getSource();
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.ClassUtils;
import static org.springframework.core.annotation.AnnotationUtils.*;
/**
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
* is backed by a {@link Map}.
*
* @author Sam Brannen
* @since 4.2
* @see Annotation
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see DefaultAnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor {
/**
* Construct a new {@code MapAnnotationAttributeExtractor}.
* <p>The supplied map must contain key-value pairs for every attribute
* defined in the supplied {@code annotationType}.
* @param attributes the map of annotation attributes; never {@code null}
* @param annotationType the type of annotation to synthesize; never {@code null}
* @param annotatedElement the element that is annotated with the annotation
* of the supplied type; may be {@code null} if unknown
*/
MapAnnotationAttributeExtractor(Map<String, Object> attributes, Class<? extends Annotation> annotationType,
AnnotatedElement annotatedElement) {
super(annotationType, annotatedElement, new HashMap<String, Object>(attributes));
validateAttributes(attributes, annotationType);
}
@Override
protected Object getRawAttributeValue(Method attributeMethod) {
return getMap().get(attributeMethod.getName());
}
@Override
protected Object getRawAttributeValue(String attributeName) {
return getMap().get(attributeName);
}
@SuppressWarnings("unchecked")
private Map<String, Object> getMap() {
return (Map<String, Object>) getSource();
}
/**
* Validate the supplied {@code attributes} map by verifying that it
* contains a non-null entry for each annotation attribute in the specified
* {@code annotationType} and that the type of the entry matches the
* return type for the corresponding annotation attribute.
*/
private static void validateAttributes(Map<String, Object> attributes, Class<? extends Annotation> annotationType) {
for (Method attributeMethod : getAttributeMethods(annotationType)) {
String attributeName = attributeMethod.getName();
Object attributeValue = attributes.get(attributeName);
if (attributeValue == null) {
throw new IllegalArgumentException(String.format(
"Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].",
attributes, attributeName, annotationType.getName()));
}
Class<?> returnType = attributeMethod.getReturnType();
if (!ClassUtils.isAssignable(returnType, attributeValue.getClass())) {
throw new IllegalArgumentException(String.format(
"Attributes map [%s] returned a value of type [%s] for attribute [%s], "
+ "but a value of type [%s] is required as defined by annotation type [%s].", attributes,
attributeValue.getClass().getName(), attributeName, returnType.getName(), annotationType.getName()));
}
}
}
}

View File

@ -18,6 +18,7 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
@ -25,6 +26,7 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -36,150 +38,141 @@ import static org.springframework.util.ReflectionUtils.*;
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional * <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
* functionality. * 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 Annotation * @see Annotation
* @see AliasFor * @see AnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement) * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/ */
class SynthesizedAnnotationInvocationHandler implements InvocationHandler { class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private final AnnotatedElement annotatedElement; private final AnnotationAttributeExtractor attributeExtractor;
private final Annotation annotation; private final Map<String, Object> valueCache = new ConcurrentHashMap<String, Object>(8);
private final Class<? extends Annotation> annotationType;
private final Map<String, String> aliasMap;
private final Map<String, Object> computedValueCache;
/** /**
* Construct a new {@code SynthesizedAnnotationInvocationHandler}. * Construct a new {@code SynthesizedAnnotationInvocationHandler} for
* * the supplied {@link AnnotationAttributeExtractor}.
* @param annotation the annotation to synthesize * @param attributeExtractor the extractor to delegate to
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
* @param aliasMap the map of attribute alias pairs, declared via
* {@code @AliasFor} in the supplied annotation
*/ */
SynthesizedAnnotationInvocationHandler(Annotation annotation, AnnotatedElement annotatedElement, SynthesizedAnnotationInvocationHandler(AnnotationAttributeExtractor attributeExtractor) {
Map<String, String> aliasMap) { Assert.notNull(attributeExtractor, "AnnotationAttributeExtractor must not be null");
this.annotatedElement = annotatedElement; this.attributeExtractor = attributeExtractor;
this.annotation = annotation;
this.annotationType = annotation.annotationType();
this.aliasMap = aliasMap;
this.computedValueCache = new ConcurrentHashMap<String, Object>(aliasMap.size());
} }
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isEqualsMethod(method)) { if (isEqualsMethod(method)) {
return equals(proxy, args[0]); return annotationEquals(args[0]);
} }
if (isHashCodeMethod(method)) { if (isHashCodeMethod(method)) {
return hashCode(proxy); return annotationHashCode();
} }
if (isToStringMethod(method)) { if (isToStringMethod(method)) {
return toString(proxy); return annotationToString();
} }
if (isAnnotationTypeMethod(method)) {
String methodName = method.getName(); return annotationType();
Class<?> returnType = method.getReturnType();
boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
String aliasedAttributeName = aliasMap.get(methodName);
boolean aliasPresent = (aliasedAttributeName != null);
makeAccessible(method);
// No custom processing necessary?
if (!aliasPresent && !nestedAnnotation) {
return invokeMethod(method, this.annotation, args);
} }
if (!isAttributeMethod(method)) {
Object cachedValue = this.computedValueCache.get(methodName); String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method,
if (cachedValue != null) { annotationType());
return cachedValue; throw new AnnotationConfigurationException(msg);
} }
return getAttributeValue(method);
}
Object value = invokeMethod(method, this.annotation, args); private Class<? extends Annotation> annotationType() {
return this.attributeExtractor.getAnnotationType();
}
if (aliasPresent) { private Object getAttributeValue(Method attributeMethod) {
Method aliasedMethod = null; String attributeName = attributeMethod.getName();
try { Object value = this.valueCache.get(attributeName);
aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName); if (value == null) {
} value = this.attributeExtractor.getAttributeValue(attributeMethod);
catch (NoSuchMethodException e) { if (value == null) {
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], " throw new IllegalStateException(String.format(
+ "but attribute [%s] does not exist.", this.annotationType.getName(), methodName, "%s returned null for attribute name [%s] from attribute source [%s]",
aliasedAttributeName, aliasedAttributeName); this.attributeExtractor.getClass().getName(), attributeName, this.attributeExtractor.getSource()));
throw new AnnotationConfigurationException(msg);
} }
makeAccessible(aliasedMethod); // Synthesize nested annotations before returning them.
Object aliasedValue = invokeMethod(aliasedMethod, this.annotation); if (value instanceof Annotation) {
Object defaultValue = getDefaultValue(this.annotation, methodName); value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
}
if (!nullSafeEquals(value, aliasedValue) && !nullSafeEquals(value, defaultValue) else if (value instanceof Annotation[]) {
&& !nullSafeEquals(aliasedValue, defaultValue)) { Annotation[] orig = (Annotation[]) value;
String elementName = (this.annotatedElement == null ? "unknown element" Annotation[] clone = (Annotation[]) Array.newInstance(orig.getClass().getComponentType(), orig.length);
: this.annotatedElement.toString()); for (int i = 0; i < orig.length; i++) {
String msg = String.format( clone[i] = synthesizeAnnotation(orig[i], this.attributeExtractor.getAnnotatedElement());
"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.", value = clone;
this.annotationType.getName(), elementName, methodName, aliasedAttributeName,
nullSafeToString(value), nullSafeToString(aliasedValue));
throw new AnnotationConfigurationException(msg);
} }
// If the user didn't declare the annotation with an explicit value, return this.valueCache.put(attributeName, value);
// the value of the alias.
if (nullSafeEquals(value, defaultValue)) {
value = aliasedValue;
}
} }
// Synthesize nested annotations before returning them. // Clone arrays so that users cannot alter the contents of values in our cache.
if (value instanceof Annotation) { if (value.getClass().isArray()) {
value = synthesizeAnnotation((Annotation) value, this.annotatedElement); value = cloneArray(value);
} }
else if (value instanceof Annotation[]) {
Annotation[] annotations = (Annotation[]) value;
for (int i = 0; i < annotations.length; i++) {
annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement);
}
}
this.computedValueCache.put(methodName, value);
return value; return value;
} }
/**
* Clone the provided array, ensuring that original component type is
* retained.
* @param array the array to clone
*/
private Object cloneArray(Object array) {
if (array instanceof boolean[]) {
return ((boolean[]) array).clone();
}
if (array instanceof byte[]) {
return ((byte[]) array).clone();
}
if (array instanceof char[]) {
return ((char[]) array).clone();
}
if (array instanceof double[]) {
return ((double[]) array).clone();
}
if (array instanceof float[]) {
return ((float[]) array).clone();
}
if (array instanceof int[]) {
return ((int[]) array).clone();
}
if (array instanceof long[]) {
return ((long[]) array).clone();
}
if (array instanceof short[]) {
return ((short[]) array).clone();
}
// else
return ((Object[]) array).clone();
}
/** /**
* See {@link Annotation#equals(Object)} for a definition of the required algorithm. * See {@link Annotation#equals(Object)} for a definition of the required algorithm.
*
* @param proxy the synthesized annotation
* @param other the other object to compare against * @param other the other object to compare against
*/ */
private boolean equals(Object proxy, Object other) { private boolean annotationEquals(Object other) {
if (this == other) { if (this == other) {
return true; return true;
} }
if (!this.annotationType.isInstance(other)) { if (!annotationType().isInstance(other)) {
return false; return false;
} }
for (Method attributeMethod : getAttributeMethods(this.annotationType)) { for (Method attributeMethod : getAttributeMethods(annotationType())) {
Object thisValue = invokeMethod(attributeMethod, proxy); Object thisValue = getAttributeValue(attributeMethod);
Object otherValue = invokeMethod(attributeMethod, other); Object otherValue = invokeMethod(attributeMethod, other);
if (!nullSafeEquals(thisValue, otherValue)) { if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false; return false;
} }
} }
@ -189,14 +182,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
/** /**
* See {@link Annotation#hashCode()} for a definition of the required algorithm. * See {@link Annotation#hashCode()} for a definition of the required algorithm.
*
* @param proxy the synthesized annotation
*/ */
private int hashCode(Object proxy) { private int annotationHashCode() {
int result = 0; int result = 0;
for (Method attributeMethod : getAttributeMethods(this.annotationType)) { for (Method attributeMethod : getAttributeMethods(annotationType())) {
Object value = invokeMethod(attributeMethod, proxy); Object value = getAttributeValue(attributeMethod);
int hashCode; int hashCode;
if (value.getClass().isArray()) { if (value.getClass().isArray()) {
hashCode = hashCodeForArray(value); hashCode = hashCodeForArray(value);
@ -250,39 +241,27 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
/** /**
* See {@link Annotation#toString()} for guidelines on the recommended format. * See {@link Annotation#toString()} for guidelines on the recommended format.
*
* @param proxy the synthesized annotation
*/ */
private String toString(Object proxy) { private String annotationToString() {
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("("); StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator(); Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Method attributeMethod = iterator.next(); Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName()); sb.append(attributeMethod.getName());
sb.append('='); sb.append('=');
sb.append(valueToString(invokeMethod(attributeMethod, proxy))); sb.append(attributeValueToString(getAttributeValue(attributeMethod)));
sb.append(iterator.hasNext() ? ", " : ""); sb.append(iterator.hasNext() ? ", " : "");
} }
return sb.append(")").toString(); return sb.append(")").toString();
} }
private String valueToString(Object value) { private String attributeValueToString(Object value) {
if (value instanceof Object[]) { if (value instanceof Object[]) {
return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]"; return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]";
} }
// else
return String.valueOf(value); return String.valueOf(value);
} }
private static boolean nullSafeEquals(Object o1, Object o2) {
return ObjectUtils.nullSafeEquals(o1, o2);
}
private static String nullSafeToString(Object obj) {
return ObjectUtils.nullSafeToString(obj);
}
} }

View File

@ -16,11 +16,13 @@
package org.springframework.core.annotation; package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited; import java.lang.annotation.Inherited;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -30,6 +32,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import static java.util.Arrays.*; import static java.util.Arrays.*;
@ -450,14 +453,33 @@ public class AnnotatedElementUtilsTests {
} }
@Test @Test
public void findAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() { public void findAndSynthesizeAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
String qualifier = "aliasForQualifier";
// 1) Find and merge AnnotationAttributes from the annotation hierarchy
AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class, AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class,
AliasedTransactional.class); AliasedTransactional.class);
assertNotNull("Should find @AliasedTransactional on AliasedTransactionalComponentClass", attributes); assertNotNull("@AliasedTransactional on AliasedTransactionalComponentClass.", attributes);
assertEquals("TX value for AliasedTransactionalComponentClass.", "aliasForQualifier",
attributes.getString("value")); // 2) Synthesize the AnnotationAttributes back into the target annotation
assertEquals("TX qualifier for AliasedTransactionalComponentClass.", "aliasForQualifier", AliasedTransactional annotation = AnnotationUtils.synthesizeAnnotation(attributes,
attributes.getString("qualifier")); AliasedTransactional.class, AliasedTransactionalComponentClass.class);
assertNotNull(annotation);
// 3) Verify that the AnnotationAttributes and synthesized annotation are equivalent
assertEquals("TX value via attributes.", qualifier, attributes.getString("value"));
assertEquals("TX value via synthesized annotation.", qualifier, annotation.value());
assertEquals("TX qualifier via attributes.", qualifier, attributes.getString("qualifier"));
assertEquals("TX qualifier via synthesized annotation.", qualifier, annotation.qualifier());
}
@Test
public void findAnnotationWithAttributeAliasesInTargetAnnotation() {
Class<?> element = AliasedTransactionalComponentClass.class;
AliasedTransactional annotation = findAnnotation(element, AliasedTransactional.class);
assertNotNull("@AliasedTransactional on " + element, annotation);
assertEquals("TX value via synthesized annotation.", "aliasForQualifier", annotation.value());
assertEquals("TX qualifier via synthesized annotation.", "aliasForQualifier", annotation.qualifier());
} }
@Test @Test
@ -480,6 +502,12 @@ public class AnnotatedElementUtilsTests {
} }
static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
return AnnotatedElementUtils.findAnnotationAttributes(element, annotationType.getName(), false, false);
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@MetaCycle3 @MetaCycle3

View File

@ -24,7 +24,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.Rule; import org.junit.Rule;
@ -436,7 +438,7 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class); exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("attribute [value] and its alias [path]")); exception.expectMessage(containsString("attribute [value] and its alias [path]"));
exception.expectMessage(containsString("values of [/enigma] and [/test]")); exception.expectMessage(containsString("values of [/enigma] and [/test]"));
exception.expectMessage(containsString("but only one declaration is permitted")); exception.expectMessage(containsString("but only one is permitted"));
getAnnotationAttributes(webMapping); getAnnotationAttributes(webMapping);
} }
@ -504,7 +506,8 @@ public class AnnotationUtilsTests {
@Test @Test
public void getRepeatableWithAttributeAliases() throws Exception { public void getRepeatableWithAttributeAliases() throws Exception {
Set<ContextConfig> annotations = getRepeatableAnnotation(TestCase.class, Hierarchy.class, ContextConfig.class); Set<ContextConfig> annotations = getRepeatableAnnotation(ConfigHierarchyTestCase.class, Hierarchy.class,
ContextConfig.class);
assertNotNull(annotations); assertNotNull(annotations);
List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList()); List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList());
@ -522,7 +525,7 @@ public class AnnotationUtilsTests {
@Test @Test
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception { public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
Component component = findAnnotation(WebController.class, Component.class); Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component); assertNotNull(component);
Component synthesizedComponent = synthesizeAnnotation(component); Component synthesizedComponent = synthesizeAnnotation(component);
assertNotNull(synthesizedComponent); assertNotNull(synthesizedComponent);
@ -530,6 +533,29 @@ public class AnnotationUtilsTests {
assertEquals("value attribute: ", "webController", synthesizedComponent.value()); assertEquals("value attribute: ", "webController", synthesizedComponent.value());
} }
@Test
public void synthesizeAnnotationsFromNullSources() throws Exception {
assertNull("null annotation", synthesizeAnnotation(null, null));
assertNull("null map", synthesizeAnnotation(null, WebMapping.class, null));
}
@Test
public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
assertNotNull(webMapping);
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
assertNotSame(webMapping, synthesizedWebMapping);
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
assertNotNull(synthesizedAgainWebMapping);
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name());
assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value());
}
@Test @Test
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception { public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class); AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
@ -638,6 +664,78 @@ public class AnnotationUtilsTests {
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value()); assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value());
} }
@Test
public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception {
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
Map<String, Object> map = new HashMap<String, Object>();
map.put(VALUE, "webController");
Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class);
assertNotNull(synthesizedComponent);
assertNotSame(component, synthesizedComponent);
assertEquals("value from component: ", "webController", component.value());
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
}
@Test
public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned null for required attribute [value]"));
exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(new HashMap<String, Object>(), Component.class, null);
}
@Test
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put(VALUE, null);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned null for required attribute [value]"));
exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(map, Component.class, null);
}
@Test
public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put(VALUE, 42L);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned a value of type [java.lang.Long]"));
exception.expectMessage(containsString("for attribute [value]"));
exception.expectMessage(containsString("but a value of type [java.lang.String] is required"));
exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(map, Component.class, null);
}
@Test
public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception {
// 1) Get an annotation
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
// 2) Convert the annotation into AnnotationAttributes
AnnotationAttributes attributes = getAnnotationAttributes(WebController.class, component);
assertNotNull(attributes);
// 3) Synthesize the AnnotationAttributes back into an annotation
Component synthesizedComponent = synthesizeAnnotation(attributes, Component.class, WebController.class);
assertNotNull(synthesizedComponent);
// 4) Verify that the original and synthesized annotations are equivalent
assertNotSame(component, synthesizedComponent);
assertEquals(component, synthesizedComponent);
assertEquals("value from component: ", "webController", component.value());
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
}
@Test @Test
public void toStringForSynthesizedAnnotations() throws Exception { public void toStringForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute"); Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
@ -670,7 +768,7 @@ public class AnnotationUtilsTests {
assertThat(string, containsString("path=/test")); assertThat(string, containsString("path=/test"));
assertThat(string, containsString("name=bar")); assertThat(string, containsString("name=bar"));
assertThat(string, containsString("method=")); assertThat(string, containsString("method="));
assertThat(string, either(containsString("[GET, POST]")).or(containsString("[POST, GET]"))); assertThat(string, containsString("[GET, POST]"));
assertThat(string, endsWith(")")); assertThat(string, endsWith(")"));
} }
@ -778,7 +876,7 @@ public class AnnotationUtilsTests {
@Test @Test
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception { public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
Hierarchy hierarchy = TestCase.class.getAnnotation(Hierarchy.class); Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
assertNotNull(hierarchy); assertNotNull(hierarchy);
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy); Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
assertNotSame(hierarchy, synthesizedHierarchy); assertNotSame(hierarchy, synthesizedHierarchy);
@ -797,20 +895,44 @@ public class AnnotationUtilsTests {
} }
@Test @Test
public void synthesizeAlreadySynthesizedAnnotation() throws Exception { public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
WebMapping webMapping = method.getAnnotation(WebMapping.class); assertNotNull(hierarchy);
assertNotNull(webMapping); Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping); assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class));
assertNotSame(webMapping, synthesizedWebMapping);
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
assertNotNull(synthesizedAgainWebMapping); ContextConfig contextConfig = SimpleConfigTestCase.class.getAnnotation(ContextConfig.class);
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name()); assertNotNull(contextConfig);
assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path());
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value()); ContextConfig[] configs = synthesizedHierarchy.value();
List<String> locations = Arrays.stream(configs).map(ContextConfig::locations).collect(toList());
assertThat(locations, equalTo(Arrays.asList("A", "B")));
// Alter array returned from synthesized annotation
configs[0] = contextConfig;
// Re-retrieve the array from the synthesized annotation
configs = synthesizedHierarchy.value();
List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(toList());
assertThat(values, equalTo(Arrays.asList("A", "B")));
}
@Test
public void synthesizeAnnotationWithArrayOfChars() throws Exception {
CharsContainer charsContainer = GroupOfCharsClass.class.getAnnotation(CharsContainer.class);
assertNotNull(charsContainer);
CharsContainer synthesizedCharsContainer = synthesizeAnnotation(charsContainer);
assertThat(synthesizedCharsContainer, instanceOf(SynthesizedAnnotation.class));
char[] chars = synthesizedCharsContainer.chars();
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
// Alter array returned from synthesized annotation
chars[0] = '?';
// Re-retrieve the array from the synthesized annotation
chars = synthesizedCharsContainer.chars();
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
} }
@ -1149,9 +1271,28 @@ public class AnnotationUtilsTests {
} }
@Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") }) @Hierarchy({ @ContextConfig("A"), @ContextConfig(locations = "B") })
static class TestCase { static class ConfigHierarchyTestCase {
} }
@ContextConfig("simple.xml")
static class SimpleConfigTestCase {
}
@Retention(RetentionPolicy.RUNTIME)
@interface CharsContainer {
@AliasFor(attribute = "chars")
char[] value() default {};
@AliasFor(attribute = "value")
char[] chars() default {};
}
@CharsContainer(chars = { 'x', 'y', 'z' })
static class GroupOfCharsClass {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface AliasForNonexistentAttribute { @interface AliasForNonexistentAttribute {

View File

@ -19,7 +19,6 @@ package org.springframework.test.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
/** /**
@ -38,13 +37,12 @@ public class TestAnnotationUtils {
* annotated with {@code @Timed} * annotated with {@code @Timed}
*/ */
public static long getTimeout(Method method) { public static long getTimeout(Method method) {
AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(method, Timed.class.getName()); Timed timed = AnnotatedElementUtils.findAnnotation(method, Timed.class);
if (attributes == null) { if (timed == null) {
return 0; return 0;
} }
else { else {
long millis = attributes.<Long> getNumber("millis").longValue(); return Math.max(0, timed.millis());
return Math.max(0, millis);
} }
} }

View File

@ -87,7 +87,7 @@ class MergedSqlConfig {
// Get global attributes, if any. // Get global attributes, if any.
AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(testClass, AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(testClass,
SqlConfig.class.getName()); SqlConfig.class.getName(), false, false);
// Override global attributes with local attributes. // Override global attributes with local attributes.
if (attributes != null) { if (attributes != null) {

View File

@ -32,7 +32,6 @@ import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
@ -115,7 +114,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@SuppressWarnings("unchecked")
@Override @Override
public final List<TestExecutionListener> getTestExecutionListeners() { public final List<TestExecutionListener> getTestExecutionListeners() {
Class<?> clazz = getBootstrapContext().getTestClass(); Class<?> clazz = getBootstrapContext().getTestClass();
@ -139,23 +137,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
// Traverse the class hierarchy... // Traverse the class hierarchy...
while (descriptor != null) { while (descriptor != null) {
Class<?> declaringClass = descriptor.getDeclaringClass(); Class<?> declaringClass = descriptor.getDeclaringClass();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); TestExecutionListeners testExecutionListeners = descriptor.getMergedAnnotation();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format( logger.trace(String.format("Retrieved @TestExecutionListeners [%s] for declaring class [%s].",
"Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, testExecutionListeners, declaringClass.getName()));
declaringClass.getName()));
} }
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners"); boolean inheritListeners = testExecutionListeners.inheritListeners();
boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor( AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
descriptor.getRootDeclaringClass().getSuperclass(), annotationType); descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
// If there are no listeners to inherit, we might need to merge the // If there are no listeners to inherit, we might need to merge the
// locally declared listeners with the defaults. // locally declared listeners with the defaults.
if ((!inheritListeners || superDescriptor == null) if ((!inheritListeners || superDescriptor == null)
&& (annAttrs.getEnum("mergeMode") == MergeMode.MERGE_WITH_DEFAULTS)) { && (testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
"Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].", "Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].",
@ -165,7 +160,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
classesList.addAll(getDefaultTestExecutionListenerClasses()); classesList.addAll(getDefaultTestExecutionListenerClasses());
} }
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses)); classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners()));
descriptor = (inheritListeners ? superDescriptor : null); descriptor = (inheritListeners ? superDescriptor : null);
} }

View File

@ -23,7 +23,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver; import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.test.util.MetaAnnotationUtils;
@ -87,14 +86,14 @@ abstract class ActiveProfilesUtils {
while (descriptor != null) { while (descriptor != null) {
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
Class<?> declaringClass = descriptor.getDeclaringClass(); Class<?> declaringClass = descriptor.getDeclaringClass();
ActiveProfiles annotation = descriptor.getMergedAnnotation();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
annAttrs, declaringClass.getName())); declaringClass.getName()));
} }
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
if (ActiveProfilesResolver.class == resolverClass) { if (ActiveProfilesResolver.class == resolverClass) {
resolverClass = DefaultActiveProfilesResolver.class; resolverClass = DefaultActiveProfilesResolver.class;
} }
@ -125,8 +124,8 @@ abstract class ActiveProfilesUtils {
} }
} }
descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor( descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor(
rootDeclaringClass.getSuperclass(), annotationType) : null; rootDeclaringClass.getSuperclass(), annotationType) : null);
} }
return StringUtils.toStringArray(activeProfiles); return StringUtils.toStringArray(activeProfiles);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,7 +26,6 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.ContextHierarchy;
@ -133,8 +132,9 @@ abstract class ContextLoaderUtils {
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
if (contextConfigDeclaredLocally) { if (contextConfigDeclaredLocally) {
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), convertContextConfigToConfigAttributesAndAddToList(
rootDeclaringClass, configAttributesList); (ContextConfiguration) descriptor.getMergedAnnotation(), rootDeclaringClass,
configAttributesList);
} }
else if (contextHierarchyDeclaredLocally) { else if (contextHierarchyDeclaredLocally) {
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
@ -256,7 +256,7 @@ abstract class ContextLoaderUtils {
annotationType.getName(), testClass.getName())); annotationType.getName(), testClass.getName()));
while (descriptor != null) { while (descriptor != null) {
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), convertContextConfigToConfigAttributesAndAddToList(descriptor.getMergedAnnotation(),
descriptor.getRootDeclaringClass(), attributesList); descriptor.getRootDeclaringClass(), attributesList);
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
} }
@ -284,24 +284,4 @@ abstract class ContextLoaderUtils {
attributesList.add(attributes); attributesList.add(attributes);
} }
/**
* Convenience method for creating a {@link ContextConfigurationAttributes}
* instance from the supplied {@link AnnotationAttributes} and declaring
* class and then adding the attributes to the supplied list.
* @since 4.0
*/
private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs,
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
annAttrs, declaringClass.getName()));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
}
} }

View File

@ -22,9 +22,9 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver; import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -73,14 +73,14 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
} }
else { else {
Class<?> declaringClass = descriptor.getDeclaringClass(); Class<?> declaringClass = descriptor.getDeclaringClass();
ActiveProfiles annotation = descriptor.getMergedAnnotation();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
annAttrs, declaringClass.getName())); declaringClass.getName()));
} }
for (String profile : annAttrs.getStringArray("profiles")) { for (String profile : annotation.profiles()) {
if (StringUtils.hasText(profile)) { if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim()); activeProfiles.add(profile.trim());
} }

View File

@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
@ -150,28 +149,27 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
*/ */
private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode, private void beforeOrAfterTestMethod(TestContext testContext, String phase, MethodMode requiredMethodMode,
ClassMode requiredClassMode) throws Exception { ClassMode requiredClassMode) throws Exception {
Class<?> testClass = testContext.getTestClass(); Class<?> testClass = testContext.getTestClass();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
Method testMethod = testContext.getTestMethod(); Method testMethod = testContext.getTestMethod();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null"); Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
final String annotationType = DirtiesContext.class.getName(); DirtiesContext methodAnn = AnnotatedElementUtils.findAnnotation(testMethod, DirtiesContext.class);
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testMethod, annotationType); DirtiesContext classAnn = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class);
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType); boolean methodAnnotated = (methodAnn != null);
boolean methodAnnotated = methodAnnAttrs != null; boolean classAnnotated = (classAnn != null);
boolean classAnnotated = classAnnAttrs != null; MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null);
MethodMode methodMode = methodAnnotated ? methodAnnAttrs.<MethodMode> getEnum("methodMode") : null; ClassMode classMode = (classAnnotated ? classAnn.classMode() : null);
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("%s test method: context %s, class annotated with @DirtiesContext [%s] "
"%s test method: context %s, class annotated with @DirtiesContext [%s] with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", + "with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", phase, testContext,
phase, testContext, classAnnotated, classMode, methodAnnotated, methodMode)); classAnnotated, classMode, methodAnnotated, methodMode));
} }
if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) { if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) {
HierarchyMode hierarchyMode = methodAnnotated ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode") HierarchyMode hierarchyMode = (methodAnnotated ? methodAnn.hierarchyMode() : classAnn.hierarchyMode());
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
dirtyContext(testContext, hierarchyMode); dirtyContext(testContext, hierarchyMode);
} }
} }
@ -185,10 +183,9 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
Class<?> testClass = testContext.getTestClass(); Class<?> testClass = testContext.getTestClass();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null"); Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
final String annotationType = DirtiesContext.class.getName(); DirtiesContext dirtiesContext = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class);
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType); boolean classAnnotated = (dirtiesContext != null);
boolean classAnnotated = classAnnAttrs != null; ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null);
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
@ -197,8 +194,7 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
} }
if (classMode == requiredClassMode) { if (classMode == requiredClassMode) {
HierarchyMode hierarchyMode = classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode"); dirtyContext(testContext, dirtiesContext.hierarchyMode());
dirtyContext(testContext, hierarchyMode);
} }
} }

View File

@ -19,7 +19,6 @@ package org.springframework.test.context.support;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@ -58,17 +57,17 @@ class TestPropertySourceAttributes {
/** /**
* Create a new {@code TestPropertySourceAttributes} instance for the * Create a new {@code TestPropertySourceAttributes} instance for the
* supplied {@link AnnotationAttributes} (parsed from a * supplied {@link TestPropertySource @TestPropertySource} annotation and
* {@link TestPropertySource @TestPropertySource} annotation) and * the {@linkplain Class test class} that declared it, enforcing
* the {@linkplain Class test class} that declared them, enforcing
* configuration rules and detecting a default properties file if * configuration rules and detecting a default properties file if
* necessary. * necessary.
* @param declaringClass the class that declared {@code @TestPropertySource} * @param declaringClass the class that declared {@code @TestPropertySource}
* @param annAttrs the annotation attributes from which to retrieve the attributes * @param testPropertySource the annotation from which to retrieve the attributes
* @since 4.2
*/ */
TestPropertySourceAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) { TestPropertySourceAttributes(Class<?> declaringClass, TestPropertySource testPropertySource) {
this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getBoolean("inheritLocations"), this(declaringClass, testPropertySource.locations(), testPropertySource.inheritLocations(),
annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties")); testPropertySource.properties(), testPropertySource.inheritProperties());
} }
private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations, private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations,

View File

@ -29,7 +29,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
@ -39,6 +38,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils; import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -96,15 +96,16 @@ public abstract class TestPropertySourceUtils {
annotationType.getName(), testClass.getName())); annotationType.getName(), testClass.getName()));
while (descriptor != null) { while (descriptor != null) {
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); TestPropertySource testPropertySource = descriptor.getMergedAnnotation();
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @TestPropertySource attributes [%s] for declaring class [%s].", logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].",
annAttrs, rootDeclaringClass.getName())); testPropertySource, rootDeclaringClass.getName()));
} }
TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, annAttrs); TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass,
testPropertySource);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Resolved TestPropertySource attributes: " + attributes); logger.trace("Resolved TestPropertySource attributes: " + attributes);
} }

View File

@ -30,7 +30,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.annotation.Rollback; import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener;
@ -500,18 +499,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (this.configurationAttributes == null) { if (this.configurationAttributes == null) {
Class<?> clazz = testContext.getTestClass(); Class<?> clazz = testContext.getTestClass();
AnnotationAttributes annAttrs = AnnotatedElementUtils.findAnnotationAttributes(clazz, TransactionConfiguration txConfig = AnnotatedElementUtils.findAnnotation(clazz,
TransactionConfiguration.class.getName()); TransactionConfiguration.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved @TransactionConfiguration attributes [%s] for test class [%s].", logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].",
annAttrs, clazz)); txConfig, clazz));
} }
String transactionManagerName; String transactionManagerName;
boolean defaultRollback; boolean defaultRollback;
if (annAttrs != null) { if (txConfig != null) {
transactionManagerName = annAttrs.getString("transactionManager"); transactionManagerName = txConfig.transactionManager();
defaultRollback = annAttrs.getBoolean("defaultRollback"); defaultRollback = txConfig.defaultRollback();
} }
else { else {
transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME; transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME;

View File

@ -272,12 +272,16 @@ public abstract class MetaAnnotationUtils {
private final T annotation; private final T annotation;
private final T mergedAnnotation;
private final AnnotationAttributes annotationAttributes; private final AnnotationAttributes annotationAttributes;
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) { public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
this(rootDeclaringClass, rootDeclaringClass, null, annotation); this(rootDeclaringClass, rootDeclaringClass, null, annotation);
} }
@SuppressWarnings("unchecked")
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass, public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
Annotation composedAnnotation, T annotation) { Annotation composedAnnotation, T annotation) {
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null"); Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
@ -286,8 +290,10 @@ public abstract class MetaAnnotationUtils {
this.declaringClass = declaringClass; this.declaringClass = declaringClass;
this.composedAnnotation = composedAnnotation; this.composedAnnotation = composedAnnotation;
this.annotation = annotation; this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes( this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(rootDeclaringClass,
rootDeclaringClass, annotation.annotationType()); annotation.annotationType().getName(), false, false);
this.mergedAnnotation = AnnotationUtils.synthesizeAnnotation(annotationAttributes,
(Class<T>) annotation.annotationType(), rootDeclaringClass);
} }
public Class<?> getRootDeclaringClass() { public Class<?> getRootDeclaringClass() {
@ -302,6 +308,16 @@ public abstract class MetaAnnotationUtils {
return this.annotation; return this.annotation;
} }
/**
* Get the annotation that was synthesized from the merged
* {@link #getAnnotationAttributes AnnotationAttributes}.
* @see #getAnnotationAttributes()
* @see AnnotationUtils#synthesizeAnnotation(java.util.Map, Class, java.lang.reflect.AnnotatedElement)
*/
public T getMergedAnnotation() {
return this.mergedAnnotation;
}
public Class<? extends Annotation> getAnnotationType() { public Class<? extends Annotation> getAnnotationType() {
return this.annotation.annotationType(); return this.annotation.annotationType();
} }

View File

@ -79,7 +79,7 @@ public class SqlScriptsTestExecutionListenerTests {
containsString("attribute [scripts] and its alias [value]"))); containsString("attribute [scripts] and its alias [value]")));
exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or( exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or(
containsString("values of [{bar}] and [{foo}]"))); containsString("values of [{bar}] and [{foo}]")));
exception.expectMessage(containsString("but only one declaration is permitted")); exception.expectMessage(containsString("but only one is permitted"));
listener.beforeTestMethod(testContext); listener.beforeTestMethod(testContext);
} }

View File

@ -401,7 +401,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
Builder mappingName(String name); Builder mappingName(String name);
/** /**
* Set a custom conditions to use. * Set a custom condition to use.
*/ */
Builder customCondition(RequestCondition<?> condition); Builder customCondition(RequestCondition<?> condition);

View File

@ -26,6 +26,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -42,7 +43,6 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.objenesis.Objenesis; import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.SpringObjenesis; import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
@ -418,13 +418,11 @@ public class MvcUriComponentsBuilder {
private static String getTypeRequestMapping(Class<?> controllerType) { private static String getTypeRequestMapping(Class<?> controllerType) {
Assert.notNull(controllerType, "'controllerType' must not be null"); Assert.notNull(controllerType, "'controllerType' must not be null");
String annotType = RequestMapping.class.getName(); RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(controllerType, RequestMapping.class);
AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(controllerType, annotType); if (requestMapping == null) {
if (attrs == null) {
return "/"; return "/";
} }
String[] paths = attrs.getStringArray("path"); String[] paths = requestMapping.path();
paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths;
if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) { if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
return "/"; return "/";
} }
@ -435,13 +433,11 @@ public class MvcUriComponentsBuilder {
} }
private static String getMethodRequestMapping(Method method) { private static String getMethodRequestMapping(Method method) {
String annotType = RequestMapping.class.getName(); RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(method, RequestMapping.class);
AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(method, annotType); if (requestMapping == null) {
if (attrs == null) {
throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString()); throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString());
} }
String[] paths = attrs.getStringArray("path"); String[] paths = requestMapping.path();
paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths;
if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) { if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
return "/"; return "/";
} }
@ -759,7 +755,6 @@ public class MvcUriComponentsBuilder {
* that accept the controllerType. * that accept the controllerType.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("unused")
public MethodArgumentBuilder(Method method) { public MethodArgumentBuilder(Method method) {
this(method.getDeclaringClass(), method); this(method.getDeclaringClass(), method);
} }

View File

@ -22,12 +22,10 @@ import java.util.List;
import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringValueResolver; import org.springframework.util.StringValueResolver;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
@ -49,6 +47,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Sam Brannen
* @since 3.1 * @since 3.1
*/ */
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
@ -212,7 +211,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @param handlerType the handler type for which to create the condition * @param handlerType the handler type for which to create the condition
* @return the condition, or {@code null} * @return the condition, or {@code null}
*/ */
@SuppressWarnings("unused")
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return null; return null;
} }
@ -228,77 +226,41 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @param method the handler method for which to create the condition * @param method the handler method for which to create the condition
* @return the condition, or {@code null} * @return the condition, or {@code null}
*/ */
@SuppressWarnings("unused")
protected RequestCondition<?> getCustomMethodCondition(Method method) { protected RequestCondition<?> getCustomMethodCondition(Method method) {
return null; return null;
} }
/** /**
* Transitional method used to invoke one of two createRequestMappingInfo * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* variants one of which is deprecated. * supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
*
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/ */
@SuppressWarnings("deprecation")
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement annotatedElement) { private RequestMappingInfo createRequestMappingInfo(AnnotatedElement annotatedElement) {
RequestMapping annotation; RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(annotatedElement, RequestMapping.class);
AnnotationAttributes attributes; RequestCondition<?> customCondition = ((annotatedElement instanceof Class<?>) ? getCustomTypeCondition((Class<?>) annotatedElement)
RequestCondition<?> customCondition; : getCustomMethodCondition((Method) annotatedElement));
String annotationType = RequestMapping.class.getName(); return ((requestMapping != null) ? createRequestMappingInfo(requestMapping, customCondition) : null);
if (annotatedElement instanceof Class<?>) {
Class<?> type = (Class<?>) annotatedElement;
annotation = AnnotationUtils.findAnnotation(type, RequestMapping.class);
attributes = AnnotatedElementUtils.findAnnotationAttributes(type, annotationType);
customCondition = getCustomTypeCondition(type);
}
else {
Method method = (Method) annotatedElement;
annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
attributes = AnnotatedElementUtils.findAnnotationAttributes(method, annotationType);
customCondition = getCustomMethodCondition(method);
}
RequestMappingInfo info = null;
if (annotation != null) {
info = createRequestMappingInfo(annotation, customCondition);
if (info == null) {
info = createRequestMappingInfo(attributes, customCondition);
}
}
return info;
} }
/** /**
* Create a RequestMappingInfo from a RequestMapping annotation. * Create a {@link RequestMappingInfo} from the supplied
* @deprecated as of 4.2 after the introduction of support for * {@link RequestMapping @RequestMapping} annotation, which is either
* {@code @RequestMapping} as meta-annotation. Please use * a directly declared annotation, a meta-annotation, or the synthesized
* {@link #createRequestMappingInfo(AnnotationAttributes, RequestCondition)}. * result of merging annotation attributes within an annotation hierarchy.
*/ */
@Deprecated protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping,
@SuppressWarnings("unused")
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation,
RequestCondition<?> customCondition) { RequestCondition<?> customCondition) {
return null; return RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
} .methods(requestMapping.method())
.params(requestMapping.params())
/** .headers(requestMapping.headers())
* Create a RequestMappingInfo from the attributes of an .consumes(requestMapping.consumes())
* {@code @RequestMapping} annotation or a meta-annotation, i.e. a custom .produces(requestMapping.produces())
* annotation annotated with {@code @RequestMapping}. .mappingName(requestMapping.name())
* @since 4.2
*/
protected RequestMappingInfo createRequestMappingInfo(AnnotationAttributes attributes,
RequestCondition<?> customCondition) {
String[] paths = attributes.getStringArray("path");
paths = ObjectUtils.isEmpty(paths) ? attributes.getStringArray("value") : paths;
paths = resolveEmbeddedValuesInPatterns(paths);
return RequestMappingInfo.paths(paths)
.methods((RequestMethod[]) attributes.get("method"))
.params(attributes.getStringArray("params"))
.headers(attributes.getStringArray("headers"))
.consumes(attributes.getStringArray("consumes"))
.produces(attributes.getStringArray("produces"))
.mappingName(attributes.getString("name"))
.customCondition(customCondition) .customCondition(customCondition)
.options(this.config) .options(this.config)
.build(); .build();