Support meta-annotation overrides in ASM processing
Prior to this commit, Spring supported meta-annotation attribute overrides in custom composed annotations with reflection-based annotation processing but not with ASM-based annotation processing. This commit ensures that meta-annotation attribute overrides are supported in AnnotationMetadataReadingVisitor.getAnnotationAttributes(). Issue: SPR-11574
This commit is contained in:
parent
1bab8a3916
commit
99cd2f6098
|
@ -23,6 +23,19 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.ComponentScanParserTests.KustomAnnotationAutowiredBean;
|
||||
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.tests.context.SimpleMapScope;
|
||||
import org.springframework.util.SerializationTestUtils;
|
||||
|
||||
import example.scannable.CustomComponent;
|
||||
import example.scannable.CustomStereotype;
|
||||
import example.scannable.DefaultNamedComponent;
|
||||
|
@ -33,19 +46,6 @@ import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImp
|
|||
import example.scannable_scoped.CustomScopeAnnotationBean;
|
||||
import example.scannable_scoped.MyScope;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.ComponentScanParserTests.CustomAnnotationAutowiredBean;
|
||||
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.tests.context.SimpleMapScope;
|
||||
import org.springframework.util.SerializationTestUtils;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;
|
||||
|
@ -159,7 +159,7 @@ public class ComponentScanAnnotationIntegrationTests {
|
|||
@Test
|
||||
public void withCustomTypeFilter() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithCustomTypeFilter.class);
|
||||
CustomAnnotationAutowiredBean testBean = ctx.getBean(CustomAnnotationAutowiredBean.class);
|
||||
KustomAnnotationAutowiredBean testBean = ctx.getBean(KustomAnnotationAutowiredBean.class);
|
||||
assertThat(testBean.getDependency(), notNullValue());
|
||||
}
|
||||
|
||||
|
@ -304,8 +304,8 @@ class ComponentScanWithCustomTypeFilter {
|
|||
return cac;
|
||||
}
|
||||
|
||||
public ComponentScanParserTests.CustomAnnotationAutowiredBean testBean() {
|
||||
return new ComponentScanParserTests.CustomAnnotationAutowiredBean();
|
||||
public ComponentScanParserTests.KustomAnnotationAutowiredBean testBean() {
|
||||
return new ComponentScanParserTests.KustomAnnotationAutowiredBean();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
|
@ -45,6 +45,7 @@ import example.scannable.AutowiredQualifierFooService;
|
|||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class ComponentScanParserTests {
|
||||
|
||||
|
@ -84,7 +85,7 @@ public class ComponentScanParserTests {
|
|||
public void testCustomAnnotationUsedForBothComponentScanAndQualifier() {
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"org/springframework/context/annotation/customAnnotationUsedForBothComponentScanAndQualifierTests.xml");
|
||||
CustomAnnotationAutowiredBean testBean = (CustomAnnotationAutowiredBean) context.getBean("testBean");
|
||||
KustomAnnotationAutowiredBean testBean = (KustomAnnotationAutowiredBean) context.getBean("testBean");
|
||||
assertNotNull(testBean.getDependency());
|
||||
}
|
||||
|
||||
|
@ -92,7 +93,7 @@ public class ComponentScanParserTests {
|
|||
public void testCustomTypeFilter() {
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"org/springframework/context/annotation/customTypeFilterTests.xml");
|
||||
CustomAnnotationAutowiredBean testBean = (CustomAnnotationAutowiredBean) context.getBean("testBean");
|
||||
KustomAnnotationAutowiredBean testBean = (KustomAnnotationAutowiredBean) context.getBean("testBean");
|
||||
assertNotNull(testBean.getDependency());
|
||||
}
|
||||
|
||||
|
@ -128,28 +129,36 @@ public class ComponentScanParserTests {
|
|||
}
|
||||
|
||||
|
||||
public static class CustomAnnotationAutowiredBean {
|
||||
/**
|
||||
* Intentionally spelling "custom" with a "k" since there are numerous
|
||||
* classes in this package named *Custom*.
|
||||
*/
|
||||
public static class KustomAnnotationAutowiredBean {
|
||||
|
||||
@Autowired
|
||||
@CustomAnnotation
|
||||
private CustomAnnotationDependencyBean dependency;
|
||||
private KustomAnnotationDependencyBean dependency;
|
||||
|
||||
public CustomAnnotationDependencyBean getDependency() {
|
||||
public KustomAnnotationDependencyBean getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@CustomAnnotation
|
||||
public static class CustomAnnotationDependencyBean {
|
||||
public static class KustomAnnotationDependencyBean {
|
||||
}
|
||||
|
||||
|
||||
public static class CustomTypeFilter implements TypeFilter {
|
||||
|
||||
/**
|
||||
* Intentionally spelling "custom" with a "k" since there are numerous
|
||||
* classes in this package named *Custom*.
|
||||
*/
|
||||
@Override
|
||||
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
|
||||
return metadataReader.getClassMetadata().getClassName().contains("Custom");
|
||||
return metadataReader.getClassMetadata().getClassName().contains("Kustom");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,73 +114,69 @@ public class ConfigurationClassPostProcessorTests {
|
|||
@Test
|
||||
public void postProcessorWorksWithComposedConfigurationUsingReflection() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ComposedConfigurationClass.class);
|
||||
postProcessorWorksWithComposedConfiguration(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessorWorksWithComposedConfigurationUsingAsm() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ComposedConfigurationClass.class.getName());
|
||||
postProcessorWorksWithComposedConfiguration(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessorWorksWithComposedConfigurationWithAttributeOverridesUsingReflection() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
ComposedConfigurationWithAttributeOverridesClass.class);
|
||||
postProcessorWorksWithComposedConfigurationWithAttributeOverrides(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
// TODO Remove expected exception when SPR-11574 is resolved.
|
||||
@Test(expected = ConflictingBeanDefinitionException.class)
|
||||
@Test
|
||||
public void postProcessorWorksWithComposedConfigurationWithAttributeOverridesUsingAsm() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
ComposedConfigurationWithAttributeOverridesClass.class.getName());
|
||||
postProcessorWorksWithComposedConfigurationWithAttributeOverrides(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverridesUsingReflection() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
ComposedComposedConfigurationWithAttributeOverridesClass.class);
|
||||
postProcessorWorksWithComposedComposedConfigurationWithAttributeOverrides(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
// TODO Remove expected exception when SPR-11574 is resolved.
|
||||
@Test(expected = ConflictingBeanDefinitionException.class)
|
||||
@Test
|
||||
public void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverridesUsingAsm() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
ComposedComposedConfigurationWithAttributeOverridesClass.class.getName());
|
||||
postProcessorWorksWithComposedComposedConfigurationWithAttributeOverrides(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesUsingReflection() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
MetaComponentScanConfigurationWithAttributeOverridesClass.class);
|
||||
postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverrides(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
// TODO Remove expected exception when SPR-11574 is resolved.
|
||||
@Test(expected = ConflictingBeanDefinitionException.class)
|
||||
@Test
|
||||
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesUsingAsm() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
MetaComponentScanConfigurationWithAttributeOverridesClass.class.getName());
|
||||
postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverrides(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclassUsingReflection() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
SubMetaComponentScanConfigurationWithAttributeOverridesClass.class);
|
||||
postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclass(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
// TODO Remove expected exception when SPR-11574 is resolved.
|
||||
@Test(expected = ConflictingBeanDefinitionException.class)
|
||||
@Test
|
||||
public void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclassUsingAsm() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
SubMetaComponentScanConfigurationWithAttributeOverridesClass.class.getName());
|
||||
postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclass(beanDefinition);
|
||||
assertSupportForComposedAnnotation(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -386,46 +382,7 @@ public class ConfigurationClassPostProcessorTests {
|
|||
assertSame(beanFactory.getBean("genericRepo"), beanFactory.getBean("repoConsumer"));
|
||||
}
|
||||
|
||||
private void postProcessorWorksWithComposedConfiguration(RootBeanDefinition beanDefinition) {
|
||||
beanFactory.registerBeanDefinition("config", beanDefinition);
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.setEnvironment(new StandardEnvironment());
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
SimpleComponent simpleComponent = beanFactory.getBean(SimpleComponent.class);
|
||||
assertNotNull(simpleComponent);
|
||||
}
|
||||
|
||||
private void postProcessorWorksWithComposedConfigurationWithAttributeOverrides(RootBeanDefinition beanDefinition) {
|
||||
beanFactory.registerBeanDefinition("config", beanDefinition);
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.setEnvironment(new StandardEnvironment());
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
SimpleComponent simpleComponent = beanFactory.getBean(SimpleComponent.class);
|
||||
assertNotNull(simpleComponent);
|
||||
}
|
||||
|
||||
private void postProcessorWorksWithComposedComposedConfigurationWithAttributeOverrides(
|
||||
RootBeanDefinition beanDefinition) {
|
||||
beanFactory.registerBeanDefinition("config", beanDefinition);
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.setEnvironment(new StandardEnvironment());
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
SimpleComponent simpleComponent = beanFactory.getBean(SimpleComponent.class);
|
||||
assertNotNull(simpleComponent);
|
||||
}
|
||||
|
||||
private void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverrides(
|
||||
RootBeanDefinition beanDefinition) {
|
||||
beanFactory.registerBeanDefinition("config", beanDefinition);
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.setEnvironment(new StandardEnvironment());
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
SimpleComponent simpleComponent = beanFactory.getBean(SimpleComponent.class);
|
||||
assertNotNull(simpleComponent);
|
||||
}
|
||||
|
||||
private void postProcessorWorksWithMetaComponentScanConfigurationWithAttributeOverridesSubclass(
|
||||
RootBeanDefinition beanDefinition) {
|
||||
private void assertSupportForComposedAnnotation(RootBeanDefinition beanDefinition) {
|
||||
beanFactory.registerBeanDefinition("config", beanDefinition);
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.setEnvironment(new StandardEnvironment());
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="testBean" class="org.springframework.context.annotation.ComponentScanParserTests$CustomAnnotationAutowiredBean"/>
|
||||
<bean id="testBean" class="org.springframework.context.annotation.ComponentScanParserTests$KustomAnnotationAutowiredBean"/>
|
||||
|
||||
</beans>
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="testBean" class="org.springframework.context.annotation.ComponentScanParserTests$CustomAnnotationAutowiredBean"/>
|
||||
<bean id="testBean" class="org.springframework.context.annotation.ComponentScanParserTests$KustomAnnotationAutowiredBean"/>
|
||||
|
||||
</beans>
|
||||
|
|
|
@ -61,7 +61,7 @@ import org.springframework.util.ReflectionUtils;
|
|||
public abstract class AnnotationUtils {
|
||||
|
||||
/** The attribute name for annotations with a single element */
|
||||
static final String VALUE = "value";
|
||||
public static final String VALUE = "value";
|
||||
|
||||
private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>();
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
|
@ -41,6 +41,7 @@ import org.springframework.util.MultiValueMap;
|
|||
* @author Mark Fisher
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
|
||||
|
@ -51,7 +52,13 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
|
|||
|
||||
protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
|
||||
|
||||
protected final MultiValueMap<String, AnnotationAttributes> attributeMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4);
|
||||
/**
|
||||
* Declared as a {@link LinkedMultiValueMap} instead of {@link MultiValueMap}
|
||||
* in order to ensure that ordering of entries is enforced.
|
||||
* @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes(LinkedMultiValueMap, String)
|
||||
*/
|
||||
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributeMap = new LinkedMultiValueMap<String, AnnotationAttributes>(
|
||||
4);
|
||||
|
||||
protected final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4);
|
||||
|
||||
|
@ -112,8 +119,8 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
|
|||
|
||||
@Override
|
||||
public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
|
||||
List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType);
|
||||
AnnotationAttributes raw = (attributes == null ? null : attributes.get(0));
|
||||
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(this.attributeMap,
|
||||
annotationType);
|
||||
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
|
@ -16,10 +16,17 @@
|
|||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
|
||||
/**
|
||||
* Internal utility class used when reading annotations.
|
||||
|
@ -28,12 +35,13 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
|||
* @author Mark Fisher
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 4.0
|
||||
*/
|
||||
abstract class AnnotationReadingVisitorUtils {
|
||||
|
||||
public static AnnotationAttributes convertClassValues(ClassLoader classLoader,
|
||||
AnnotationAttributes original, boolean classValuesAsString) {
|
||||
public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original,
|
||||
boolean classValuesAsString) {
|
||||
|
||||
if (original == null) {
|
||||
return null;
|
||||
|
@ -44,26 +52,24 @@ abstract class AnnotationReadingVisitorUtils {
|
|||
try {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof AnnotationAttributes) {
|
||||
value = convertClassValues(classLoader, (AnnotationAttributes) value,
|
||||
classValuesAsString);
|
||||
value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString);
|
||||
}
|
||||
else if (value instanceof AnnotationAttributes[]) {
|
||||
AnnotationAttributes[] values = (AnnotationAttributes[])value;
|
||||
AnnotationAttributes[] values = (AnnotationAttributes[]) value;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = convertClassValues(classLoader, values[i],
|
||||
classValuesAsString);
|
||||
values[i] = convertClassValues(classLoader, values[i], classValuesAsString);
|
||||
}
|
||||
}
|
||||
else if (value instanceof Type) {
|
||||
value = (classValuesAsString ? ((Type) value).getClassName() :
|
||||
classLoader.loadClass(((Type) value).getClassName()));
|
||||
value = (classValuesAsString ? ((Type) value).getClassName()
|
||||
: classLoader.loadClass(((Type) value).getClassName()));
|
||||
}
|
||||
else if (value instanceof Type[]) {
|
||||
Type[] array = (Type[]) value;
|
||||
Object[] convArray = (classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
convArray[i] = (classValuesAsString ? array[i].getClassName() :
|
||||
classLoader.loadClass(array[i].getClassName()));
|
||||
convArray[i] = (classValuesAsString ? array[i].getClassName()
|
||||
: classLoader.loadClass(array[i].getClassName()));
|
||||
}
|
||||
value = convArray;
|
||||
}
|
||||
|
@ -83,10 +89,66 @@ abstract class AnnotationReadingVisitorUtils {
|
|||
result.put(entry.getKey(), value);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
// Class not found - can't resolve class reference in annotation
|
||||
// attribute.
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the merged attributes of the annotation of the given type, if any,
|
||||
* from the supplied {@code attributeMap}.
|
||||
* <p>Annotation attribute values appearing <em>lower</em> in the annotation
|
||||
* hierarchy (i.e., closer to the declaring class) will override those
|
||||
* defined <em>higher</em> in the annotation hierarchy.
|
||||
* @param attributeMap the map of annotation attribute lists, keyed by
|
||||
* annotation type
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return the merged annotation attributes; or {@code null} if no matching
|
||||
* annotation is present in the {@code attributeMap}
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public static AnnotationAttributes getMergedAnnotationAttributes(
|
||||
LinkedMultiValueMap<String, AnnotationAttributes> attributeMap, String annotationType) {
|
||||
|
||||
// Get the unmerged list of attributes for the target annotation.
|
||||
List<AnnotationAttributes> attributesList = attributeMap.get(annotationType);
|
||||
if (attributesList == null || attributesList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// To start with, we populate the results with all attribute values from the
|
||||
// target annotation.
|
||||
AnnotationAttributes results = attributesList.get(0);
|
||||
Set<String> supportedAttributeNames = results.keySet();
|
||||
|
||||
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
|
||||
// elements in the map and reverse the order of the keys in order to traverse
|
||||
// "down" the meta-annotation hierarchy.
|
||||
List<String> annotationTypes = new ArrayList<String>(attributeMap.keySet());
|
||||
Collections.reverse(annotationTypes);
|
||||
|
||||
for (String currentAnnotationType : annotationTypes) {
|
||||
if (!currentAnnotationType.startsWith("java.lang.annotation")) {
|
||||
for (String attributeName : supportedAttributeNames) {
|
||||
if (!VALUE.equals(attributeName)) {
|
||||
List<AnnotationAttributes> currentAttributes = attributeMap.get(currentAnnotationType);
|
||||
if (currentAttributes != null && !currentAttributes.isEmpty()) {
|
||||
Object value = currentAttributes.get(0).get(attributeName);
|
||||
if (value != null) {
|
||||
// Overwrite value from target annotation with the value
|
||||
// from an attribute of the same name found lower in the
|
||||
// meta-annotation hierarchy.
|
||||
results.put(attributeName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
|
@ -16,15 +16,6 @@
|
|||
|
||||
package org.springframework.core.type;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
@ -43,6 +34,9 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
|
|||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests demonstrating that the reflection-based {@link StandardAnnotationMetadata}
|
||||
* and ASM-based {@code AnnotationMetadataReadingVisitor} produce identical output.
|
||||
|
@ -50,18 +44,19 @@ import org.springframework.stereotype.Component;
|
|||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class AnnotationMetadataTests {
|
||||
|
||||
@Test
|
||||
public void testStandardAnnotationMetadata() throws IOException {
|
||||
public void standardAnnotationMetadata() throws Exception {
|
||||
AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class, true);
|
||||
doTestAnnotationInfo(metadata);
|
||||
doTestMethodAnnotationInfo(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsmAnnotationMetadata() throws IOException {
|
||||
public void asmAnnotationMetadata() throws Exception {
|
||||
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName());
|
||||
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
|
||||
|
@ -77,14 +72,43 @@ public class AnnotationMetadataTests {
|
|||
* 'true' as is done in the main test above.
|
||||
*/
|
||||
@Test
|
||||
public void testStandardAnnotationMetadata_nestedAnnotationsAsMap_false() throws IOException {
|
||||
public void standardAnnotationMetadata_nestedAnnotationsAsMap_false() throws Exception {
|
||||
AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class);
|
||||
|
||||
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
|
||||
Annotation[] nestedAnnoArray = (Annotation[])specialAttrs.get("nestedAnnoArray");
|
||||
Annotation[] nestedAnnoArray = (Annotation[]) specialAttrs.get("nestedAnnoArray");
|
||||
assertThat(nestedAnnoArray[0], instanceOf(NestedAnno.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaAnnotationOverridesUsingStandardAnnotationMetadata() {
|
||||
AnnotationMetadata metadata = new StandardAnnotationMetadata(
|
||||
ComposedConfigurationWithAttributeOverridesClass.class);
|
||||
|
||||
assertMetaAnnotationOverrides(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metaAnnotationOverridesUsingAnnotationMetadataReadingVisitor() throws Exception {
|
||||
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(ComposedConfigurationWithAttributeOverridesClass.class.getName());
|
||||
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
|
||||
|
||||
assertMetaAnnotationOverrides(metadata);
|
||||
}
|
||||
|
||||
private void assertMetaAnnotationOverrides(AnnotationMetadata metadata) {
|
||||
AnnotationAttributes attributes = (AnnotationAttributes) metadata.getAnnotationAttributes(
|
||||
TestComponentScan.class.getName(), false);
|
||||
String[] basePackages = attributes.getStringArray("basePackages");
|
||||
assertThat("length of basePackages[]", basePackages.length, is(1));
|
||||
assertThat("basePackages[0]", basePackages[0], is("org.example.componentscan"));
|
||||
String[] value = attributes.getStringArray("value");
|
||||
assertThat("length of value[]", value.length, is(0));
|
||||
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
|
||||
assertThat("length of basePackageClasses[]", basePackageClasses.length, is(0));
|
||||
}
|
||||
|
||||
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
|
||||
assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
|
||||
assertThat(metadata.isInterface(), is(false));
|
||||
|
@ -127,58 +151,59 @@ public class AnnotationMetadataTests {
|
|||
AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
|
||||
assertThat("na", is(nestedAnno.getString("value")));
|
||||
assertTrue(nestedAnno.getEnum("anEnum").equals(SomeEnum.LABEL1));
|
||||
assertArrayEquals(new Class[]{String.class}, (Class[])nestedAnno.get("classArray"));
|
||||
assertArrayEquals(new Class[] { String.class }, (Class[]) nestedAnno.get("classArray"));
|
||||
|
||||
AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
|
||||
assertThat(nestedAnnoArray.length, is(2));
|
||||
assertThat(nestedAnnoArray[0].getString("value"), is("default"));
|
||||
assertTrue(nestedAnnoArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
|
||||
assertArrayEquals(new Class[]{Void.class}, (Class[])nestedAnnoArray[0].get("classArray"));
|
||||
assertArrayEquals(new Class[] { Void.class }, (Class[]) nestedAnnoArray[0].get("classArray"));
|
||||
assertThat(nestedAnnoArray[1].getString("value"), is("na1"));
|
||||
assertTrue(nestedAnnoArray[1].getEnum("anEnum").equals(SomeEnum.LABEL2));
|
||||
assertArrayEquals(new Class[]{Number.class}, (Class[])nestedAnnoArray[1].get("classArray"));
|
||||
assertArrayEquals(new Class[]{Number.class}, nestedAnnoArray[1].getClassArray("classArray"));
|
||||
assertArrayEquals(new Class[] { Number.class }, (Class[]) nestedAnnoArray[1].get("classArray"));
|
||||
assertArrayEquals(new Class[] { Number.class }, nestedAnnoArray[1].getClassArray("classArray"));
|
||||
|
||||
AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
|
||||
assertThat(optional.getString("value"), is("optional"));
|
||||
assertTrue(optional.getEnum("anEnum").equals(SomeEnum.DEFAULT));
|
||||
assertArrayEquals(new Class[]{Void.class}, (Class[])optional.get("classArray"));
|
||||
assertArrayEquals(new Class[]{Void.class}, optional.getClassArray("classArray"));
|
||||
assertArrayEquals(new Class[] { Void.class }, (Class[]) optional.get("classArray"));
|
||||
assertArrayEquals(new Class[] { Void.class }, optional.getClassArray("classArray"));
|
||||
|
||||
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
|
||||
assertThat(optionalArray.length, is(1));
|
||||
assertThat(optionalArray[0].getString("value"), is("optional"));
|
||||
assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
|
||||
assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray"));
|
||||
assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray"));
|
||||
assertArrayEquals(new Class[] { Void.class }, (Class[]) optionalArray[0].get("classArray"));
|
||||
assertArrayEquals(new Class[] { Void.class }, optionalArray[0].getClassArray("classArray"));
|
||||
|
||||
assertEquals("direct", metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
|
||||
allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
|
||||
assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
|
||||
}
|
||||
{ // perform tests with classValuesAsString = true
|
||||
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true);
|
||||
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(
|
||||
SpecialAttr.class.getName(), true);
|
||||
assertThat(specialAttrs.size(), is(6));
|
||||
assertThat(specialAttrs.get("clazz"), is((Object)String.class.getName()));
|
||||
assertThat(specialAttrs.get("clazz"), is((Object) String.class.getName()));
|
||||
assertThat(specialAttrs.getString("clazz"), is(String.class.getName()));
|
||||
|
||||
AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
|
||||
assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray"));
|
||||
assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray"));
|
||||
assertArrayEquals(new String[] { String.class.getName() }, nestedAnno.getStringArray("classArray"));
|
||||
assertArrayEquals(new String[] { String.class.getName() }, nestedAnno.getStringArray("classArray"));
|
||||
|
||||
AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
|
||||
assertArrayEquals(new String[]{Void.class.getName()}, (String[])nestedAnnoArray[0].get("classArray"));
|
||||
assertArrayEquals(new String[]{Void.class.getName()}, nestedAnnoArray[0].getStringArray("classArray"));
|
||||
assertArrayEquals(new String[]{Number.class.getName()}, (String[])nestedAnnoArray[1].get("classArray"));
|
||||
assertArrayEquals(new String[]{Number.class.getName()}, nestedAnnoArray[1].getStringArray("classArray"));
|
||||
assertArrayEquals(new String[] { Void.class.getName() }, (String[]) nestedAnnoArray[0].get("classArray"));
|
||||
assertArrayEquals(new String[] { Void.class.getName() }, nestedAnnoArray[0].getStringArray("classArray"));
|
||||
assertArrayEquals(new String[] { Number.class.getName() }, (String[]) nestedAnnoArray[1].get("classArray"));
|
||||
assertArrayEquals(new String[] { Number.class.getName() }, nestedAnnoArray[1].getStringArray("classArray"));
|
||||
|
||||
AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
|
||||
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optional.get("classArray"));
|
||||
assertArrayEquals(new String[]{Void.class.getName()}, optional.getStringArray("classArray"));
|
||||
assertArrayEquals(new String[] { Void.class.getName() }, (String[]) optional.get("classArray"));
|
||||
assertArrayEquals(new String[] { Void.class.getName() }, optional.getStringArray("classArray"));
|
||||
|
||||
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
|
||||
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray"));
|
||||
assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray"));
|
||||
assertArrayEquals(new String[] { Void.class.getName() }, (String[]) optionalArray[0].get("classArray"));
|
||||
assertArrayEquals(new String[] { Void.class.getName() }, optionalArray[0].getStringArray("classArray"));
|
||||
|
||||
assertEquals(metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"), "direct");
|
||||
allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
|
||||
|
@ -194,6 +219,9 @@ public class AnnotationMetadataTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public static enum SomeEnum {
|
||||
LABEL1, LABEL2, DEFAULT;
|
||||
}
|
||||
|
@ -201,8 +229,11 @@ public class AnnotationMetadataTests {
|
|||
@Target({})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NestedAnno {
|
||||
|
||||
String value() default "default";
|
||||
|
||||
SomeEnum anEnum() default SomeEnum.DEFAULT;
|
||||
|
||||
Class<?>[] classArray() default Void.class;
|
||||
}
|
||||
|
||||
|
@ -218,14 +249,15 @@ public class AnnotationMetadataTests {
|
|||
|
||||
NestedAnno[] nestedAnnoArray();
|
||||
|
||||
NestedAnno optional() default @NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class);
|
||||
NestedAnno optional() default @NestedAnno(value = "optional", anEnum = SomeEnum.DEFAULT, classArray = Void.class);
|
||||
|
||||
NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)};
|
||||
NestedAnno[] optionalArray() default { @NestedAnno(value = "optional", anEnum = SomeEnum.DEFAULT, classArray = Void.class) };
|
||||
}
|
||||
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DirectAnnotation {
|
||||
|
||||
String value();
|
||||
}
|
||||
|
||||
|
@ -250,6 +282,7 @@ public class AnnotationMetadataTests {
|
|||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EnumSubclasses {
|
||||
|
||||
SubclassEnum[] value();
|
||||
}
|
||||
|
||||
|
@ -263,13 +296,9 @@ public class AnnotationMetadataTests {
|
|||
|
||||
@Component("myName")
|
||||
@Scope("myScope")
|
||||
@SpecialAttr(clazz = String.class, state = Thread.State.NEW,
|
||||
nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = {String.class}),
|
||||
nestedAnnoArray = {
|
||||
@NestedAnno,
|
||||
@NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class})
|
||||
})
|
||||
@SuppressWarnings({"serial", "unused"})
|
||||
@SpecialAttr(clazz = String.class, state = Thread.State.NEW, nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = { String.class }), nestedAnnoArray = {
|
||||
@NestedAnno, @NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = { Number.class }) })
|
||||
@SuppressWarnings({ "serial", "unused" })
|
||||
@DirectAnnotation("direct")
|
||||
@MetaMetaAnnotation
|
||||
@EnumSubclasses({ SubclassEnum.FOO, SubclassEnum.BAR })
|
||||
|
@ -279,7 +308,7 @@ public class AnnotationMetadataTests {
|
|||
public void doWork(@TestQualifier("myColor") java.awt.Color color) {
|
||||
}
|
||||
|
||||
public void doSleep() {
|
||||
public void doSleep() {
|
||||
}
|
||||
|
||||
@DirectAnnotation("direct")
|
||||
|
@ -288,4 +317,36 @@ public class AnnotationMetadataTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Component
|
||||
public static @interface TestConfiguration {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public static @interface TestComponentScan {
|
||||
|
||||
String[] value() default {};
|
||||
|
||||
String[] basePackages() default {};
|
||||
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
@TestComponentScan(basePackages = "bogus")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public static @interface ComposedConfigurationWithAttributeOverrides {
|
||||
|
||||
String[] basePackages() default {};
|
||||
}
|
||||
|
||||
@ComposedConfigurationWithAttributeOverrides(basePackages = "org.example.componentscan")
|
||||
public static class ComposedConfigurationWithAttributeOverridesClass {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue