Skip to content

Commit cbadf35

Browse files
committed
Continued progress on SPR-5682: Provide dedicated ApplicationContext implementations for use with @configuration classes
Resolved sub-task SPR-6186: Eliminate duplicate code between ConfigurationClassApplicationContext and ConfigurationClassWebApplicationContext
1 parent ab10d37 commit cbadf35

File tree

2 files changed

+119
-88
lines changed

2 files changed

+119
-88
lines changed

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

+83-42
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@
2828
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2929
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
3030
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
31+
import org.springframework.context.support.AbstractApplicationContext;
3132
import org.springframework.context.support.AbstractRefreshableApplicationContext;
3233
import org.springframework.core.annotation.AnnotationUtils;
3334
import org.springframework.util.Assert;
3435
import org.springframework.util.StringUtils;
3536

3637

3738
/**
38-
* Standalone application context, accepting {@link Configuration}-annotated
39-
* class literals as input. Useful for test harnesses or any other scenario
39+
* Standalone application context, accepting {@link Configuration @Configuration}
40+
* -annotated class literals as input. Useful for test harnesses or any other scenario
4041
* where XML-based configuration is unnecessary or undesired.
4142
*
4243
* <p>In case of multiple Configuration classes, {@link Bean}
@@ -50,7 +51,8 @@
5051
*/
5152
public class ConfigurationClassApplicationContext extends AbstractRefreshableApplicationContext {
5253

53-
private final Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>();
54+
private final ConfigurationClassApplicationContext.Delegate delegate =
55+
new ConfigurationClassApplicationContext.Delegate();
5456

5557
/**
5658
* Create a new {@link ConfigurationClassApplicationContext}, loading bean
@@ -70,7 +72,7 @@ public ConfigurationClassApplicationContext(Class<?>... configClasses) {
7072
}
7173

7274
for (Class<?> configClass : configClasses) {
73-
addConfigurationClass(configClass);
75+
this.addConfigurationClass(configClass);
7476
}
7577

7678
this.refresh();
@@ -88,10 +90,7 @@ public ConfigurationClassApplicationContext(Class<?>... configClasses) {
8890
* @see #refresh()
8991
*/
9092
public void addConfigurationClass(Class<?> configClass) {
91-
Assert.notNull(
92-
AnnotationUtils.findAnnotation(configClass, Configuration.class),
93-
"Class [" + configClass.getName() + "] is not annotated with @Configuration");
94-
this.configClasses.add(configClass);
93+
this.delegate.addConfigurationClass(configClass);
9594
}
9695

9796
/**
@@ -100,30 +99,18 @@ public void addConfigurationClass(Class<?> configClass) {
10099
* processors, such that {@literal @Autowired}, {@literal @Required}, and associated
101100
* annotations can be used within Configuration classes.
102101
*
103-
* <p>Configuration class bean definitions are registered with generated bean definition names.
102+
* <p>Configuration class bean definitions are registered with generated bean definition
103+
* names unless the {@literal value} attribute is provided to the Configuration annotation.
104104
*
105105
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
106106
* @see ConfigurationClassPostProcessor
107+
* @see DefaultBeanNameGenerator
108+
* @see Configuration#value()
107109
*/
108110
@Override
109111
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
110112
throws IOException, BeansException {
111-
112-
// @Autowired and friends must be enabled by default when processing @Configuration classes
113-
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
114-
115-
for (Class<?> configClass : configClasses) {
116-
AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition();
117-
118-
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
119-
if (!StringUtils.hasLength(name)) {
120-
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
121-
}
122-
123-
beanFactory.registerBeanDefinition(name, def);
124-
}
125-
126-
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
113+
this.delegate.loadBeanDefinitions(beanFactory);
127114
}
128115

129116
/**
@@ -138,24 +125,78 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
138125
* @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class)
139126
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Class)
140127
*/
141-
@SuppressWarnings("unchecked")
142128
public <T> T getBean(Class<T> requiredType) {
143-
Assert.notNull(requiredType, "requiredType may not be null");
144-
145-
Map<String, ?> beansOfType = this.getBeansOfType(requiredType);
146-
147-
switch (beansOfType.size()) {
148-
case 0:
149-
throw new NoSuchBeanDefinitionException(requiredType);
150-
case 1:
151-
return (T) beansOfType.values().iterator().next();
152-
default:
153-
throw new NoSuchBeanDefinitionException(requiredType,
154-
beansOfType.size() + " matching bean definitions found " +
155-
"(" + StringUtils.collectionToCommaDelimitedString(beansOfType.keySet()) + "). " +
156-
"Consider qualifying with getBean(Class<T> beanType, String beanName) or " +
157-
"declaring one bean definition as @Primary");
158-
}
129+
return this.delegate.getBean(requiredType, this);
159130
}
160131

132+
133+
/**
134+
* Encapsulates behavior common to {@link ConfigurationClassApplicationContext}
135+
* and its {@link org.springframework.web.context.support.ConfigurationClassWebApplicationContext}
136+
* variant. Both classes already participate in mutually exclusive superclass
137+
* hierarchies, and this class allows for avoiding what would otherwise be a multiple
138+
* inheritance problem through composition.
139+
*
140+
* <p><strong>This class is public by necessity but should be considered private and
141+
* subject to change without notice.</strong>
142+
*/
143+
public static class Delegate {
144+
145+
private final Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>();
146+
147+
/**
148+
* @see ConfigurationClassApplicationContext#addConfigurationClass(Class)
149+
*/
150+
public void addConfigurationClass(Class<?> configClass) {
151+
Assert.notNull(
152+
AnnotationUtils.findAnnotation(configClass, Configuration.class),
153+
"Class [" + configClass.getName() + "] is not annotated with @Configuration");
154+
this.configClasses.add(configClass);
155+
}
156+
157+
/**
158+
* @see ConfigurationClassApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory)
159+
*/
160+
public void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
161+
// @Autowired and friends must be enabled by default when processing @Configuration classes
162+
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
163+
164+
for (Class<?> configClass : this.configClasses) {
165+
AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition();
166+
167+
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
168+
if (!StringUtils.hasLength(name)) {
169+
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
170+
}
171+
172+
beanFactory.registerBeanDefinition(name, def);
173+
}
174+
175+
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
176+
}
177+
178+
/**
179+
* @see ConfigurationClassApplicationContext#getBean(Class)
180+
*/
181+
@SuppressWarnings("unchecked")
182+
public <T> T getBean(Class<T> requiredType, AbstractApplicationContext context) {
183+
Assert.notNull(requiredType, "requiredType may not be null");
184+
Assert.notNull(context, "context may not be null");
185+
186+
Map<String, ?> beansOfType = context.getBeansOfType(requiredType);
187+
188+
switch (beansOfType.size()) {
189+
case 0:
190+
throw new NoSuchBeanDefinitionException(requiredType);
191+
case 1:
192+
return (T) beansOfType.values().iterator().next();
193+
default:
194+
throw new NoSuchBeanDefinitionException(requiredType,
195+
beansOfType.size() + " matching bean definitions found " +
196+
"(" + StringUtils.collectionToCommaDelimitedString(beansOfType.keySet()) + "). " +
197+
"Consider qualifying with getBean(Class<T> beanType, String beanName) or " +
198+
"declaring one bean definition as @Primary");
199+
}
200+
}
201+
}
161202
}

