Merge from sbrannen/SPR-13067
* SPR-13067: Synthesize annotation from a map of attributes
This commit is contained in:
commit
a31d1bdf60
|
@ -32,7 +32,6 @@ import org.springframework.context.expression.AnnotatedElementKey;
|
|||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
|
@ -248,11 +247,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
|||
*/
|
||||
protected String getCondition() {
|
||||
if (this.condition == null) {
|
||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||
.findAnnotationAttributes(this.method, EventListener.class);
|
||||
if (annotationAttributes != null) {
|
||||
String value = annotationAttributes.getString("condition");
|
||||
this.condition = (value != null ? value : "");
|
||||
EventListener eventListener = AnnotatedElementUtils.findAnnotation(this.method, EventListener.class);
|
||||
if (eventListener != null) {
|
||||
this.condition = eventListener.condition();
|
||||
}
|
||||
}
|
||||
return this.condition;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -48,8 +48,8 @@ import org.springframework.util.StringUtils;
|
|||
* <h3>Annotation Attribute Overrides</h3>
|
||||
* <p>Support for meta-annotations with <em>attribute overrides</em> in
|
||||
* <em>composed annotations</em> is provided by all variants of the
|
||||
* {@code getAnnotationAttributes()} and {@code findAnnotationAttributes()}
|
||||
* methods.
|
||||
* {@code getAnnotationAttributes()}, {@code findAnnotation()}, and
|
||||
* {@code findAnnotationAttributes()} methods.
|
||||
*
|
||||
* <h3>Find vs. Get Semantics</h3>
|
||||
* <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
|
||||
* 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)},
|
||||
* 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
|
||||
* not found
|
||||
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, Class)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
||||
* @see #findAnnotation(AnnotatedElement, Class)
|
||||
* @see #getAllAnnotationAttributes(AnnotatedElement, String)
|
||||
*/
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
|
||||
|
@ -248,7 +251,9 @@ public class AnnotatedElementUtils {
|
|||
* annotations in lower levels of the annotation hierarchy.
|
||||
*
|
||||
* <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
|
||||
* algorithm used by this method will stop searching the annotation
|
||||
|
@ -269,8 +274,7 @@ public class AnnotatedElementUtils {
|
|||
* as Annotation instances
|
||||
* @return the merged {@code AnnotationAttributes}, or {@code null} if
|
||||
* not found
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, Class)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String)
|
||||
* @see #findAnnotation(AnnotatedElement, Class)
|
||||
* @see #findAnnotationAttributes(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
|
||||
* 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
|
||||
* 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)}
|
||||
* 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 annotationType the annotation type to find; never {@code null}
|
||||
* @return the merged {@code AnnotationAttributes}, or {@code null} if
|
||||
* not found
|
||||
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found
|
||||
* @since 4.2
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String)
|
||||
* @see #findAnnotation(AnnotatedElement, String)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
||||
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
|
||||
*/
|
||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element,
|
||||
Class<? extends Annotation> annotationType) {
|
||||
public static <A extends Annotation> A findAnnotation(AnnotatedElement element, Class<A> annotationType) {
|
||||
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
|
||||
* 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
|
||||
* 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)}
|
||||
* 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 annotationType the fully qualified class name of the annotation
|
||||
* type to find; never {@code null} or empty
|
||||
* @return the merged {@code AnnotationAttributes}, or {@code null} if
|
||||
* not found
|
||||
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found
|
||||
* @since 4.2
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, Class)
|
||||
* @see #findAnnotation(AnnotatedElement, Class)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
||||
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
|
||||
*/
|
||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) {
|
||||
return findAnnotationAttributes(element, annotationType, false, false);
|
||||
@SuppressWarnings("unchecked")
|
||||
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.
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
* not found
|
||||
* @since 4.2
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, Class)
|
||||
* @see #findAnnotationAttributes(AnnotatedElement, String)
|
||||
* @see #findAnnotation(AnnotatedElement, Class)
|
||||
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
||||
*/
|
||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -809,8 +809,10 @@ public abstract class AnnotationUtils {
|
|||
* merging attributes within an annotation hierarchy. When running in <em>merge mode</em>,
|
||||
* the following special rules apply:
|
||||
* <ol>
|
||||
* <li>The supplied annotation will <strong>not</strong> be
|
||||
* {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes.</li>
|
||||
* <li>The supplied annotation will <em>not</em> be
|
||||
* {@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>The resulting, merged annotation attributes should eventually be
|
||||
* {@linkplain #postProcessAnnotationAttributes post-processed} in order to
|
||||
|
@ -1051,12 +1053,13 @@ public abstract class AnnotationUtils {
|
|||
* @param annotation the annotation to synthesize
|
||||
* @param annotatedElement the element that is annotated with the supplied
|
||||
* annotation; may be {@code null} if unknown
|
||||
* @return the synthesized annotation, if the supplied annotation is
|
||||
* @return the synthesized annotation if the supplied annotation is
|
||||
* <em>synthesizable</em>; {@code null} if the supplied annotation is
|
||||
* {@code null}; otherwise, the supplied annotation unmodified
|
||||
* {@code null}; otherwise the supplied annotation unmodified
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @since 4.2
|
||||
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
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();
|
||||
|
||||
// No need to synthesize?
|
||||
if (!isSynthesizable(annotationType)) {
|
||||
return annotation;
|
||||
}
|
||||
|
||||
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(annotation, annotatedElement,
|
||||
getAttributeAliasMap(annotationType));
|
||||
AnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation,
|
||||
annotatedElement);
|
||||
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
|
||||
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
|
||||
(Class<A>) annotationType, SynthesizedAnnotation.class }, handler);
|
||||
|
||||
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}
|
||||
|
@ -1098,7 +1139,7 @@ public abstract class AnnotationUtils {
|
|||
* @return a map containing attribute alias pairs; never {@code null}
|
||||
* @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) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
@ -1334,7 +1375,7 @@ public abstract class AnnotationUtils {
|
|||
|
||||
methods = new ArrayList<Method>();
|
||||
for (Method method : annotationType.getDeclaredMethods()) {
|
||||
if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) {
|
||||
if (isAttributeMethod(method)) {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
methods.add(method);
|
||||
}
|
||||
|
@ -1345,6 +1386,24 @@ public abstract class AnnotationUtils {
|
|||
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}.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.springframework.core.annotation;
|
|||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
@ -25,6 +26,7 @@ import java.util.Iterator;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
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
|
||||
* 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
|
||||
* @since 4.2
|
||||
* @see Annotation
|
||||
* @see AliasFor
|
||||
* @see AnnotationAttributeExtractor
|
||||
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
|
||||
*/
|
||||
class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final AnnotatedElement annotatedElement;
|
||||
private final AnnotationAttributeExtractor attributeExtractor;
|
||||
|
||||
private final Annotation annotation;
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
|
||||
private final Map<String, String> aliasMap;
|
||||
|
||||
private final Map<String, Object> computedValueCache;
|
||||
private final Map<String, Object> valueCache = new ConcurrentHashMap<String, Object>(8);
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code SynthesizedAnnotationInvocationHandler}.
|
||||
*
|
||||
* @param annotation the annotation to synthesize
|
||||
* @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
|
||||
* Construct a new {@code SynthesizedAnnotationInvocationHandler} for
|
||||
* the supplied {@link AnnotationAttributeExtractor}.
|
||||
* @param attributeExtractor the extractor to delegate to
|
||||
*/
|
||||
SynthesizedAnnotationInvocationHandler(Annotation annotation, AnnotatedElement annotatedElement,
|
||||
Map<String, String> aliasMap) {
|
||||
this.annotatedElement = annotatedElement;
|
||||
this.annotation = annotation;
|
||||
this.annotationType = annotation.annotationType();
|
||||
this.aliasMap = aliasMap;
|
||||
this.computedValueCache = new ConcurrentHashMap<String, Object>(aliasMap.size());
|
||||
SynthesizedAnnotationInvocationHandler(AnnotationAttributeExtractor attributeExtractor) {
|
||||
Assert.notNull(attributeExtractor, "AnnotationAttributeExtractor must not be null");
|
||||
this.attributeExtractor = attributeExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (isEqualsMethod(method)) {
|
||||
return equals(proxy, args[0]);
|
||||
return annotationEquals(args[0]);
|
||||
}
|
||||
if (isHashCodeMethod(method)) {
|
||||
return hashCode(proxy);
|
||||
return annotationHashCode();
|
||||
}
|
||||
if (isToStringMethod(method)) {
|
||||
return toString(proxy);
|
||||
return annotationToString();
|
||||
}
|
||||
|
||||
String methodName = method.getName();
|
||||
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 (isAnnotationTypeMethod(method)) {
|
||||
return annotationType();
|
||||
}
|
||||
|
||||
Object cachedValue = this.computedValueCache.get(methodName);
|
||||
if (cachedValue != null) {
|
||||
return cachedValue;
|
||||
if (!isAttributeMethod(method)) {
|
||||
String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]", method,
|
||||
annotationType());
|
||||
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) {
|
||||
Method aliasedMethod = null;
|
||||
try {
|
||||
aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], "
|
||||
+ "but attribute [%s] does not exist.", this.annotationType.getName(), methodName,
|
||||
aliasedAttributeName, aliasedAttributeName);
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
private Object getAttributeValue(Method attributeMethod) {
|
||||
String attributeName = attributeMethod.getName();
|
||||
Object value = this.valueCache.get(attributeName);
|
||||
if (value == null) {
|
||||
value = this.attributeExtractor.getAttributeValue(attributeMethod);
|
||||
if (value == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"%s returned null for attribute name [%s] from attribute source [%s]",
|
||||
this.attributeExtractor.getClass().getName(), attributeName, this.attributeExtractor.getSource()));
|
||||
}
|
||||
|
||||
makeAccessible(aliasedMethod);
|
||||
Object aliasedValue = invokeMethod(aliasedMethod, this.annotation);
|
||||
Object defaultValue = getDefaultValue(this.annotation, methodName);
|
||||
|
||||
if (!nullSafeEquals(value, aliasedValue) && !nullSafeEquals(value, defaultValue)
|
||||
&& !nullSafeEquals(aliasedValue, defaultValue)) {
|
||||
String elementName = (this.annotatedElement == null ? "unknown element"
|
||||
: this.annotatedElement.toString());
|
||||
String msg = String.format(
|
||||
"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.",
|
||||
this.annotationType.getName(), elementName, methodName, aliasedAttributeName,
|
||||
nullSafeToString(value), nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
// Synthesize nested annotations before returning them.
|
||||
if (value instanceof Annotation) {
|
||||
value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
|
||||
}
|
||||
else if (value instanceof Annotation[]) {
|
||||
Annotation[] orig = (Annotation[]) value;
|
||||
Annotation[] clone = (Annotation[]) Array.newInstance(orig.getClass().getComponentType(), orig.length);
|
||||
for (int i = 0; i < orig.length; i++) {
|
||||
clone[i] = synthesizeAnnotation(orig[i], this.attributeExtractor.getAnnotatedElement());
|
||||
}
|
||||
value = clone;
|
||||
}
|
||||
|
||||
// If the user didn't declare the annotation with an explicit value, return
|
||||
// the value of the alias.
|
||||
if (nullSafeEquals(value, defaultValue)) {
|
||||
value = aliasedValue;
|
||||
}
|
||||
this.valueCache.put(attributeName, value);
|
||||
}
|
||||
|
||||
// Synthesize nested annotations before returning them.
|
||||
if (value instanceof Annotation) {
|
||||
value = synthesizeAnnotation((Annotation) value, this.annotatedElement);
|
||||
// Clone arrays so that users cannot alter the contents of values in our cache.
|
||||
if (value.getClass().isArray()) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param proxy the synthesized annotation
|
||||
* @param other the other object to compare against
|
||||
*/
|
||||
private boolean equals(Object proxy, Object other) {
|
||||
private boolean annotationEquals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!this.annotationType.isInstance(other)) {
|
||||
if (!annotationType().isInstance(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Method attributeMethod : getAttributeMethods(this.annotationType)) {
|
||||
Object thisValue = invokeMethod(attributeMethod, proxy);
|
||||
for (Method attributeMethod : getAttributeMethods(annotationType())) {
|
||||
Object thisValue = getAttributeValue(attributeMethod);
|
||||
Object otherValue = invokeMethod(attributeMethod, other);
|
||||
if (!nullSafeEquals(thisValue, otherValue)) {
|
||||
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -189,14 +182,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
for (Method attributeMethod : getAttributeMethods(this.annotationType)) {
|
||||
Object value = invokeMethod(attributeMethod, proxy);
|
||||
for (Method attributeMethod : getAttributeMethods(annotationType())) {
|
||||
Object value = getAttributeValue(attributeMethod);
|
||||
int hashCode;
|
||||
if (value.getClass().isArray()) {
|
||||
hashCode = hashCodeForArray(value);
|
||||
|
@ -250,39 +241,27 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||
|
||||
/**
|
||||
* See {@link Annotation#toString()} for guidelines on the recommended format.
|
||||
*
|
||||
* @param proxy the synthesized annotation
|
||||
*/
|
||||
private String toString(Object proxy) {
|
||||
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
|
||||
private String annotationToString() {
|
||||
StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
|
||||
|
||||
Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator();
|
||||
Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Method attributeMethod = iterator.next();
|
||||
sb.append(attributeMethod.getName());
|
||||
sb.append('=');
|
||||
sb.append(valueToString(invokeMethod(attributeMethod, proxy)));
|
||||
sb.append(attributeValueToString(getAttributeValue(attributeMethod)));
|
||||
sb.append(iterator.hasNext() ? ", " : "");
|
||||
}
|
||||
|
||||
return sb.append(")").toString();
|
||||
}
|
||||
|
||||
private String valueToString(Object value) {
|
||||
private String attributeValueToString(Object value) {
|
||||
if (value instanceof Object[]) {
|
||||
return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]";
|
||||
}
|
||||
|
||||
// else
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -30,6 +32,7 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
|
@ -450,14 +453,33 @@ public class AnnotatedElementUtilsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
|
||||
public void findAndSynthesizeAnnotationAttributesOnClassWithAttributeAliasesInTargetAnnotation() {
|
||||
String qualifier = "aliasForQualifier";
|
||||
|
||||
// 1) Find and merge AnnotationAttributes from the annotation hierarchy
|
||||
AnnotationAttributes attributes = findAnnotationAttributes(AliasedTransactionalComponentClass.class,
|
||||
AliasedTransactional.class);
|
||||
assertNotNull("Should find @AliasedTransactional on AliasedTransactionalComponentClass", attributes);
|
||||
assertEquals("TX value for AliasedTransactionalComponentClass.", "aliasForQualifier",
|
||||
attributes.getString("value"));
|
||||
assertEquals("TX qualifier for AliasedTransactionalComponentClass.", "aliasForQualifier",
|
||||
attributes.getString("qualifier"));
|
||||
assertNotNull("@AliasedTransactional on AliasedTransactionalComponentClass.", attributes);
|
||||
|
||||
// 2) Synthesize the AnnotationAttributes back into the target annotation
|
||||
AliasedTransactional annotation = AnnotationUtils.synthesizeAnnotation(attributes,
|
||||
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
|
||||
|
@ -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
|
||||
|
|
|
@ -24,7 +24,9 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Rule;
|
||||
|
@ -436,7 +438,7 @@ public class AnnotationUtilsTests {
|
|||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("attribute [value] and its alias [path]"));
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -504,7 +506,8 @@ public class AnnotationUtilsTests {
|
|||
|
||||
@Test
|
||||
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);
|
||||
|
||||
List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList());
|
||||
|
@ -522,7 +525,7 @@ public class AnnotationUtilsTests {
|
|||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
|
||||
Component component = findAnnotation(WebController.class, Component.class);
|
||||
Component component = WebController.class.getAnnotation(Component.class);
|
||||
assertNotNull(component);
|
||||
Component synthesizedComponent = synthesizeAnnotation(component);
|
||||
assertNotNull(synthesizedComponent);
|
||||
|
@ -530,6 +533,29 @@ public class AnnotationUtilsTests {
|
|||
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
|
||||
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
|
||||
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
|
||||
|
@ -638,6 +664,78 @@ public class AnnotationUtilsTests {
|
|||
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
|
||||
public void toStringForSynthesizedAnnotations() throws Exception {
|
||||
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
|
||||
|
@ -670,7 +768,7 @@ public class AnnotationUtilsTests {
|
|||
assertThat(string, containsString("path=/test"));
|
||||
assertThat(string, containsString("name=bar"));
|
||||
assertThat(string, containsString("method="));
|
||||
assertThat(string, either(containsString("[GET, POST]")).or(containsString("[POST, GET]")));
|
||||
assertThat(string, containsString("[GET, POST]"));
|
||||
assertThat(string, endsWith(")"));
|
||||
}
|
||||
|
||||
|
@ -778,7 +876,7 @@ public class AnnotationUtilsTests {
|
|||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
|
||||
Hierarchy hierarchy = TestCase.class.getAnnotation(Hierarchy.class);
|
||||
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
|
||||
assertNotNull(hierarchy);
|
||||
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
|
||||
assertNotSame(hierarchy, synthesizedHierarchy);
|
||||
|
@ -797,20 +895,44 @@ public class AnnotationUtilsTests {
|
|||
}
|
||||
|
||||
@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));
|
||||
public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception {
|
||||
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
|
||||
assertNotNull(hierarchy);
|
||||
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
|
||||
assertThat(synthesizedHierarchy, 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());
|
||||
ContextConfig contextConfig = SimpleConfigTestCase.class.getAnnotation(ContextConfig.class);
|
||||
assertNotNull(contextConfig);
|
||||
|
||||
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") })
|
||||
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)
|
||||
@interface AliasForNonexistentAttribute {
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.springframework.test.annotation;
|
|||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
|
||||
/**
|
||||
|
@ -38,13 +37,12 @@ public class TestAnnotationUtils {
|
|||
* annotated with {@code @Timed}
|
||||
*/
|
||||
public static long getTimeout(Method method) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(method, Timed.class.getName());
|
||||
if (attributes == null) {
|
||||
Timed timed = AnnotatedElementUtils.findAnnotation(method, Timed.class);
|
||||
if (timed == null) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
long millis = attributes.<Long> getNumber("millis").longValue();
|
||||
return Math.max(0, millis);
|
||||
return Math.max(0, timed.millis());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ class MergedSqlConfig {
|
|||
|
||||
// Get global attributes, if any.
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils.findAnnotationAttributes(testClass,
|
||||
SqlConfig.class.getName());
|
||||
SqlConfig.class.getName(), false, false);
|
||||
|
||||
// Override global attributes with local attributes.
|
||||
if (attributes != null) {
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.springframework.beans.BeanInstantiationException;
|
|||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
|
@ -115,7 +114,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public final List<TestExecutionListener> getTestExecutionListeners() {
|
||||
Class<?> clazz = getBootstrapContext().getTestClass();
|
||||
|
@ -139,23 +137,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
|||
// Traverse the class hierarchy...
|
||||
while (descriptor != null) {
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
|
||||
TestExecutionListeners testExecutionListeners = descriptor.getMergedAnnotation();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format(
|
||||
"Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs,
|
||||
declaringClass.getName()));
|
||||
logger.trace(String.format("Retrieved @TestExecutionListeners [%s] for declaring class [%s].",
|
||||
testExecutionListeners, declaringClass.getName()));
|
||||
}
|
||||
|
||||
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
|
||||
|
||||
boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
|
||||
boolean inheritListeners = testExecutionListeners.inheritListeners();
|
||||
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
|
||||
descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
|
||||
|
||||
// If there are no listeners to inherit, we might need to merge the
|
||||
// locally declared listeners with the defaults.
|
||||
if ((!inheritListeners || superDescriptor == null)
|
||||
&& (annAttrs.getEnum("mergeMode") == MergeMode.MERGE_WITH_DEFAULTS)) {
|
||||
&& (testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"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(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
|
||||
classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners()));
|
||||
|
||||
descriptor = (inheritListeners ? superDescriptor : null);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.apache.commons.logging.Log;
|
|||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ActiveProfilesResolver;
|
||||
import org.springframework.test.util.MetaAnnotationUtils;
|
||||
|
@ -87,14 +86,14 @@ abstract class ActiveProfilesUtils {
|
|||
while (descriptor != null) {
|
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
ActiveProfiles annotation = descriptor.getMergedAnnotation();
|
||||
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
|
||||
annAttrs, declaringClass.getName()));
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
|
||||
declaringClass.getName()));
|
||||
}
|
||||
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
|
||||
if (ActiveProfilesResolver.class == resolverClass) {
|
||||
resolverClass = DefaultActiveProfilesResolver.class;
|
||||
}
|
||||
|
@ -125,8 +124,8 @@ abstract class ActiveProfilesUtils {
|
|||
}
|
||||
}
|
||||
|
||||
descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor(
|
||||
rootDeclaringClass.getSuperclass(), annotationType) : null;
|
||||
descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor(
|
||||
rootDeclaringClass.getSuperclass(), annotationType) : null);
|
||||
}
|
||||
|
||||
return StringUtils.toStringArray(activeProfiles);
|
||||
|
|
|
@ -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");
|
||||
* 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.LogFactory;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
import org.springframework.test.context.ContextHierarchy;
|
||||
|
@ -133,8 +132,9 @@ abstract class ContextLoaderUtils {
|
|||
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
|
||||
|
||||
if (contextConfigDeclaredLocally) {
|
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
|
||||
rootDeclaringClass, configAttributesList);
|
||||
convertContextConfigToConfigAttributesAndAddToList(
|
||||
(ContextConfiguration) descriptor.getMergedAnnotation(), rootDeclaringClass,
|
||||
configAttributesList);
|
||||
}
|
||||
else if (contextHierarchyDeclaredLocally) {
|
||||
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
|
||||
|
@ -256,7 +256,7 @@ abstract class ContextLoaderUtils {
|
|||
annotationType.getName(), testClass.getName()));
|
||||
|
||||
while (descriptor != null) {
|
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
|
||||
convertContextConfigToConfigAttributesAndAddToList(descriptor.getMergedAnnotation(),
|
||||
descriptor.getRootDeclaringClass(), attributesList);
|
||||
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
|
||||
}
|
||||
|
@ -284,24 +284,4 @@ abstract class ContextLoaderUtils {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ import java.util.Set;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ActiveProfilesResolver;
|
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
@ -73,14 +73,14 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
|
|||
}
|
||||
else {
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
ActiveProfiles annotation = descriptor.getMergedAnnotation();
|
||||
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
|
||||
annAttrs, declaringClass.getName()));
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
|
||||
declaringClass.getName()));
|
||||
}
|
||||
|
||||
for (String profile : annAttrs.getStringArray("profiles")) {
|
||||
for (String profile : annotation.profiles()) {
|
||||
if (StringUtils.hasText(profile)) {
|
||||
activeProfiles.add(profile.trim());
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory;
|
|||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
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,
|
||||
ClassMode requiredClassMode) throws Exception {
|
||||
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
|
||||
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");
|
||||
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes methodAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testMethod, annotationType);
|
||||
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType);
|
||||
boolean methodAnnotated = methodAnnAttrs != null;
|
||||
boolean classAnnotated = classAnnAttrs != null;
|
||||
MethodMode methodMode = methodAnnotated ? methodAnnAttrs.<MethodMode> getEnum("methodMode") : null;
|
||||
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
|
||||
DirtiesContext methodAnn = AnnotatedElementUtils.findAnnotation(testMethod, DirtiesContext.class);
|
||||
DirtiesContext classAnn = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class);
|
||||
boolean methodAnnotated = (methodAnn != null);
|
||||
boolean classAnnotated = (classAnn != null);
|
||||
MethodMode methodMode = (methodAnnotated ? methodAnn.methodMode() : null);
|
||||
ClassMode classMode = (classAnnotated ? classAnn.classMode() : null);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"%s test method: context %s, class annotated with @DirtiesContext [%s] with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].",
|
||||
phase, testContext, classAnnotated, classMode, methodAnnotated, methodMode));
|
||||
logger.debug(String.format("%s test method: context %s, class annotated with @DirtiesContext [%s] "
|
||||
+ "with mode [%s], method annotated with @DirtiesContext [%s] with mode [%s].", phase, testContext,
|
||||
classAnnotated, classMode, methodAnnotated, methodMode));
|
||||
}
|
||||
|
||||
if ((methodMode == requiredMethodMode) || (classMode == requiredClassMode)) {
|
||||
HierarchyMode hierarchyMode = methodAnnotated ? methodAnnAttrs.<HierarchyMode> getEnum("hierarchyMode")
|
||||
: classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
HierarchyMode hierarchyMode = (methodAnnotated ? methodAnn.hierarchyMode() : classAnn.hierarchyMode());
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
}
|
||||
}
|
||||
|
@ -185,10 +183,9 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
|||
Class<?> testClass = testContext.getTestClass();
|
||||
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
|
||||
|
||||
final String annotationType = DirtiesContext.class.getName();
|
||||
AnnotationAttributes classAnnAttrs = AnnotatedElementUtils.findAnnotationAttributes(testClass, annotationType);
|
||||
boolean classAnnotated = classAnnAttrs != null;
|
||||
ClassMode classMode = classAnnotated ? classAnnAttrs.<ClassMode> getEnum("classMode") : null;
|
||||
DirtiesContext dirtiesContext = AnnotatedElementUtils.findAnnotation(testClass, DirtiesContext.class);
|
||||
boolean classAnnotated = (dirtiesContext != null);
|
||||
ClassMode classMode = (classAnnotated ? dirtiesContext.classMode() : null);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
|
@ -197,8 +194,7 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
|
|||
}
|
||||
|
||||
if (classMode == requiredClassMode) {
|
||||
HierarchyMode hierarchyMode = classAnnAttrs.<HierarchyMode> getEnum("hierarchyMode");
|
||||
dirtyContext(testContext, hierarchyMode);
|
||||
dirtyContext(testContext, dirtiesContext.hierarchyMode());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.springframework.test.context.support;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
@ -58,17 +57,17 @@ class TestPropertySourceAttributes {
|
|||
|
||||
/**
|
||||
* Create a new {@code TestPropertySourceAttributes} instance for the
|
||||
* supplied {@link AnnotationAttributes} (parsed from a
|
||||
* {@link TestPropertySource @TestPropertySource} annotation) and
|
||||
* the {@linkplain Class test class} that declared them, enforcing
|
||||
* supplied {@link TestPropertySource @TestPropertySource} annotation and
|
||||
* the {@linkplain Class test class} that declared it, enforcing
|
||||
* configuration rules and detecting a default properties file if
|
||||
* necessary.
|
||||
* @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) {
|
||||
this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getBoolean("inheritLocations"),
|
||||
annAttrs.getStringArray("properties"), annAttrs.getBoolean("inheritProperties"));
|
||||
TestPropertySourceAttributes(Class<?> declaringClass, TestPropertySource testPropertySource) {
|
||||
this(declaringClass, testPropertySource.locations(), testPropertySource.inheritLocations(),
|
||||
testPropertySource.properties(), testPropertySource.inheritProperties());
|
||||
}
|
||||
|
||||
private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations,
|
||||
|
|
|
@ -29,7 +29,6 @@ import org.apache.commons.logging.Log;
|
|||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
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.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.util.TestContextResourceUtils;
|
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -96,15 +96,16 @@ public abstract class TestPropertySourceUtils {
|
|||
annotationType.getName(), testClass.getName()));
|
||||
|
||||
while (descriptor != null) {
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
|
||||
TestPropertySource testPropertySource = descriptor.getMergedAnnotation();
|
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @TestPropertySource attributes [%s] for declaring class [%s].",
|
||||
annAttrs, rootDeclaringClass.getName()));
|
||||
logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].",
|
||||
testPropertySource, rootDeclaringClass.getName()));
|
||||
}
|
||||
|
||||
TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, annAttrs);
|
||||
TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass,
|
||||
testPropertySource);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved TestPropertySource attributes: " + attributes);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.springframework.beans.BeansException;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
|
@ -500,18 +499,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
|||
if (this.configurationAttributes == null) {
|
||||
Class<?> clazz = testContext.getTestClass();
|
||||
|
||||
AnnotationAttributes annAttrs = AnnotatedElementUtils.findAnnotationAttributes(clazz,
|
||||
TransactionConfiguration.class.getName());
|
||||
TransactionConfiguration txConfig = AnnotatedElementUtils.findAnnotation(clazz,
|
||||
TransactionConfiguration.class);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Retrieved @TransactionConfiguration attributes [%s] for test class [%s].",
|
||||
annAttrs, clazz));
|
||||
logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].",
|
||||
txConfig, clazz));
|
||||
}
|
||||
|
||||
String transactionManagerName;
|
||||
boolean defaultRollback;
|
||||
if (annAttrs != null) {
|
||||
transactionManagerName = annAttrs.getString("transactionManager");
|
||||
defaultRollback = annAttrs.getBoolean("defaultRollback");
|
||||
if (txConfig != null) {
|
||||
transactionManagerName = txConfig.transactionManager();
|
||||
defaultRollback = txConfig.defaultRollback();
|
||||
}
|
||||
else {
|
||||
transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME;
|
||||
|
|
|
@ -272,12 +272,16 @@ public abstract class MetaAnnotationUtils {
|
|||
|
||||
private final T annotation;
|
||||
|
||||
private final T mergedAnnotation;
|
||||
|
||||
private final AnnotationAttributes annotationAttributes;
|
||||
|
||||
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, T annotation) {
|
||||
this(rootDeclaringClass, rootDeclaringClass, null, annotation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass,
|
||||
Annotation composedAnnotation, T annotation) {
|
||||
Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null");
|
||||
|
@ -286,8 +290,10 @@ public abstract class MetaAnnotationUtils {
|
|||
this.declaringClass = declaringClass;
|
||||
this.composedAnnotation = composedAnnotation;
|
||||
this.annotation = annotation;
|
||||
this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(
|
||||
rootDeclaringClass, annotation.annotationType());
|
||||
this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(rootDeclaringClass,
|
||||
annotation.annotationType().getName(), false, false);
|
||||
this.mergedAnnotation = AnnotationUtils.synthesizeAnnotation(annotationAttributes,
|
||||
(Class<T>) annotation.annotationType(), rootDeclaringClass);
|
||||
}
|
||||
|
||||
public Class<?> getRootDeclaringClass() {
|
||||
|
@ -302,6 +308,16 @@ public abstract class MetaAnnotationUtils {
|
|||
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() {
|
||||
return this.annotation.annotationType();
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ public class SqlScriptsTestExecutionListenerTests {
|
|||
containsString("attribute [scripts] and its alias [value]")));
|
||||
exception.expectMessage(either(containsString("values of [{foo}] and [{bar}]")).or(
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -401,7 +401,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
|||
Builder mappingName(String name);
|
||||
|
||||
/**
|
||||
* Set a custom conditions to use.
|
||||
* Set a custom condition to use.
|
||||
*/
|
||||
Builder customCondition(RequestCondition<?> condition);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Set;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
|
@ -42,7 +43,6 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
|
|||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.objenesis.Objenesis;
|
||||
import org.springframework.objenesis.SpringObjenesis;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
@ -418,13 +418,11 @@ public class MvcUriComponentsBuilder {
|
|||
|
||||
private static String getTypeRequestMapping(Class<?> controllerType) {
|
||||
Assert.notNull(controllerType, "'controllerType' must not be null");
|
||||
String annotType = RequestMapping.class.getName();
|
||||
AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(controllerType, annotType);
|
||||
if (attrs == null) {
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(controllerType, RequestMapping.class);
|
||||
if (requestMapping == null) {
|
||||
return "/";
|
||||
}
|
||||
String[] paths = attrs.getStringArray("path");
|
||||
paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths;
|
||||
String[] paths = requestMapping.path();
|
||||
if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
|
||||
return "/";
|
||||
}
|
||||
|
@ -435,13 +433,11 @@ public class MvcUriComponentsBuilder {
|
|||
}
|
||||
|
||||
private static String getMethodRequestMapping(Method method) {
|
||||
String annotType = RequestMapping.class.getName();
|
||||
AnnotationAttributes attrs = AnnotatedElementUtils.findAnnotationAttributes(method, annotType);
|
||||
if (attrs == null) {
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(method, RequestMapping.class);
|
||||
if (requestMapping == null) {
|
||||
throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString());
|
||||
}
|
||||
String[] paths = attrs.getStringArray("path");
|
||||
paths = ObjectUtils.isEmpty(paths) ? attrs.getStringArray("value") : paths;
|
||||
String[] paths = requestMapping.path();
|
||||
if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
|
||||
return "/";
|
||||
}
|
||||
|
@ -759,7 +755,6 @@ public class MvcUriComponentsBuilder {
|
|||
* that accept the controllerType.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unused")
|
||||
public MethodArgumentBuilder(Method method) {
|
||||
this(method.getDeclaringClass(), method);
|
||||
}
|
||||
|
|
|
@ -22,12 +22,10 @@ import java.util.List;
|
|||
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
|
@ -49,6 +47,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
|
|||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
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
|
||||
* @return the condition, or {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
|
||||
return null;
|
||||
}
|
||||
|
@ -228,77 +226,41 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
|||
* @param method the handler method for which to create the condition
|
||||
* @return the condition, or {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected RequestCondition<?> getCustomMethodCondition(Method method) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitional method used to invoke one of two createRequestMappingInfo
|
||||
* variants one of which is deprecated.
|
||||
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
|
||||
* 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) {
|
||||
RequestMapping annotation;
|
||||
AnnotationAttributes attributes;
|
||||
RequestCondition<?> customCondition;
|
||||
String annotationType = RequestMapping.class.getName();
|
||||
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;
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findAnnotation(annotatedElement, RequestMapping.class);
|
||||
RequestCondition<?> customCondition = ((annotatedElement instanceof Class<?>) ? getCustomTypeCondition((Class<?>) annotatedElement)
|
||||
: getCustomMethodCondition((Method) annotatedElement));
|
||||
return ((requestMapping != null) ? createRequestMappingInfo(requestMapping, customCondition) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RequestMappingInfo from a RequestMapping annotation.
|
||||
* @deprecated as of 4.2 after the introduction of support for
|
||||
* {@code @RequestMapping} as meta-annotation. Please use
|
||||
* {@link #createRequestMappingInfo(AnnotationAttributes, RequestCondition)}.
|
||||
* Create a {@link RequestMappingInfo} from the supplied
|
||||
* {@link RequestMapping @RequestMapping} annotation, which is either
|
||||
* a directly declared annotation, a meta-annotation, or the synthesized
|
||||
* result of merging annotation attributes within an annotation hierarchy.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unused")
|
||||
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation,
|
||||
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping,
|
||||
RequestCondition<?> customCondition) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RequestMappingInfo from the attributes of an
|
||||
* {@code @RequestMapping} annotation or a meta-annotation, i.e. a custom
|
||||
* annotation annotated with {@code @RequestMapping}.
|
||||
* @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"))
|
||||
return RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
|
||||
.methods(requestMapping.method())
|
||||
.params(requestMapping.params())
|
||||
.headers(requestMapping.headers())
|
||||
.consumes(requestMapping.consumes())
|
||||
.produces(requestMapping.produces())
|
||||
.mappingName(requestMapping.name())
|
||||
.customCondition(customCondition)
|
||||
.options(this.config)
|
||||
.build();
|
||||
|
|
Loading…
Reference in New Issue