From 89005a5b7034cc1c2f702eac4bd36836b99f3765 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Fri, 6 May 2011 19:05:15 +0000 Subject: [PATCH] Process all meta and local @Import declarations Includes the introduction of AnnotationUtils#findAllAnnotationAttributes to support iterating through all annotations declared on a given type and interrogating each for the presence of a meta-annotation. See tests for details. --- .../annotation/ConfigurationClassParser.java | 10 +- .../ImportAnnotationDetectionTests.java | 139 ++++++++++++++++++ .../core/annotation/AnnotationUtils.java | 60 ++++++++ .../core/annotation/AnnotationUtilsTests.java | 50 ++++++- 4 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportAnnotationDetectionTests.java diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index b0ed6bdb95..6d33e5bd5b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -21,12 +21,15 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.Stack; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; @@ -135,9 +138,12 @@ class ConfigurationClassParser { } protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { - if (metadata.isAnnotated(Import.class.getName())) { - processImport(configClass, (String[]) metadata.getAnnotationAttributes(Import.class.getName(), true).get("value")); + List> allImportAttribs = + AnnotationUtils.findAllAnnotationAttributes(Import.class, metadata.getClassName(), true); + for (Map importAttribs : allImportAttribs) { + processImport(configClass, (String[]) importAttribs.get("value")); } + if (metadata.isAnnotated(ImportResource.class.getName())) { String[] resources = (String[]) metadata.getAnnotationAttributes(ImportResource.class.getName()).get("value"); Class readerClass = (Class) metadata.getAnnotationAttributes(ImportResource.class.getName()).get("reader"); diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportAnnotationDetectionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportAnnotationDetectionTests.java new file mode 100644 index 0000000000..6250ffd310 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportAnnotationDetectionTests.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2011 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.context.annotation.configuration; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.Test; +import org.springframework.beans.TestBean; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * Tests that @Import may be used both as a locally declared and meta-declared + * annotation, that all declarations are processed, and that any local declaration + * is processed last. + * + * @author Chris Beams + * @since 3.1 + */ +public class ImportAnnotationDetectionTests { + + @Test + public void multipleMetaImportsAreProcessed() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(MultiMetaImportConfig.class); + ctx.refresh(); + assertThat(ctx.containsBean("testBean1"), is(true)); + assertThat(ctx.containsBean("testBean2"), is(true)); + } + + @Test + public void localAndMetaImportsAreProcessed() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(MultiMetaImportConfigWithLocalImport.class); + ctx.refresh(); + assertThat(ctx.containsBean("testBean1"), is(true)); + assertThat(ctx.containsBean("testBean2"), is(true)); + assertThat(ctx.containsBean("testBean3"), is(true)); + } + + @Test + public void localImportIsProcessedLast() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(MultiMetaImportConfigWithLocalImportWithBeanOverride.class); + ctx.refresh(); + assertThat(ctx.containsBean("testBean1"), is(true)); + assertThat(ctx.containsBean("testBean2"), is(true)); + assertThat(ctx.getBean("testBean2", TestBean.class).getName(), is("2a")); + } + + @Configuration + @MetaImport1 + @MetaImport2 + static class MultiMetaImportConfig { + } + + @Configuration + @MetaImport1 + @MetaImport2 + @Import(Config3.class) + static class MultiMetaImportConfigWithLocalImport { + } + + @Configuration + @MetaImport1 + @MetaImport2 + @Import(Config2a.class) + static class MultiMetaImportConfigWithLocalImportWithBeanOverride { + } + + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Import(Config1.class) + @interface MetaImport1 { + } + + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Import(Config2.class) + @interface MetaImport2 { + } + + + @Configuration + static class Config1 { + @Bean + TestBean testBean1() { + return new TestBean("1"); + } + } + + @Configuration + static class Config2 { + @Bean + TestBean testBean2() { + return new TestBean("2"); + } + } + + @Configuration + static class Config2a { + @Bean + TestBean testBean2() { + return new TestBean("2a"); + } + } + + @Configuration + static class Config3 { + @Bean + TestBean testBean3() { + return new TestBean("3"); + } + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 31adc8956d..b8e8ca73ba 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -16,14 +16,20 @@ package org.springframework.core.annotation; +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.util.Assert; /** @@ -323,6 +329,60 @@ public abstract class AnnotationUtils { return attrs; } + /** + * Return a list of attribute maps for all declarations of the given target annotation + * on the given annotated class. Meta annotations are ordered first in the list, and if + * the target annotation is declared directly on the class, its map of attributes will be + * ordered last in the list. + * @param targetAnnotation the annotation to search for, both locally and as a meta-annotation + * @param annotatedClassName the class to search + * @param classValuesAsString whether class attributes should be returned as strings + * @see {@link #findAllAnnotationAttributes(Class, String)} + */ + public static List> findAllAnnotationAttributes( + Class targetAnnotation, String annotatedClassName, boolean classValuesAsString) throws IOException { + + List> allAttribs = new ArrayList>(); + + MetadataReader reader = new SimpleMetadataReaderFactory().getMetadataReader(annotatedClassName); + AnnotationMetadata metadata = reader.getAnnotationMetadata(); + String targetAnnotationType = targetAnnotation.getName(); + + for (String annotationType : metadata.getAnnotationTypes()) { + if (annotationType.equals(targetAnnotationType)) { + continue; + } + MetadataReader metaReader = new SimpleMetadataReaderFactory().getMetadataReader(annotationType); + Map targetAttribs = + metaReader.getAnnotationMetadata().getAnnotationAttributes(targetAnnotationType, classValuesAsString); + if (targetAttribs != null) { + allAttribs.add(targetAttribs); + } + } + + Map localAttribs = + metadata.getAnnotationAttributes(targetAnnotationType, classValuesAsString); + if (localAttribs != null) { + allAttribs.add(localAttribs); + } + + return allAttribs; + } + + /** + * Return a list of attribute maps for all declarations of the given target annotation + * on the given annotated class. Meta annotations are ordered first in the list, and if + * the target annotation is declared directly on the class, its map of attributes will be + * ordered last in the list. + * @param targetAnnotation the annotation to search for, both locally and as a meta-annotation + * @param annotatedClassName the class to search + * @see {@link #findAllAnnotationAttributes(Class, String, boolean)} + */ + public static List> findAllAnnotationAttributes( + Class targetAnnotation, String annotatedClassName) throws IOException { + return findAllAnnotationAttributes(targetAnnotation, annotatedClassName, false); + } + /** * Retrieve the value of the "value" attribute of a * single-element Annotation, given an annotation instance. diff --git a/org.springframework.core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/org.springframework.core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index e131c72f3c..4e65e232b2 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -16,16 +16,30 @@ package org.springframework.core.annotation; -import static org.junit.Assert.*; -import static org.springframework.core.annotation.AnnotationUtils.*; - +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.springframework.core.annotation.AnnotationUtils.findAnnotation; +import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass; +import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; +import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally; +import static org.springframework.core.annotation.AnnotationUtils.isAnnotationInherited; + +import java.io.IOException; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; import org.junit.Test; import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; /** * @author Rod Johnson @@ -204,6 +218,36 @@ public class AnnotationUtilsTests { assertNotNull(order); } + @Test + public void findAllComponentAnnotationAttributes() throws IOException { + List> allAttribs = + AnnotationUtils.findAllAnnotationAttributes(Component.class, HasLocalAndMetaComponentAnnotation.class.getName()); + + Object value = null; + for (Map attribs : allAttribs) { + value = attribs.get("value"); + } + + assertThat(allAttribs.size(), is(3)); + assertEquals("local", value); + } + + @Component(value="meta1") + @Retention(RetentionPolicy.RUNTIME) + @interface Meta1 { + } + + @Component(value="meta2") + @Retention(RetentionPolicy.RUNTIME) + @interface Meta2 { + } + + @Meta1 + @Component(value="local") + @Meta2 + static class HasLocalAndMetaComponentAnnotation { + } + public static interface AnnotatedInterface { @Order(0)