org.springframework.web/src/main/java/org/springframework/web/context/support/ConfigurationClassWebApplicationContext.java

+36-46
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,14 @@
1717
package org.springframework.web.context.support;
1818

1919
import java.io.IOException;
20-
import java.util.LinkedHashSet;
21-
import java.util.Map;
22-
import java.util.Set;
2320

2421
import org.springframework.beans.BeansException;
25-
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
26-
import org.springframework.beans.factory.support.AbstractBeanDefinition;
27-
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
28-
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
2922
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
30-
import org.springframework.context.annotation.AnnotationConfigUtils;
3123
import org.springframework.context.annotation.Configuration;
32-
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
24+
import org.springframework.context.annotation.ConfigurationClassApplicationContext;
3325
import org.springframework.core.annotation.AnnotationUtils;
3426
import org.springframework.util.Assert;
3527
import org.springframework.util.ClassUtils;
36-
import org.springframework.util.StringUtils;
3728

3829

3930
/**
@@ -57,15 +48,34 @@
5748
* to deliberately override certain bean definitions via an extra Configuration class.
5849
*
5950
* @author Chris Beams
60-
* @see org.springframework.context.annotation.ConfigurationClassApplicationContext
51+
* @since 3.0
52+
* @see ConfigurationClassApplicationContext
53+
* @see ConfigurationClassApplicationContext.Delegate
6154
*/
6255
public class ConfigurationClassWebApplicationContext extends AbstractRefreshableWebApplicationContext {
6356

57+
private final ConfigurationClassApplicationContext.Delegate delegate =
58+
new ConfigurationClassApplicationContext.Delegate();
59+
6460
/**
61+
* Register a {@link BeanDefinition} for each {@link Configuration @Configuration}
62+
* class specified by {@link #getConfigLocations()}. Enables the default set of
63+
* annotation configuration post processors, such that {@literal @Autowired},
64+
* {@literal @Required}, and associated annotations can be used within Configuration
65+
* classes.
66+
*
67+
* <p>Configuration class bean definitions are registered with generated bean
68+
* definition names unless the {@literal value} attribute is provided to the
69+
* Configuration annotation.
70+
*
6571
* @throws IllegalArgumentException if configLocations array is null or empty
6672
* @throws IOException if any one configLocation is not loadable as a class
6773
* @throws IllegalArgumentException if any one loaded class is not annotated with {@literal @Configuration}
6874
* @see #getConfigLocations()
75+
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
76+
* @see ConfigurationClassPostProcessor
77+
* @see DefaultBeanNameGenerator
78+
* @see Configuration#value()
6979
*/
7080
@Override
7181
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
@@ -75,54 +85,34 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
7585
"No config locations were specified. Is the 'contextConfigLocations' " +
7686
"context-param and/or init-param set properly in web.xml?");
7787

