Backport "Prevent duplicate @Import processing"
Refactor ConfigurationClassParser to recursively find values from
all @Import annotations, combining them into a single unique set.
This change prevents ImportBeanDefinitionRegistrars from being
invoked twice.
Issue: SPR-9925
Backport-Commit: 6d8b37d8bb
Conflicts:
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
This commit is contained in:
committed by
Chris Beams
parent
bbd5fc6f35
commit
e9616b7204
@@ -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;
|
||||||
@@ -221,10 +220,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
|
||||||
@@ -274,45 +272,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 {
|
||||||
@@ -451,5 +440,4 @@ class ConfigurationClassParser {
|
|||||||
new Location(importStack.peek().getResource(), metadata));
|
new Location(importStack.peek().getResource(), metadata));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user