Skip to content

Commit e81df2e

Browse files
committed
Improve @configuration bean name discovery
Prior to this commit, and based on earlier changes supporting SPR-9023, ConfigurationClassBeanDefinitionReader employed a simplistic strategy for extracting the 'value' attribute (if any) from @configuration in order to determine the bean name for imported and nested configuration classes. An example case follows: @configuration("myConfig") public class AppConfig { ... } This approach is too simplistic however, given that it is possible in 'configuration lite' mode to specify a @Component-annotated class with @bean methods, e.g. @component("myConfig") public class AppConfig { @bean public Foo foo() { ... } } In this case, it's the 'value' attribute of @component, not @configuration, that should be consulted for the bean name. Or indeed if it were any other stereotype annotation meta-annotated with @component, the value attribute should respected. This kind of sophisticated discovery is exactly what AnnotationBeanNameGenerator was designed to do, and ConfigurationClassBeanDefinitionReader now uses it in favor of the custom approach described above. To enable this refactoring, nested and imported configuration classes are no longer registered as GenericBeanDefinition, but rather as AnnotatedGenericBeanDefinition given that AnnotationBeanNameGenerator falls back to a generic strategy unless the bean definition in question is assignable to AnnotatedBeanDefinition. A new constructor accepting AnnotationMetadata has been added to AnnotatedGenericBeanDefinition in order to support the ASM-based approach in use by configuration class processing. Javadoc has been updated for both AnnotatedGenericBeanDefinition and its now very similar cousin ScannedGenericBeanDefinition to make clear the semantics and intention of these two variants. Issue: SPR-9023
1 parent 08e2669 commit e81df2e

File tree

4 files changed

+121
-37
lines changed

4 files changed

+121
-37
lines changed

org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import org.springframework.beans.factory.support.GenericBeanDefinition;
2020
import org.springframework.core.type.AnnotationMetadata;
2121
import org.springframework.core.type.StandardAnnotationMetadata;
22+
import org.springframework.util.Assert;
2223

