Synthesize annotation from a map of attributes
Spring Framework 4.2 RC1 introduced support for synthesizing an annotation from an existing annotation in order to provide additional functionality above and beyond that provided by Java. Specifically, such synthesized annotations provide support for @AliasFor semantics. As luck would have it, the same principle can be used to synthesize an annotation from any map of attributes, and in particular, from an instance of AnnotationAttributes. The following highlight the major changes in this commit toward achieving this goal. - Introduced AnnotationAttributeExtractor abstraction and refactored SynthesizedAnnotationInvocationHandler to delegate to an AnnotationAttributeExtractor. - Extracted code from SynthesizedAnnotationInvocationHandler into new AbstractAliasAwareAnnotationAttributeExtractor and DefaultAnnotationAttributeExtractor implementation classes. - Introduced MapAnnotationAttributeExtractor for synthesizing an annotation that is backed by a map or AnnotationAttributes instance. - Introduced a variant of synthesizeAnnotation() in AnnotationUtils that accepts a map. - Introduced findAnnotation(*) methods in AnnotatedElementUtils that synthesize merged AnnotationAttributes back into an annotation of the target type. The following classes have been refactored to use the new support for synthesizing AnnotationAttributes back into an annotation. - ApplicationListenerMethodAdapter - TestAnnotationUtils - AbstractTestContextBootstrapper - ActiveProfilesUtils - ContextLoaderUtils - DefaultActiveProfilesResolver - DirtiesContextTestExecutionListener - TestPropertySourceAttributes - TestPropertySourceUtils - TransactionalTestExecutionListener - MetaAnnotationUtils - MvcUriComponentsBuilder - RequestMappingHandlerMapping In addition, this commit also includes changes to ensure that arrays returned by synthesized annotations are properly cloned first. Issue: SPR-13067
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user