Overhaul AnnotatedElementUtils
- Methods which search for a specific annotation now properly ensure that the sought annotation was actually found. - Both the "get" and the "find" search algorithms no longer needlessly traverse meta-annotation hierarchies twice. - Both the "get" and the "find" search algorithms now properly increment the metaDepth when recursively searching within the meta-annotation hierarchy. - Redesigned getMetaAnnotationTypes() so that it doesn't needlessly search irrelevant annotations. - Documented and tested hasMetaAnnotationTypes(). - Documented isAnnotated(). Issue: SPR-11514
This commit is contained in:
@@ -45,14 +45,16 @@ public class AnnotatedElementUtils {
|
||||
* <em>present</em> on the annotation (of the specified
|
||||
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>This method also finds all meta-annotations in the annotation
|
||||
* hierarchy above the specified annotation.
|
||||
* <p>This method finds all meta-annotations in the annotation hierarchy
|
||||
* above the specified annotation.
|
||||
*
|
||||
* @param element the annotated element; never {@code null}
|
||||
* @param annotationType the annotation type on which to find
|
||||
* meta-annotations; never {@code null}
|
||||
* @return the names of all meta-annotations present on the annotation,
|
||||
* or {@code null} if not found
|
||||
* @see #getMetaAnnotationTypes(AnnotatedElement, String)
|
||||
* @see #hasMetaAnnotationTypes
|
||||
*/
|
||||
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
|
||||
Class<? extends Annotation> annotationType) {
|
||||
@@ -65,14 +67,16 @@ public class AnnotatedElementUtils {
|
||||
* <em>present</em> on the annotation (of the specified
|
||||
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
|
||||
*
|
||||
* <p>This method also finds all meta-annotations in the annotation
|
||||
* hierarchy above the specified annotation.
|
||||
* <p>This method finds all meta-annotations in the annotation hierarchy
|
||||
* above the specified annotation.
|
||||
*
|
||||
* @param element the annotated element; never {@code null}
|
||||
* @param annotationType the fully qualified class name of the annotation
|
||||
* type on which to find meta-annotations; never {@code null} or empty
|
||||
* @return the names of all meta-annotations present on the annotation,
|
||||
* or {@code null} if not found
|
||||
* @see #getMetaAnnotationTypes(AnnotatedElement, Class)
|
||||
* @see #hasMetaAnnotationTypes
|
||||
*/
|
||||
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
||||
Assert.notNull(element, "AnnotatedElement must not be null");
|
||||
@@ -80,32 +84,49 @@ public class AnnotatedElementUtils {
|
||||
|
||||
final Set<String> types = new LinkedHashSet<String>();
|
||||
|
||||
processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Object>() {
|
||||
@Override
|
||||
public Object process(Annotation annotation, int metaDepth) {
|
||||
if (metaDepth > 0) {
|
||||
types.add(annotation.annotationType().getName());
|
||||
}
|
||||
return null;
|
||||
try {
|
||||
Annotation annotation = getAnnotation(element, annotationType);
|
||||
if (annotation != null) {
|
||||
processWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor<Object>() {
|
||||
|
||||
@Override
|
||||
public Object process(Annotation annotation, int metaDepth) {
|
||||
types.add(annotation.annotationType().getName());
|
||||
return null;
|
||||
}
|
||||
}, new HashSet<AnnotatedElement>(), 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||
}
|
||||
|
||||
return (types.isEmpty() ? null : types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link AnnotatedElement} is annotated with
|
||||
* a <em>composed annotation</em> that is meta-annotated with an
|
||||
* annotation of the specified {@code annotationType}.
|
||||
*
|
||||
* <p>This method finds all meta-annotations in the annotation hierarchy
|
||||
* above the specified element.
|
||||
*
|
||||
* @param element the annotated element; never {@code null}
|
||||
* @param annotationType the fully qualified class name of the annotation
|
||||
* @param annotationType the fully qualified class name of the meta-annotation
|
||||
* type to find; never {@code null} or empty
|
||||
* @return {@code true} if a matching meta-annotation is present
|
||||
* @see #getMetaAnnotationTypes
|
||||
*/
|
||||
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
||||
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final String annotationType) {
|
||||
Assert.notNull(element, "AnnotatedElement must not be null");
|
||||
Assert.hasText(annotationType, "annotationType must not be null or empty");
|
||||
|
||||
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
||||
@Override
|
||||
public Boolean process(Annotation annotation, int metaDepth) {
|
||||
if (metaDepth > 0) {
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
if (found && (metaDepth > 0)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
return null;
|
||||
@@ -114,18 +135,27 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an annotation of the specified {@code annotationType}
|
||||
* is <em>present</em> on the supplied {@link AnnotatedElement} or
|
||||
* within the annotation hierarchy above the specified element.
|
||||
*
|
||||
* <p>If this method returns {@code true}, then {@link #getAnnotationAttributes}
|
||||
* will return a non-null value.
|
||||
*
|
||||
* @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 {@code true} if a matching annotation is present
|
||||
*/
|
||||
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
|
||||
public static boolean isAnnotated(AnnotatedElement element, final String annotationType) {
|
||||
Assert.notNull(element, "AnnotatedElement must not be null");
|
||||
Assert.hasText(annotationType, "annotationType must not be null or empty");
|
||||
|
||||
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
||||
@Override
|
||||
public Boolean process(Annotation annotation, int metaDepth) {
|
||||
return Boolean.TRUE;
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
return (found ? Boolean.TRUE : null);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -167,7 +197,7 @@ public class AnnotatedElementUtils {
|
||||
*/
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(
|
||||
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(annotationType,
|
||||
classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
|
||||
@@ -269,7 +299,7 @@ public class AnnotatedElementUtils {
|
||||
|
||||
return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses,
|
||||
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergeAnnotationAttributesProcessor(
|
||||
classValuesAsString, nestedAnnotationsAsMap));
|
||||
annotationType, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,21 +406,12 @@ public class AnnotatedElementUtils {
|
||||
|
||||
// Search in local annotations
|
||||
for (Annotation annotation : annotations) {
|
||||
// TODO Add check for !isInJavaLangAnnotationPackage()
|
||||
// if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
||||
// && (annotation.annotationType().getName().equals(annotationType) ||
|
||||
// metaDepth > 0)) {
|
||||
// TODO Test for !AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
||||
if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
|
||||
T result = processor.process(annotation, metaDepth);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
result = processWithGetSemantics(annotation.annotationType(), annotationType, processor,
|
||||
visited, metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +419,7 @@ public class AnnotatedElementUtils {
|
||||
for (Annotation annotation : annotations) {
|
||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
||||
T result = processWithGetSemantics(annotation.annotationType(), annotationType, processor,
|
||||
visited, metaDepth);
|
||||
visited, metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
return result;
|
||||
@@ -492,13 +513,6 @@ public class AnnotatedElementUtils {
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
result = processWithFindSemantics(annotation.annotationType(), annotationType,
|
||||
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
||||
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +521,7 @@ public class AnnotatedElementUtils {
|
||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
||||
T result = processWithFindSemantics(annotation.annotationType(), annotationType,
|
||||
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
||||
searchOnMethodsInSuperclasses, processor, visited, metaDepth);
|
||||
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
return result;
|
||||
@@ -636,6 +650,14 @@ public class AnnotatedElementUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Annotation getAnnotation(AnnotatedElement element, String annotationType) {
|
||||
for (Annotation annotation : element.getAnnotations()) {
|
||||
if (annotation.annotationType().getName().equals(annotationType)) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface that is used to process a target annotation (or
|
||||
@@ -707,18 +729,21 @@ public class AnnotatedElementUtils {
|
||||
*/
|
||||
private static class MergeAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
||||
|
||||
private final String annotationType;
|
||||
private final boolean classValuesAsString;
|
||||
private final boolean nestedAnnotationsAsMap;
|
||||
|
||||
|
||||
MergeAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
MergeAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
this.annotationType = annotationType;
|
||||
this.classValuesAsString = classValuesAsString;
|
||||
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
|
||||
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
return (!found ? null : AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.
|
||||
@@ -28,7 +28,7 @@ import org.springframework.util.ClassUtils;
|
||||
* A simple filter which matches classes with a given annotation,
|
||||
* checking inherited annotations as well.
|
||||
*
|
||||
* <p>The matching logic mirrors that of {@code Class.isAnnotationPresent()}.
|
||||
* <p>The matching logic mirrors that of {@link java.lang.Class#isAnnotationPresent(Class)}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Ramnivas Laddad
|
||||
|
||||
@@ -62,11 +62,33 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
@Test
|
||||
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
|
||||
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class,
|
||||
ComposedTransactionalComponent.class);
|
||||
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class);
|
||||
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class, Retention.class, Documented.class, Target.class, Inherited.class), names);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasMetaAnnotationTypesOnNonAnnotatedClass() {
|
||||
assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, Transactional.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasMetaAnnotationTypesOnClassWithMetaDepth0() {
|
||||
assertFalse(hasMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasMetaAnnotationTypesOnClassWithMetaDepth1() {
|
||||
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Transactional.class.getName()));
|
||||
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasMetaAnnotationTypesOnClassWithMetaDepth2() {
|
||||
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Transactional.class.getName()));
|
||||
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName()));
|
||||
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllAnnotationAttributesOnClassWithLocalAnnotation() {
|
||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxConfig.class,
|
||||
@@ -277,6 +299,14 @@ public class AnnotatedElementUtilsTests {
|
||||
assertNotNull("Should find @Order on StringGenericParameter.getFor() bridge method", attributes);
|
||||
}
|
||||
|
||||
/** @since 4.2 */
|
||||
@Test
|
||||
public void findAnnotationAttributesOnClassWithMetaAndLocalTxConfig() {
|
||||
AnnotationAttributes attributes = findAnnotationAttributes(MetaAndLocalTxConfigClass.class, Transactional.class);
|
||||
assertNotNull("Should find @Transactional on MetaAndLocalTxConfigClass", attributes);
|
||||
assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier"));
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -315,6 +345,8 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
String value() default "";
|
||||
|
||||
String qualifier() default "transactionManager";
|
||||
|
||||
boolean readOnly() default false;
|
||||
}
|
||||
|
||||
@@ -333,6 +365,13 @@ public class AnnotatedElementUtilsTests {
|
||||
@interface Composed2 {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TxComposedWithOverride {
|
||||
|
||||
String qualifier() default "txMgr";
|
||||
}
|
||||
|
||||
@Transactional("TxComposed1")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TxComposed1 {
|
||||
@@ -354,6 +393,14 @@ public class AnnotatedElementUtilsTests {
|
||||
@interface ComposedTransactionalComponent {
|
||||
}
|
||||
|
||||
@TxComposedWithOverride
|
||||
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
|
||||
@Transactional(qualifier = "localTxMgr")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@interface MetaAndLocalTxConfig {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class NonAnnotatedClass {
|
||||
@@ -389,6 +436,10 @@ public class AnnotatedElementUtilsTests {
|
||||
static class SubSubClassWithInheritedComposedAnnotation extends SubClassWithInheritedComposedAnnotation {
|
||||
}
|
||||
|
||||
@MetaAndLocalTxConfig
|
||||
static class MetaAndLocalTxConfigClass {
|
||||
}
|
||||
|
||||
@Transactional("TxConfig")
|
||||
static class TxConfig {
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -18,18 +18,14 @@ package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -83,14 +79,21 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||
import org.springframework.web.servlet.view.ViewResolverComposite;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* A test fixture with an {@link WebMvcConfigurationSupport} instance.
|
||||
* Integration tests for {@link WebMvcConfigurationSupport} (imported via
|
||||
* {@link EnableWebMvc @EnableWebMvc}).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class WebMvcConfigurationSupportTests {
|
||||
|
||||
@@ -106,10 +109,10 @@ public class WebMvcConfigurationSupportTests {
|
||||
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[0].getClass());
|
||||
|
||||
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/scoped"));
|
||||
assertNotNull(chain);
|
||||
assertNotNull("HandlerExecutionChain for '/scoped' mapping should not be null.", chain);
|
||||
|
||||
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/scopedProxy"));
|
||||
assertNotNull(chain);
|
||||
assertNotNull("HandlerExecutionChain for '/scopedProxy' mapping should not be null.", chain);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -207,6 +210,7 @@ public class WebMvcConfigurationSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handlerExceptionResolver() throws Exception {
|
||||
ApplicationContext context = initContext(WebConfig.class);
|
||||
HandlerExceptionResolverComposite compositeResolver =
|
||||
@@ -267,9 +271,6 @@ public class WebMvcConfigurationSupportTests {
|
||||
ApplicationContext context = initContext(CustomViewResolverOrderConfig.class);
|
||||
ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class);
|
||||
|
||||
Map<String, ViewResolver> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
context, ViewResolver.class, true, false);
|
||||
|
||||
assertNotNull(resolver);
|
||||
assertEquals(1, resolver.getViewResolvers().size());
|
||||
assertEquals(InternalResourceViewResolver.class, resolver.getViewResolvers().get(0).getClass());
|
||||
@@ -287,8 +288,7 @@ public class WebMvcConfigurationSupportTests {
|
||||
assertEquals(AntPathMatcher.class, pathMatcher.getClass());
|
||||
}
|
||||
|
||||
|
||||
private ApplicationContext initContext(Class... configClasses) {
|
||||
private ApplicationContext initContext(Class<?>... configClasses) {
|
||||
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
|
||||
context.setServletContext(new MockServletContext());
|
||||
context.register(configClasses);
|
||||
|
||||
Reference in New Issue
Block a user