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
This commit is contained in:
parent
ab10d37fae
commit
cbadf350e1
|
|
@ -28,6 +28,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
|
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.context.support.AbstractApplicationContext;
|
||||||
import org.springframework.context.support.AbstractRefreshableApplicationContext;
|
import org.springframework.context.support.AbstractRefreshableApplicationContext;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -35,8 +36,8 @@ import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standalone application context, accepting {@link Configuration}-annotated
|
* Standalone application context, accepting {@link Configuration @Configuration}
|
||||||
* class literals as input. Useful for test harnesses or any other scenario
|
* -annotated class literals as input. Useful for test harnesses or any other scenario
|
||||||
* where XML-based configuration is unnecessary or undesired.
|
* where XML-based configuration is unnecessary or undesired.
|
||||||
*
|
*
|
||||||
* <p>In case of multiple Configuration classes, {@link Bean}
|
* <p>In case of multiple Configuration classes, {@link Bean}
|
||||||
|
|
@ -50,7 +51,8 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
public class ConfigurationClassApplicationContext extends AbstractRefreshableApplicationContext {
|
public class ConfigurationClassApplicationContext extends AbstractRefreshableApplicationContext {
|
||||||
|
|
||||||
private final Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>();
|
private final ConfigurationClassApplicationContext.Delegate delegate =
|
||||||
|
new ConfigurationClassApplicationContext.Delegate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link ConfigurationClassApplicationContext}, loading bean
|
* Create a new {@link ConfigurationClassApplicationContext}, loading bean
|
||||||
|
|
@ -70,7 +72,7 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Class<?> configClass : configClasses) {
|
for (Class<?> configClass : configClasses) {
|
||||||
addConfigurationClass(configClass);
|
this.addConfigurationClass(configClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
|
@ -88,10 +90,7 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp
|
||||||
* @see #refresh()
|
* @see #refresh()
|
||||||
*/
|
*/
|
||||||
public void addConfigurationClass(Class<?> configClass) {
|
public void addConfigurationClass(Class<?> configClass) {
|
||||||
Assert.notNull(
|
this.delegate.addConfigurationClass(configClass);
|
||||||
AnnotationUtils.findAnnotation(configClass, Configuration.class),
|
|
||||||
"Class [" + configClass.getName() + "] is not annotated with @Configuration");
|
|
||||||
this.configClasses.add(configClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -100,30 +99,18 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp
|
||||||
* processors, such that {@literal @Autowired}, {@literal @Required}, and associated
|
* processors, such that {@literal @Autowired}, {@literal @Required}, and associated
|
||||||
* annotations can be used within Configuration classes.
|
* annotations can be used within Configuration classes.
|
||||||
*
|
*
|
||||||
* <p>Configuration class bean definitions are registered with generated bean definition names.
|
* <p>Configuration class bean definitions are registered with generated bean definition
|
||||||
|
* names unless the {@literal value} attribute is provided to the Configuration annotation.
|
||||||
*
|
*
|
||||||
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||||
* @see ConfigurationClassPostProcessor
|
* @see ConfigurationClassPostProcessor
|
||||||
|
* @see DefaultBeanNameGenerator
|
||||||
|
* @see Configuration#value()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
|
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
|
||||||
throws IOException, BeansException {
|
throws IOException, BeansException {
|
||||||
|
this.delegate.loadBeanDefinitions(beanFactory);
|
||||||
// @Autowired and friends must be enabled by default when processing @Configuration classes
|
|
||||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
|
||||||
|
|
||||||
for (Class<?> configClass : configClasses) {
|
|
||||||
AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition();
|
|
||||||
|
|
||||||
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
|
|
||||||
if (!StringUtils.hasLength(name)) {
|
|
||||||
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
beanFactory.registerBeanDefinition(name, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,24 +125,78 @@ public class ConfigurationClassApplicationContext extends AbstractRefreshableApp
|
||||||
* @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class)
|
* @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class)
|
||||||
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Class)
|
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Class)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> T getBean(Class<T> requiredType) {
|
public <T> T getBean(Class<T> requiredType) {
|
||||||
Assert.notNull(requiredType, "requiredType may not be null");
|
return this.delegate.getBean(requiredType, this);
|
||||||
|
|
||||||
Map<String, ?> beansOfType = this.getBeansOfType(requiredType);
|
|
||||||
|
|
||||||
switch (beansOfType.size()) {
|
|
||||||
case 0:
|
|
||||||
throw new NoSuchBeanDefinitionException(requiredType);
|
|
||||||
case 1:
|
|
||||||
return (T) beansOfType.values().iterator().next();
|
|
||||||
default:
|
|
||||||
throw new NoSuchBeanDefinitionException(requiredType,
|
|
||||||
beansOfType.size() + " matching bean definitions found " +
|
|
||||||
"(" + StringUtils.collectionToCommaDelimitedString(beansOfType.keySet()) + "). " +
|
|
||||||
"Consider qualifying with getBean(Class<T> beanType, String beanName) or " +
|
|
||||||
"declaring one bean definition as @Primary");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates behavior common to {@link ConfigurationClassApplicationContext}
|
||||||
|
* and its {@link org.springframework.web.context.support.ConfigurationClassWebApplicationContext}
|
||||||
|
* variant. Both classes already participate in mutually exclusive superclass
|
||||||
|
* hierarchies, and this class allows for avoiding what would otherwise be a multiple
|
||||||
|
* inheritance problem through composition.
|
||||||
|
*
|
||||||
|
* <p><strong>This class is public by necessity but should be considered private and
|
||||||
|
* subject to change without notice.</strong>
|
||||||
|
*/
|
||||||
|
public static class Delegate {
|
||||||
|
|
||||||
|
private final Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ConfigurationClassApplicationContext#addConfigurationClass(Class)
|
||||||
|
*/
|
||||||
|
public void addConfigurationClass(Class<?> configClass) {
|
||||||
|
Assert.notNull(
|
||||||
|
AnnotationUtils.findAnnotation(configClass, Configuration.class),
|
||||||
|
"Class [" + configClass.getName() + "] is not annotated with @Configuration");
|
||||||
|
this.configClasses.add(configClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ConfigurationClassApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory)
|
||||||
|
*/
|
||||||
|
public void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
|
||||||
|
// @Autowired and friends must be enabled by default when processing @Configuration classes
|
||||||
|
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
||||||
|
|
||||||
|
for (Class<?> configClass : this.configClasses) {
|
||||||
|
AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition();
|
||||||
|
|
||||||
|
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
|
||||||
|
if (!StringUtils.hasLength(name)) {
|
||||||
|
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
beanFactory.registerBeanDefinition(name, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ConfigurationClassApplicationContext#getBean(Class)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T getBean(Class<T> requiredType, AbstractApplicationContext context) {
|
||||||
|
Assert.notNull(requiredType, "requiredType may not be null");
|
||||||
|
Assert.notNull(context, "context may not be null");
|
||||||
|
|
||||||
|
Map<String, ?> beansOfType = context.getBeansOfType(requiredType);
|
||||||
|
|
||||||
|
switch (beansOfType.size()) {
|
||||||
|
case 0:
|
||||||
|
throw new NoSuchBeanDefinitionException(requiredType);
|
||||||
|
case 1:
|
||||||
|
return (T) beansOfType.values().iterator().next();
|
||||||
|
default:
|
||||||
|
throw new NoSuchBeanDefinitionException(requiredType,
|
||||||
|
beansOfType.size() + " matching bean definitions found " +
|
||||||
|
"(" + StringUtils.collectionToCommaDelimitedString(beansOfType.keySet()) + "). " +
|
||||||
|
"Consider qualifying with getBean(Class<T> beanType, String beanName) or " +
|
||||||
|
"declaring one bean definition as @Primary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,23 +17,14 @@
|
||||||
package org.springframework.web.context.support;
|
package org.springframework.web.context.support;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|
||||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
|
||||||
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
|
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
|
import org.springframework.context.annotation.ConfigurationClassApplicationContext;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,15 +48,34 @@ import org.springframework.util.StringUtils;
|
||||||
* to deliberately override certain bean definitions via an extra Configuration class.
|
* to deliberately override certain bean definitions via an extra Configuration class.
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @see org.springframework.context.annotation.ConfigurationClassApplicationContext
|
* @since 3.0
|
||||||
|
* @see ConfigurationClassApplicationContext
|
||||||
|
* @see ConfigurationClassApplicationContext.Delegate
|
||||||
*/
|
*/
|
||||||
public class ConfigurationClassWebApplicationContext extends AbstractRefreshableWebApplicationContext {
|
public class ConfigurationClassWebApplicationContext extends AbstractRefreshableWebApplicationContext {
|
||||||
|
|
||||||
|
private final ConfigurationClassApplicationContext.Delegate delegate =
|
||||||
|
new ConfigurationClassApplicationContext.Delegate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Register a {@link BeanDefinition} for each {@link Configuration @Configuration}
|
||||||
|
* class specified by {@link #getConfigLocations()}. Enables the default set of
|
||||||
|
* annotation configuration post processors, such that {@literal @Autowired},
|
||||||
|
* {@literal @Required}, and associated annotations can be used within Configuration
|
||||||
|
* classes.
|
||||||
|
*
|
||||||
|
* <p>Configuration class bean definitions are registered with generated bean
|
||||||
|
* definition names unless the {@literal value} attribute is provided to the
|
||||||
|
* Configuration annotation.
|
||||||
|
*
|
||||||
* @throws IllegalArgumentException if configLocations array is null or empty
|
* @throws IllegalArgumentException if configLocations array is null or empty
|
||||||
* @throws IOException if any one configLocation is not loadable as a class
|
* @throws IOException if any one configLocation is not loadable as a class
|
||||||
* @throws IllegalArgumentException if any one loaded class is not annotated with {@literal @Configuration}
|
* @throws IllegalArgumentException if any one loaded class is not annotated with {@literal @Configuration}
|
||||||
* @see #getConfigLocations()
|
* @see #getConfigLocations()
|
||||||
|
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||||
|
* @see ConfigurationClassPostProcessor
|
||||||
|
* @see DefaultBeanNameGenerator
|
||||||
|
* @see Configuration#value()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
|
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
|
||||||
|
|
@ -75,54 +85,34 @@ public class ConfigurationClassWebApplicationContext extends AbstractRefreshable
|
||||||
"No config locations were specified. Is the 'contextConfigLocations' " +
|
"No config locations were specified. Is the 'contextConfigLocations' " +
|
||||||
"context-param and/or init-param set properly in web.xml?");
|
"context-param and/or init-param set properly in web.xml?");
|
||||||
|
|
||||||
Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>();
|
|
||||||
|
|
||||||
for (String configLocation : getConfigLocations()) {
|
for (String configLocation : getConfigLocations()) {
|
||||||
try {
|
try {
|
||||||
Class<?> configClass = ClassUtils.getDefaultClassLoader().loadClass(configLocation);
|
Class<?> configClass = ClassUtils.getDefaultClassLoader().loadClass(configLocation);
|
||||||
if (AnnotationUtils.findAnnotation(configClass, Configuration.class) == null) {
|
if (AnnotationUtils.findAnnotation(configClass, Configuration.class) == null) {
|
||||||
throw new IllegalArgumentException("Class [" + configClass.getName() + "] is not annotated with @Configuration");
|
throw new IllegalArgumentException("Class [" + configClass.getName() + "] is not annotated with @Configuration");
|
||||||
}
|
}
|
||||||
configClasses.add(configClass);
|
this.delegate.addConfigurationClass(configClass);
|
||||||
} catch (ClassNotFoundException ex) {
|
} catch (ClassNotFoundException ex) {
|
||||||
throw new IOException("Could not load @Configuration class [" + configLocation + "]", ex);
|
throw new IOException("Could not load @Configuration class [" + configLocation + "]", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Autowired and friends must be enabled by default when processing @Configuration classes
|
this.delegate.loadBeanDefinitions(beanFactory);
|
||||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
|
||||||
|
|
||||||
for (Class<?> configClass : configClasses) {
|
|
||||||
AbstractBeanDefinition def = BeanDefinitionBuilder.rootBeanDefinition(configClass).getBeanDefinition();
|
|
||||||
|
|
||||||
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
|
|
||||||
if (!StringUtils.hasLength(name)) {
|
|
||||||
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
beanFactory.registerBeanDefinition(name, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
/**
|
||||||
|
* Return the bean instance that matches the given object type.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* @param requiredType type the bean must match; can be an interface or superclass.
|
||||||
|
* {@literal null} is disallowed.
|
||||||
|
* @return bean matching required type
|
||||||
|
* @throws NoSuchBeanDefinitionException if there is not exactly one matching bean
|
||||||
|
* found
|
||||||
|
* @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class)
|
||||||
|
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Class)
|
||||||
|
*/
|
||||||
public <T> T getBean(Class<T> requiredType) {
|
public <T> T getBean(Class<T> requiredType) {
|
||||||
Assert.notNull(requiredType, "requiredType may not be null");
|
return this.delegate.getBean(requiredType, this);
|
||||||
|
|
||||||
Map<String, ?> beansOfType = this.getBeansOfType(requiredType);
|
|
||||||
|
|
||||||
switch (beansOfType.size()) {
|
|
||||||
case 0:
|
|
||||||
throw new NoSuchBeanDefinitionException(requiredType);
|
|
||||||
case 1:
|
|
||||||
return (T) beansOfType.values().iterator().next();
|
|
||||||
default:
|
|
||||||
throw new NoSuchBeanDefinitionException(requiredType,
|
|
||||||
beansOfType.size() + " matching bean definitions found " +
|
|
||||||
"(" + StringUtils.collectionToCommaDelimitedString(beansOfType.keySet()) + "). " +
|
|
||||||
"Consider qualifying with getBean(Class<T> beanType, String beanName) or " +
|
|
||||||
"declaring one bean definition as @Primary");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue