Find all @ComponentScan and @PropertySource annotations
Prior to this commit, Spring failed to find multiple composed @ComponentScan and @PropertySource annotations or multiple @ComponentScans and @PropertySources container annotations. The reason was due to lacking support in the AnnotatedTypeMetadata API. This commit introduces support for finding all @ComponentScan and @PropertySource annotations by making use of the new getMergedRepeatableAnnotationAttributes() method in AnnotatedTypeMetadata. Closes gh-30941 See gh-31041
This commit is contained in:
parent
0b902f32f6
commit
3bda2b7124
|
|
@ -17,9 +17,7 @@
|
||||||
package org.springframework.context.annotation;
|
package org.springframework.context.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||||
|
|
@ -51,6 +49,7 @@ import org.springframework.util.ClassUtils;
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
* @see ContextAnnotationAutowireCandidateResolver
|
* @see ContextAnnotationAutowireCandidateResolver
|
||||||
* @see ConfigurationClassPostProcessor
|
* @see ConfigurationClassPostProcessor
|
||||||
|
|
@ -281,33 +280,10 @@ public abstract class AnnotationConfigUtils {
|
||||||
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationTypeName));
|
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationTypeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
|
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
|
||||||
Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
|
Class<? extends Annotation> annotationType, Class<? extends Annotation> containerType) {
|
||||||
|
|
||||||
Set<AnnotationAttributes> result = new LinkedHashSet<>();
|
return metadata.getMergedRepeatableAnnotationAttributes(annotationType, containerType, false);
|
||||||
|
|
||||||
// Direct annotation present or meta-present?
|
|
||||||
addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationType.getName()));
|
|
||||||
|
|
||||||
// Container annotation present or meta-present?
|
|
||||||
Map<String, Object> container = metadata.getAnnotationAttributes(containerType.getName());
|
|
||||||
if (container != null && container.containsKey("value")) {
|
|
||||||
for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
|
|
||||||
addAttributesIfNotNull(result, containedAttributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return merged result
|
|
||||||
return Collections.unmodifiableSet(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addAttributesIfNotNull(
|
|
||||||
Set<AnnotationAttributes> result, @Nullable Map<String, Object> attributes) {
|
|
||||||
|
|
||||||
if (attributes != null) {
|
|
||||||
result.add(AnnotationAttributes.fromMap(attributes));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -46,6 +46,10 @@ import org.springframework.core.type.filter.TypeFilter;
|
||||||
*
|
*
|
||||||
* <p>See {@link Configuration @Configuration}'s Javadoc for usage examples.
|
* <p>See {@link Configuration @Configuration}'s Javadoc for usage examples.
|
||||||
*
|
*
|
||||||
|
* <p>{@code @ComponentScan} can be used as a <em>{@linkplain Repeatable repeatable}</em>
|
||||||
|
* annotation. {@code @ComponentScan} may also be used as a <em>meta-annotation</em>
|
||||||
|
* to create custom <em>composed annotations</em> with attribute overrides.
|
||||||
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
|
|
||||||
|
|
@ -267,8 +267,8 @@ class ConfigurationClassParser {
|
||||||
|
|
||||||
// Process any @PropertySource annotations
|
// Process any @PropertySource annotations
|
||||||
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
|
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
|
||||||
sourceClass.getMetadata(), PropertySources.class,
|
sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class,
|
||||||
org.springframework.context.annotation.PropertySource.class)) {
|
PropertySources.class)) {
|
||||||
if (this.propertySourceRegistry != null) {
|
if (this.propertySourceRegistry != null) {
|
||||||
this.propertySourceRegistry.processPropertySource(propertySource);
|
this.propertySourceRegistry.processPropertySource(propertySource);
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +280,7 @@ class ConfigurationClassParser {
|
||||||
|
|
||||||
// Process any @ComponentScan annotations
|
// Process any @ComponentScan annotations
|
||||||
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
|
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
|
||||||
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
|
sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class);
|
||||||
if (!componentScans.isEmpty() &&
|
if (!componentScans.isEmpty() &&
|
||||||
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
|
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
|
||||||
for (AnnotationAttributes componentScan : componentScans) {
|
for (AnnotationAttributes componentScan : componentScans) {
|
||||||
|
|
|
||||||
|
|
@ -147,11 +147,9 @@ import org.springframework.core.io.support.PropertySourceFactory;
|
||||||
* ConfigurableEnvironment} and {@link org.springframework.core.env.MutablePropertySources
|
* ConfigurableEnvironment} and {@link org.springframework.core.env.MutablePropertySources
|
||||||
* MutablePropertySources} javadocs for details.
|
* MutablePropertySources} javadocs for details.
|
||||||
*
|
*
|
||||||
* <p><b>NOTE: This annotation is repeatable according to Java 8 conventions.</b>
|
* <p>{@code @PropertySource} can be used as a <em>{@linkplain Repeatable repeatable}</em>
|
||||||
* However, all such {@code @PropertySource} annotations need to be declared at the same
|
* annotation. {@code @PropertySource} may also be used as a <em>meta-annotation</em>
|
||||||
* level: either directly on the configuration class or as meta-annotations on the
|
* to create custom <em>composed annotations</em> with attribute overrides.
|
||||||
* same custom annotation. Mixing direct annotations and meta-annotations is not
|
|
||||||
* recommended since direct annotations will effectively override meta-annotations.
|
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.EnvironmentAware;
|
import org.springframework.context.EnvironmentAware;
|
||||||
import org.springframework.context.ResourceLoaderAware;
|
import org.springframework.context.ResourceLoaderAware;
|
||||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||||
|
|
@ -143,6 +144,15 @@ public class ComponentScanAnnotationIntegrationTests {
|
||||||
.isTrue();
|
.isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void multipleComposedComponentScanAnnotations() { // gh-30941
|
||||||
|
ApplicationContext ctx = new AnnotationConfigApplicationContext(MultipleComposedAnnotationsConfig.class);
|
||||||
|
ctx.getBean(MultipleComposedAnnotationsConfig.class);
|
||||||
|
assertContextContainsBean(ctx, "componentScanAnnotationIntegrationTests.MultipleComposedAnnotationsConfig");
|
||||||
|
assertContextContainsBean(ctx, "simpleComponent");
|
||||||
|
assertContextContainsBean(ctx, "barComponent");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void viaBeanRegistration() {
|
public void viaBeanRegistration() {
|
||||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||||
|
|
@ -267,6 +277,10 @@ public class ComponentScanAnnotationIntegrationTests {
|
||||||
assertThat(ctx.containsBean("fooServiceImpl")).isTrue();
|
assertThat(ctx.containsBean("fooServiceImpl")).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertContextContainsBean(ApplicationContext ctx, String beanName) {
|
||||||
|
assertThat(ctx.containsBean(beanName)).as("context contains bean " + beanName).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ComponentScan
|
@ComponentScan
|
||||||
|
|
@ -278,10 +292,25 @@ public class ComponentScanAnnotationIntegrationTests {
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface ComposedConfiguration2 {
|
||||||
|
|
||||||
|
@AliasFor(annotation = ComponentScan.class)
|
||||||
|
String[] basePackages() default {};
|
||||||
|
}
|
||||||
|
|
||||||
@ComposedConfiguration(basePackages = "org.springframework.context.annotation.componentscan.simple")
|
@ComposedConfiguration(basePackages = "org.springframework.context.annotation.componentscan.simple")
|
||||||
public static class ComposedAnnotationConfig {
|
public static class ComposedAnnotationConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ComposedConfiguration(basePackages = "org.springframework.context.annotation.componentscan.simple")
|
||||||
|
@ComposedConfiguration2(basePackages = "example.scannable.sub")
|
||||||
|
static class MultipleComposedAnnotationsConfig {
|
||||||
|
}
|
||||||
|
|
||||||
public static class AwareTypeFilter implements TypeFilter, EnvironmentAware,
|
public static class AwareTypeFilter implements TypeFilter, EnvironmentAware,
|
||||||
ResourceLoaderAware, BeanClassLoaderAware, BeanFactoryAware {
|
ResourceLoaderAware, BeanClassLoaderAware, BeanFactoryAware {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||||
import org.springframework.beans.factory.FactoryBean;
|
import org.springframework.beans.factory.FactoryBean;
|
||||||
import org.springframework.beans.testfixture.beans.TestBean;
|
import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.annotation.AliasFor;
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
|
@ -239,6 +240,14 @@ class PropertySourceAnnotationTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void multipleComposedPropertySourceAnnotations() { // gh-30941
|
||||||
|
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MultipleComposedAnnotationsConfig.class);
|
||||||
|
ctx.getBean(MultipleComposedAnnotationsConfig.class);
|
||||||
|
assertEnvironmentContainsProperties(ctx, "from.p1", "from.p2", "from.p3", "from.p4", "from.p5");
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void withNamedPropertySources() {
|
void withNamedPropertySources() {
|
||||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithNamedPropertySources.class);
|
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithNamedPropertySources.class);
|
||||||
|
|
@ -305,6 +314,17 @@ class PropertySourceAnnotationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void assertEnvironmentContainsProperties(ApplicationContext ctx, String... names) {
|
||||||
|
for (String name : names) {
|
||||||
|
assertThat(ctx.getEnvironment().containsProperty(name)).as("environment contains property " + name).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEnvironmentContainsProperty(ApplicationContext ctx, String name) {
|
||||||
|
assertThat(ctx.getEnvironment().containsProperty(name)).as("environment contains property " + name).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@PropertySource("classpath:${unresolvable}/p1.properties")
|
@PropertySource("classpath:${unresolvable}/p1.properties")
|
||||||
static class ConfigWithUnresolvablePlaceholder {
|
static class ConfigWithUnresolvablePlaceholder {
|
||||||
|
|
@ -496,6 +516,28 @@ class PropertySourceAnnotationTests {
|
||||||
static class ConfigWithRepeatedPropertySourceAnnotationsOnComposedAnnotation {
|
static class ConfigWithRepeatedPropertySourceAnnotationsOnComposedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
|
||||||
|
@interface PropertySource1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p2.properties")
|
||||||
|
@PropertySources({
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p3.properties"),
|
||||||
|
})
|
||||||
|
@interface PropertySource23 {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource1
|
||||||
|
@PropertySource23
|
||||||
|
@PropertySources({
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p4.properties")
|
||||||
|
})
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p5.properties")
|
||||||
|
static class MultipleComposedAnnotationsConfig {
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@PropertySources({
|
@PropertySources({
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
testbean.name=p4TestBean
|
testbean.name=p4TestBean
|
||||||
|
from.p4=p4Value
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
testbean.name=p5TestBean
|
||||||
|
from.p5=p5Value
|
||||||
Loading…
Reference in New Issue