diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
index 0d80658fc8f..1506ec18f9f 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
@@ -45,14 +45,16 @@ public class AnnotatedElementUtils {
* present on the annotation (of the specified
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
*
- *
This method also finds all meta-annotations in the annotation
- * hierarchy above the specified annotation.
+ *
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 getMetaAnnotationTypes(AnnotatedElement element,
Class extends Annotation> annotationType) {
@@ -65,14 +67,16 @@ public class AnnotatedElementUtils {
* present on the annotation (of the specified
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
*
- * This method also finds all meta-annotations in the annotation
- * hierarchy above the specified annotation.
+ *
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 getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
@@ -80,32 +84,49 @@ public class AnnotatedElementUtils {
final Set types = new LinkedHashSet();
- processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() {
- @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() {
+
+ @Override
+ public Object process(Annotation annotation, int metaDepth) {
+ types.add(annotation.annotationType().getName());
+ return null;
+ }
+ }, new HashSet(), 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 composed annotation that is meta-annotated with an
+ * annotation of the specified {@code annotationType}.
+ *
+ * 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() {
@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 present on the supplied {@link AnnotatedElement} or
+ * within the annotation hierarchy above the specified element.
+ *
+ * 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() {
@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 {
+ 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
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java
index 7d2d674c7c3..2b2faa4d05f 100644
--- a/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java
@@ -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.
*
- * The matching logic mirrors that of {@code Class.isAnnotationPresent()}.
+ *
The matching logic mirrors that of {@link java.lang.Class#isAnnotationPresent(Class)}.
*
* @author Mark Fisher
* @author Ramnivas Laddad
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
index 11fa7e68d51..d2195af6a8a 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
@@ -62,11 +62,33 @@ public class AnnotatedElementUtilsTests {
@Test
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
- Set names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class,
- ComposedTransactionalComponent.class);
+ Set 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 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 {
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
index 277897c307f..e48d055b3bc 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
@@ -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 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);