2324
/**
2425
* Extension of the {@link org.springframework.beans.factory.support.GenericBeanDefinition}
@@ -32,27 +33,46 @@
3233
* which also implements the AnnotatedBeanDefinition interface).
3334
*
3435
* @author Juergen Hoeller
36+
* @author Chris Beams
3537
* @since 2.5
3638
* @see AnnotatedBeanDefinition#getMetadata()
3739
* @see org.springframework.core.type.StandardAnnotationMetadata
3840
*/
41+
@SuppressWarnings("serial")
3942
public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {
4043

41-
private final AnnotationMetadata annotationMetadata;
44+
private final AnnotationMetadata metadata;
4245

4346

4447
/**
4548
* Create a new AnnotatedGenericBeanDefinition for the given bean class.
4649
* @param beanClass the loaded bean class
4750
*/
48-
public AnnotatedGenericBeanDefinition(Class beanClass) {
51+
public AnnotatedGenericBeanDefinition(Class<?> beanClass) {
4952
setBeanClass(beanClass);
50-
this.annotationMetadata = new StandardAnnotationMetadata(beanClass, true);
53+
this.metadata = new StandardAnnotationMetadata(beanClass, true);
54+
}
55+
56+
/**
57+
* Create a new AnnotatedGenericBeanDefinition for the given annotation metadata,
58+
* allowing for ASM-based processing and avoidance of early loading of the bean class.
59+
* Note that this constructor is functionally equivalent to
60+
* {@link org.springframework.context.annotation.ScannedGenericBeanDefinition
61+
* ScannedGenericBeanDefinition}, however the semantics of the latter indicate that
62+
* a bean was discovered specifically via component-scanning as opposed to other
63+
* means.
64+
* @param metadata the annotation metadata for the bean class in question
65+
* @since 3.1.1
66+
*/
67+
public AnnotatedGenericBeanDefinition(AnnotationMetadata metadata) {
68+
Assert.notNull(metadata, "AnnotationMetadata must not be null");
69+
setBeanClassName(metadata.getClassName());
70+
this.metadata = metadata;
5171
}
5272

5373

5474
public final AnnotationMetadata getMetadata() {
55-
return this.annotationMetadata;
75+
return this.metadata;
5676
}
5777

5878
}

org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import java.io.IOException;
2019
import java.lang.reflect.Method;
2120
import java.util.ArrayList;
2221
import java.util.Arrays;
@@ -29,6 +28,7 @@
2928
import org.apache.commons.logging.LogFactory;
3029

3130
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
31+
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
3232
import org.springframework.beans.factory.annotation.Autowire;
3333
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
3434
import org.springframework.beans.factory.config.BeanDefinition;
@@ -37,20 +37,17 @@
3737
import org.springframework.beans.factory.parsing.Problem;
3838
import org.springframework.beans.factory.parsing.ProblemReporter;
3939
import org.springframework.beans.factory.parsing.SourceExtractor;
40-
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4140
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
4241
import org.springframework.beans.factory.support.BeanDefinitionReader;
43-
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
4442
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
45-
import org.springframework.beans.factory.support.GenericBeanDefinition;
43+
import org.springframework.beans.factory.support.BeanNameGenerator;
4644
import org.springframework.beans.factory.support.RootBeanDefinition;
4745
import org.springframework.core.annotation.AnnotationAttributes;
4846
import org.springframework.core.env.Environment;
4947
import org.springframework.core.io.Resource;
5048
import org.springframework.core.io.ResourceLoader;
5149
import org.springframework.core.type.AnnotationMetadata;
5250
import org.springframework.core.type.MethodMetadata;
53-
import org.springframework.core.type.classreading.MetadataReader;
5451
import org.springframework.core.type.classreading.MetadataReaderFactory;
5552
import org.springframework.util.StringUtils;
5653

@@ -85,6 +82,8 @@ class ConfigurationClassBeanDefinitionReader {
8582

8683
private final Environment environment;
8784

85+
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
86+
8887

8988
/**
9089
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
@@ -135,39 +134,21 @@ private void doLoadBeanDefinitionForConfigurationClassIfNecessary(ConfigurationC
135134
return;
136135
}
137136

138-
BeanDefinition configBeanDef = new GenericBeanDefinition();
139-
String className = configClass.getMetadata().getClassName();
137+
AnnotationMetadata metadata = configClass.getMetadata();
138+
BeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
139+
String className = metadata.getClassName();
140140
configBeanDef.setBeanClassName(className);
141-
MetadataReader reader;
142-
try {
143-
reader = this.metadataReaderFactory.getMetadataReader(className);
144-
}
145-
catch (IOException ex) {
146-
throw new IllegalStateException("Could not create MetadataReader for class " + className);
147-
}
148141
if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {
149-
Map<String, Object> configAttributes =
150-
reader.getAnnotationMetadata().getAnnotationAttributes(Configuration.class.getName());
151-
152-
// has the 'value' attribute of @Configuration been set?
153-
String configBeanName = (String) configAttributes.get("value");
154-
if (StringUtils.hasText(configBeanName)) {
155-
// yes -> register the configuration class bean with this name
156-
this.registry.registerBeanDefinition(configBeanName, configBeanDef);
157-
}
158-
else {
159-
// no -> register the configuration class bean with a generated name
160-
configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry);
161-
}
142+
String configBeanName = this.beanNameGenerator.generateBeanName(configBeanDef, this.registry);
143+
this.registry.registerBeanDefinition(configBeanName, configBeanDef);
162144
configClass.setBeanName(configBeanName);
163145
if (logger.isDebugEnabled()) {
164146
logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName));
165147
}
166148
}
167149
else {
168-
AnnotationMetadata metadata = reader.getAnnotationMetadata();
169150
this.problemReporter.error(
170-
new InvalidConfigurationImportProblem(className, reader.getResource(), metadata));
151+
new InvalidConfigurationImportProblem(className, configClass.getResource(), metadata));
171152
}
172153
}
173154

org.springframework.context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2007 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.context.annotation;
1818

1919
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
20+
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
2021
import org.springframework.beans.factory.support.GenericBeanDefinition;
2122
import org.springframework.core.type.AnnotationMetadata;
2223
import org.springframework.core.type.classreading.MetadataReader;
@@ -27,16 +28,22 @@
2728
* class, based on an ASM ClassReader, with support for annotation metadata exposed
2829
* through the {@link AnnotatedBeanDefinition} interface.
2930
*
30-
* <p>This class does <i>not</i> load the bean <code>Class</code> early.
31+
* <p>This class does <i>not</i> load the bean {@code Class} early.
3132
* It rather retrieves all relevant metadata from the ".class" file itself,
32-
* parsed with the ASM ClassReader.
33+
* parsed with the ASM ClassReader. It is functionally equivalent to
34+
* {@link AnnotatedGenericBeanDefinition#AnnotatedGenericBeanDefinition(AnnotationMetadata)}
35+
* but distinguishes by type beans that have been <em>scanned</em> vs those that have
36+
* been otherwise registered or detected by other means.
3337
*
3438
* @author Juergen Hoeller
39+
* @author Chris Beams
3540
* @since 2.5
3641
* @see #getMetadata()
3742
* @see #getBeanClassName()
3843
* @see org.springframework.core.type.classreading.MetadataReaderFactory
44+
* @see AnnotatedGenericBeanDefinition
3945
*/
46+
@SuppressWarnings("serial")
4047
public class ScannedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {
4148

4249
private final AnnotationMetadata metadata;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.annotation.configuration;
18+
19+
import org.junit.Test;
20+
import org.springframework.beans.factory.config.BeanDefinition;
21+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
22+
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
23+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.context.annotation.Import;
27+
import org.springframework.stereotype.Component;
28+
29+
import static org.hamcrest.CoreMatchers.*;
30+
import static org.junit.Assert.*;
31+
32+
/**
33+
* Unit tests ensuring that configuration class bean names as expressed via @Configuration
34+
* or @Component 'value' attributes are indeed respected
35+
*
36+
* @author Chris Beams
37+
* @since 3.1.1
38+
*/
39+
public class ConfigurationBeanNameTests {
40+
41+
@Test
42+
public void registerOuterConfig() {
43+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
44+
ctx.register(A.class);
45+
ctx.refresh();
46+
assertThat(ctx.containsBean("outer"), is(true));
47+
assertThat(ctx.containsBean("imported"), is(true));
48+
assertThat(ctx.containsBean("nested"), is(true));
49+
assertThat(ctx.containsBean("nestedBean"), is(true));
50+
}
51+
52+
@Test
53+
public void registerNestedConfig() {
54+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
55+
ctx.register(A.B.class);
56+
ctx.refresh();
57+
assertThat(ctx.containsBean("outer"), is(false));
58+
assertThat(ctx.containsBean("imported"), is(false));
59+
assertThat(ctx.containsBean("nested"), is(true));
60+
assertThat(ctx.containsBean("nestedBean"), is(true));
61+
}
62+
63+
@Configuration("outer")
64+
@Import(C.class)
65+
static class A {
66+
@Component("nested")
67+
static class B {
68+
@Bean public String nestedBean() { return ""; }
69+
}
70+
}
71+
72+
@Configuration("imported")
73+
static class C {
74+
@Bean public String s() { return "s"; }
75+
}
76+
}

0 commit comments

Comments
 (0)