Browse Source

Merge pull request #176 from philwebb/SPR-9925

# By Phillip Webb
* SPR-9925:
  Prevent duplicate @Import processing
  Polish Javadoc for @Import
  Improve #toString for AnnotationAttributes
pull/168/merge
Chris Beams 13 years ago
parent
commit
985cb9df11
  1. 74
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
  2. 3
      spring-context/src/main/java/org/springframework/context/annotation/Import.java
  3. 53
      spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java
  4. 28
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java

74
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

@ -17,14 +17,13 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Annotation; import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
@ -222,10 +221,9 @@ class ConfigurationClassParser {
} }
// process any @Import annotations // process any @Import annotations
List<AnnotationAttributes> imports = Set<String> imports = getImports(metadata.getClassName(), null, new HashSet<String>());
findAllAnnotationAttributes(Import.class, metadata.getClassName(), true); if (imports != null && !imports.isEmpty()) {
for (AnnotationAttributes importAnno : imports) { processImport(configClass, imports.toArray(new String[imports.size()]), true);
processImport(configClass, importAnno.getStringArray("value"), true);
} }
// process any @ImportResource annotations // process any @ImportResource annotations
@ -265,45 +263,36 @@ class ConfigurationClassParser {
} }
/** /**
* Return a list of attribute maps for all declarations of the given annotation * Recursively collect all declared {@code @Import} values. Unlike most
* on the given annotated class using the given MetadataReaderFactory to introspect * meta-annotations it is valid to have several {@code @Import}s declared with
* annotation metadata. Meta-annotations are ordered first in the list, and if the * different values, the usual process or returning values from the first
* target annotation is declared directly on the class, its map of attributes will be * meta-annotation on a class is not sufficient.
* ordered last in the list. * <p>For example, it is common for a {@code @Configuration} class to declare direct
* @param targetAnnotation the annotation to search for, both locally and as a meta-annotation * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
* @param annotatedClassName the class to inspect * annotation.
* @param classValuesAsString whether class attributes should be returned as strings * @param className the class name to search
* @param imports the imports collected so far or {@code null}
* @param visited used to track visited classes to prevent infinite recursion (must not be null)
* @return a set of all {@link Import#value() import values} or {@code null}
* @throws IOException if there is any problem reading metadata from the named class
*/ */
private List<AnnotationAttributes> findAllAnnotationAttributes( private Set<String> getImports(String className, Set<String> imports,
Class<? extends Annotation> targetAnnotation, String annotatedClassName, Set<String> visited) throws IOException {
boolean classValuesAsString) throws IOException { if (visited.add(className)) {
AnnotationMetadata metadata = metadataReaderFactory.getMetadataReader(className).getAnnotationMetadata();
List<AnnotationAttributes> allAttribs = new ArrayList<AnnotationAttributes>(); Map<String, Object> attributes = metadata.getAnnotationAttributes(Import.class.getName(), true);
if (attributes != null) {
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(annotatedClassName); String[] value = (String[]) attributes.get("value");
AnnotationMetadata metadata = reader.getAnnotationMetadata(); if (value != null && value.length > 0) {
String targetAnnotationType = targetAnnotation.getName(); imports = (imports == null ? new LinkedHashSet<String>() : imports);
imports.addAll(Arrays.asList(value));
for (String annotationType : metadata.getAnnotationTypes()) { }
if (annotationType.equals(targetAnnotationType)) {
continue;
} }
AnnotationMetadata metaAnnotations = for (String annotationType : metadata.getAnnotationTypes()) {
this.metadataReaderFactory.getMetadataReader(annotationType).getAnnotationMetadata(); getImports(annotationType, imports, visited);
AnnotationAttributes targetAttribs =
AnnotationAttributes.fromMap(metaAnnotations.getAnnotationAttributes(targetAnnotationType, classValuesAsString));
if (targetAttribs != null) {
allAttribs.add(targetAttribs);
} }
} }
return imports;
AnnotationAttributes localAttribs =
AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(targetAnnotationType, classValuesAsString));
if (localAttribs != null) {
allAttribs.add(localAttribs);
}
return allAttribs;
} }
private void processImport(ConfigurationClass configClass, String[] classesToImport, boolean checkForCircularImports) throws IOException { private void processImport(ConfigurationClass configClass, String[] classesToImport, boolean checkForCircularImports) throws IOException {
@ -440,5 +429,4 @@ class ConfigurationClassParser {
new Location(importStack.peek().getResource(), metadata)); new Location(importStack.peek().getResource(), metadata));
} }
} }
} }

3
spring-context/src/main/java/org/springframework/context/annotation/Import.java

@ -53,7 +53,8 @@ import java.lang.annotation.Target;
public @interface Import { public @interface Import {
/** /**
* The @{@link Configuration} and/or {@link ImportSelector} classes to import. * The @{@link Configuration}, {@link ImportSelector} and/or
* {@link ImportBeanDefinitionRegistrar} classes to import.
*/ */
Class<?>[] value(); Class<?>[] value();
} }

53
spring-context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java

@ -25,10 +25,14 @@ import org.junit.Test;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
import org.springframework.util.Assert;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -77,6 +81,24 @@ public class ImportAwareTests {
assertThat(foo, is("xyz")); assertThat(foo, is("xyz"));
} }
@Test
public void importRegistrar() throws Exception {
ImportedRegistrar.called = false;
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ImportingRegistrarConfig.class);
ctx.refresh();
assertNotNull(ctx.getBean("registrarImportedBean"));
}
@Test
public void importRegistrarWithImport() throws Exception {
ImportedRegistrar.called = false;
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ImportingRegistrarConfigWithImport.class);
ctx.refresh();
assertNotNull(ctx.getBean("registrarImportedBean"));
assertNotNull(ctx.getBean(ImportedConfig.class));
}
@Configuration @Configuration
@Import(ImportedConfig.class) @Import(ImportedConfig.class)
@ -131,4 +153,35 @@ public class ImportAwareTests {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
} }
} }
@Configuration
@EnableImportRegistrar
static class ImportingRegistrarConfig {
}
@Configuration
@EnableImportRegistrar
@Import(ImportedConfig.class)
static class ImportingRegistrarConfigWithImport {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ImportedRegistrar.class)
public @interface EnableImportRegistrar {
}
static class ImportedRegistrar implements ImportBeanDefinitionRegistrar {
static boolean called;
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(String.class.getName());
registry.registerBeanDefinition("registrarImportedBean", beanDefinition );
Assert.state(called == false, "ImportedRegistrar called twice");
called = true;
}
}
} }

28
spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java

@ -18,10 +18,12 @@ package org.springframework.core.annotation;
import static java.lang.String.format; import static java.lang.String.format;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs * {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
@ -129,4 +131,28 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName())); attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
return (T) value; return (T) value;
} }
}
public String toString() {
Iterator<Map.Entry<String, Object>> entries = entrySet().iterator();
StringBuilder sb = new StringBuilder("{");
while (entries.hasNext()) {
Map.Entry<String, Object> entry = entries.next();
sb.append(entry.getKey());
sb.append('=');
sb.append(valueToString(entry.getValue()));
sb.append(entries.hasNext() ? ", " : "");
}
sb.append("}");
return sb.toString();
}
private String valueToString(Object value) {
if (value == this) {
return "(this Map)";
}
if (value instanceof Object[]) {
return "[" + StringUtils.arrayToCommaDelimitedString((Object[]) value) + "]";
}
return String.valueOf(value);
}
}

Loading…
Cancel
Save