78-
Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>();
79-
8088
for (String configLocation : getConfigLocations()) {
8189
try {
8290
Class<?> configClass = ClassUtils.getDefaultClassLoader().loadClass(configLocation);
8391
if (AnnotationUtils.findAnnotation(configClass, Configuration.class) == null) {
8492
throw new IllegalArgumentException("Class [" + configClass.getName() + "] is not annotated with @Configuration");
8593
}
86-
configClasses.add(configClass);
94+
this.delegate.addConfigurationClass(configClass);
8795
} catch (ClassNotFoundException ex) {
8896
throw new IOException("Could not load @Configuration class [" + configLocation + "]", ex);
8997
}
9098
}
9199

92-
// @Autowired and friends must be enabled by default when processing @Configuration classes
93-
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
94-
95-
for (Class<?> configClass : configClasses) {
96-
AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition();
97-
98-
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
99-
if (!StringUtils.hasLength(name)) {
100-
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
101-
}
102-
103-
beanFactory.registerBeanDefinition(name, def);
104-
}
105-
106-
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
100+
this.delegate.loadBeanDefinitions(beanFactory);
107101
}
108102

109-
@SuppressWarnings("unchecked")
103+
/**
104+
* Return the bean instance that matches the given object type.
105+
*
106+
* @param <T>
107+
* @param requiredType type the bean must match; can be an interface or superclass.
108+
* {@literal null} is disallowed.
109+
* @return bean matching required type
110+
* @throws NoSuchBeanDefinitionException if there is not exactly one matching bean
111+
* found
112+
* @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class)
113+
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Class)
114+
*/
110115
public <T> T getBean(Class<T> requiredType) {
111-
Assert.notNull(requiredType, "requiredType may not be null");
112-
113-
Map<String, ?> beansOfType = this.getBeansOfType(requiredType);
114-
115-
switch (beansOfType.size()) {
116-
case 0:
117-
throw new NoSuchBeanDefinitionException(requiredType);
118-
case 1:
119-
return (T) beansOfType.values().iterator().next();
120-
default:
121-
throw new NoSuchBeanDefinitionException(requiredType,
122-
beansOfType.size() + " matching bean definitions found " +
123-
"(" + StringUtils.collectionToCommaDelimitedString(beansOfType.keySet()) + "). " +
124-
"Consider qualifying with getBean(Class<T> beanType, String beanName) or " +
125-
"declaring one bean definition as @Primary");
126-
}
116+
return this.delegate.getBean(requiredType, this);
127117
}
128118
}

0 commit comments

Comments
 (0)