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:
Sam Brannen
2015-05-25 16:58:18 +02:00
parent f41de12cf6
commit e30c9b2ef3
25 changed files with 881 additions and 358 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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();