reworked ConfigurationClass(Web)ApplicationContext into AnnotationConfig(Web)ApplicationContext; revised BeansException signatures

This commit is contained in:
Juergen Hoeller 2009-10-23 17:46:16 +00:00
parent 87b2f23692
commit 8a09c8e7da
16 changed files with 344 additions and 512 deletions

View File

@ -9,6 +9,8 @@ Changes in version 3.0.0.RC2 (2009-10-30)
* updated to final versions of JSR-330 "javax.inject" and JSR-303 "javax.validation" APIs
* full compliance with the JSR-330 TCK (i.e. full compliance with the final specification)
* support for Hibernate Validator 4.0 GA (as the JSR-303 reference implementation)
* added AnnotatedBeanDefinitionReader helper for programmatic registration of annotated classes
* added AnnotationConfig(Web)ApplicationContext for convenient registration/scanning of classes
* revised MethodParameter's annotation accessor methods
* PathMatchingResourcePatternResolver leniently ignores non-existing root directories
* DefaultConversionService understands "on"/"off", "yes"/"no", "1"/"0" as boolean values
@ -21,6 +23,7 @@ Changes in version 3.0.0.RC2 (2009-10-30)
* ContentNegotiatingViewResolver works with ignoreAcceptHeader and defaultContentType as well
* re-introduced Struts 1.x support ("org.springframework.web.struts") in deprecated form
* deprecated scheduling support for JDK 1.3 Timer ("org.springframework.scheduling.timer")
* deprecated remoting support for JAX-RPC (in favor of JAX-WS)
Changes in version 3.0.0.RC1 (2009-09-25)

View File

@ -243,6 +243,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
//---------------------------------------------------------------------
public <T> T getBean(Class<T> requiredType) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
String[] beanNames = getBeanNamesForType(requiredType);
if (beanNames.length == 1) {
return getBean(beanNames[0], requiredType);
@ -251,7 +252,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return getParentBeanFactory().getBean(requiredType);
}
else {
throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " + beanNames.length);
throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " +
beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
}

View File

@ -27,10 +27,12 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
/**
* Convenient adapter for programmatic registration of annotated bean classes.
*
* This is an alternative to {@link ClassPathBeanDefinitionScanner}, applying
* the same resolution of annotations but for explicitly registered classes only.
*
* @author Juergen Hoeller
* @since 3.0
* @see AnnotationConfigApplicationContext#register
*/
public class AnnotatedBeanDefinitionReader {
@ -40,11 +42,15 @@ public class AnnotatedBeanDefinitionReader {
private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
private boolean includeAnnotationConfig = true;
/**
* Create a new AnnotatedBeanDefinitionReader for the given bean factory.
* @param registry the BeanFactory to load bean definitions into,
* in the form of a BeanDefinitionRegistry
*/
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this.registry = registry;
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
@ -65,35 +71,14 @@ public class AnnotatedBeanDefinitionReader {
/**
* Set the ScopeMetadataResolver to use for detected bean classes.
* Note that this will override any custom "scopedProxyMode" setting.
* <p>The default is an {@link AnnotationScopeMetadataResolver}.
* @see #setScopedProxyMode
*/
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.scopeMetadataResolver = scopeMetadataResolver;
}
/**
* Specify the proxy behavior for non-singleton scoped beans.
* Note that this will override any custom "scopeMetadataResolver" setting.
* <p>The default is {@link ScopedProxyMode#NO}.
* @see #setScopeMetadataResolver
*/
public void setScopedProxyMode(ScopedProxyMode scopedProxyMode) {
this.scopeMetadataResolver = new AnnotationScopeMetadataResolver(scopedProxyMode);
}
/**
* Specify whether to register annotation config post-processors.
* <p>The default is to register the post-processors. Turn this off
* to be able to ignore the annotations or to process them differently.
*/
public void setIncludeAnnotationConfig(boolean includeAnnotationConfig) {
this.includeAnnotationConfig = includeAnnotationConfig;
this.scopeMetadataResolver = (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver());
}
public void registerBeans(Class<?>... annotatedClasses) {
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
@ -112,17 +97,7 @@ public class AnnotatedBeanDefinitionReader {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
if (abd.getMetadata().isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
if (abd.getMetadata().isAnnotated(Lazy.class.getName())) {
Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value");
abd.setLazyInit(value);
}
if (abd.getMetadata().isAnnotated(DependsOn.class.getName())) {
String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value");
abd.setDependsOn(value);
}
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class.equals(qualifier)) {
@ -137,28 +112,8 @@ public class AnnotatedBeanDefinitionReader {
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = applyScopedProxyMode(definitionHolder, scopeMetadata);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
}
/**
* Apply the specified scope to the given bean definition.
* @param definition the bean definition to configure
* @param metadata the corresponding scope metadata
* @return the final bean definition to use (potentially a proxy)
*/
private BeanDefinitionHolder applyScopedProxyMode(BeanDefinitionHolder definition, ScopeMetadata metadata) {
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
return ScopedProxyCreator.createScopedProxy(definition, this.registry, proxyTargetClass);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.support.GenericApplicationContext;
/**
* Standalone application context, accepting annotated classes as input - in particular
* {@link org.springframework.context.annotation.Configuration @Configuration}-annotated
* classes, but also plain {@link org.springframework.stereotype.Component @Components}
* and JSR-330 compliant classes using {@literal javax.inject} annotations. Allows for
* registering classes one by one ({@link #register}) as well as for classpath scanning
* ({@link #scan}).
*
* <p>Useful for test harnesses or any other scenario where XML-based configuration
* is unnecessary or undesired.
*
* <p>In case of multiple Configuration classes, {@link Bean} methods defined in later
* classes will override those defined in earlier classes. This can be leveraged to
* deliberately override certain bean definitions via an extra Configuration class.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see AnnotatedBeanDefinitionReader
* @see ClassPathBeanDefinitionScanner
*/
public class AnnotationConfigApplicationContext extends GenericApplicationContext {
private final AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(this);
private final ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this);
/**
* Create a new AnnotationConfigApplicationContext that needs to be populated
* through {@link #add} calls and then manually {@link #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
}
/**
* Create a new AnnotationConfigApplicationContext, deriving bean
* definitions from the given annotated classes and automatically
* refreshing the context.
* @param annotatedClasses one or more annotated classes,
* e.g. {@link Configuration @Configuration} classes
*/
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
register(annotatedClasses);
refresh();
}
/**
* Create a new AnnotationConfigApplicationContext, scanning for bean
* definitions in the given packages and automatically refreshing the
* context.
* @param basePackages the packages to check for annotated classes
*/
public AnnotationConfigApplicationContext(String... basePackages) {
scan(basePackages);
refresh();
}
/**
* Set the BeanNameGenerator to use for detected bean classes.
* <p>Default is a {@link AnnotationBeanNameGenerator}.
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.reader.setBeanNameGenerator(beanNameGenerator);
this.scanner.setBeanNameGenerator(beanNameGenerator);
}
/**
* Set the ScopeMetadataResolver to use for detected bean classes.
* <p>The default is an {@link AnnotationScopeMetadataResolver}.
*/
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.reader.setScopeMetadataResolver(scopeMetadataResolver);
this.scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
/**
* Register an annotated class to be processed. Allows for programmatically
* building a {@link AnnotationConfigApplicationContext}. Note that
* {@link AnnotationConfigApplicationContext#refresh()} must be called in
* order for the context to fully process the new class.
* <p>Calls to {@link #register} are idempotent; adding the same
* annotated class more than once has no additional effect.
* @param annotatedClasses one or more annotated classes,
* e.g. {@link Configuration @Configuration} classes
* @see #refresh()
*/
public void register(Class<?>... annotatedClasses) {
this.reader.register(annotatedClasses);
}
/**
* Perform a scan within the specified base packages.
* @param basePackages the packages to check for annotated classes
*/
public void scan(String[] basePackages) {
this.scanner.scan(basePackages);
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.context.annotation;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
@ -160,4 +161,29 @@ public class AnnotationConfigUtils {
return new BeanDefinitionHolder(definition, beanName);
}
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
if (abd.getMetadata().isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
if (abd.getMetadata().isAnnotated(Lazy.class.getName())) {
Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value");
abd.setLazyInit(value);
}
if (abd.getMetadata().isAnnotated(DependsOn.class.getName())) {
String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value");
abd.setDependsOn(value);
}
}
static BeanDefinitionHolder applyScopedProxyMode(
ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}
}

View File

@ -46,6 +46,7 @@ import org.springframework.util.PatternMatchUtils;
* @author Mark Fisher
* @author Juergen Hoeller
* @since 2.5
* @see AnnotationConfigApplicationContext#scan
* @see org.springframework.stereotype.Component
* @see org.springframework.stereotype.Repository
* @see org.springframework.stereotype.Service
@ -145,7 +146,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* @see #setScopedProxyMode
*/
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.scopeMetadataResolver = scopeMetadataResolver;
this.scopeMetadataResolver = (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver());
}
/**
@ -206,22 +207,11 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition abd = (AnnotatedBeanDefinition) candidate;
if (abd.getMetadata().isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
if (abd.getMetadata().isAnnotated(Lazy.class.getName())) {
Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value");
abd.setLazyInit(value);
}
if (abd.getMetadata().isAnnotated(DependsOn.class.getName())) {
String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value");
abd.setDependsOn(value);
}
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = applyScopedProxyMode(definitionHolder, scopeMetadata);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
@ -300,19 +290,4 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
newDefinition.equals(existingDefinition)); // scanned equivalent class twice
}
/**
* Apply the specified scope to the given bean definition.
* @param definition the bean definition to configure
* @param metadata the corresponding scope metadata
* @return the final bean definition to use (potentially a proxy)
*/
private BeanDefinitionHolder applyScopedProxyMode(BeanDefinitionHolder definition, ScopeMetadata metadata) {
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
return ScopedProxyCreator.createScopedProxy(definition, this.registry, proxyTargetClass);
}
}

View File

@ -56,7 +56,7 @@ import org.springframework.stereotype.Component;
* @see Lazy
* @see Bean
* @see ConfigurationClassPostProcessor;
* @see ConfigurationClassApplicationContext;
* @see AnnotationConfigApplicationContext ;
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ -70,7 +70,7 @@ public @interface Configuration {
* a bean name will be automatically generated.
*
* <p>The custom name applies only if the Configuration class is picked up via
* component scanning or supplied directly to a {@link ConfigurationClassApplicationContext}.
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the Configuration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
*

View File

@ -1,192 +0,0 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.AbstractRefreshableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Standalone application context, accepting {@link Configuration @Configuration}
* -annotated class literals as input. Useful for test harnesses or any other scenario
* where XML-based configuration is unnecessary or undesired.
*
* <p>In case of multiple Configuration classes, {@link Bean}
* methods defined in later classes will override those defined in earlier
* classes. This can be leveraged to deliberately override certain bean
* definitions via an extra Configuration class.
*
* @author Chris Beams
* @since 3.0
* @see Configuration
*/
public class ConfigurationClassApplicationContext extends AbstractRefreshableApplicationContext {
private final ConfigurationClassApplicationContext.Delegate delegate =
new ConfigurationClassApplicationContext.Delegate();
/**
* Create a new {@link ConfigurationClassApplicationContext}, loading bean
* definitions from the given {@literal configClasses} and automatically
* refreshing the context. <p>Note: if zero classes are specified, the
* context will <b>not</b> be refreshed automatically, assuming that
* the user will subsequently call {@link #addConfigurationClass(Class)}
* and then manually refresh.
*
* @param configClasses zero or more {@link Configuration} classes
* @see #addConfigurationClass(Class)
* @see #refresh()
*/
public ConfigurationClassApplicationContext(Class<?>... configClasses) {
if (configClasses.length == 0) {
return;
}
for (Class<?> configClass : configClasses) {
this.addConfigurationClass(configClass);
}
this.refresh();
}
/**
* Add a {@link Configuration} class to be processed. Allows for programmatically
* building a {@link ConfigurationClassApplicationContext}. Note that
* {@link ConfigurationClassApplicationContext#refresh()} must be called in
* order for the context to process the new class. Calls to
* {@link #addConfigurationClass(Class)} are idempotent; adding the same
* Configuration class more than once has no additional effect.
* @param configClass new Configuration class to be processed.
* @see #ConfigurationClassApplicationContext(Class...)
* @see #refresh()
*/
public void addConfigurationClass(Class<?> configClass) {
this.delegate.addConfigurationClass(configClass);
}
/**
* Register a {@link BeanDefinition} for each {@link Configuration @Configuration}
* class specified. 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.
*
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
* @see ConfigurationClassPostProcessor
* @see DefaultBeanNameGenerator
* @see Configuration#value()
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
this.delegate.loadBeanDefinitions(beanFactory);
}
/**
* 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) {
return this.delegate.getBean(requiredType, this);
}
/**
* 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) {
RootBeanDefinition def = new RootBeanDefinition(configClass);
String name = AnnotationUtils.findAnnotation(configClass, Configuration.class).value();
if (!StringUtils.hasLength(name)) {
name = new DefaultBeanNameGenerator().generateBeanName(def, beanFactory);
}
beanFactory.registerBeanDefinition(name, def);
}
}
/**
* @see ConfigurationClassApplicationContext#getBean(Class)
*/
public <T> T getBean(Class<T> requiredType, ListableBeanFactory context) {
Assert.notNull(requiredType, "requiredType may not be null");
Assert.notNull(context, "context may not be null");
Map<String, T> beansOfType = context.getBeansOfType(requiredType);
switch (beansOfType.size()) {
case 0:
throw new NoSuchBeanDefinitionException(requiredType);
case 1:
return 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");
}
}
}
}

View File

@ -49,7 +49,7 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
* specific bean definition format, are {@link ClassPathXmlApplicationContext}
* and {@link FileSystemXmlApplicationContext}, which both derive from the
* common {@link AbstractXmlApplicationContext} base class;
* {@link org.springframework.context.annotation.ConfigurationClassApplicationContext}
* {@link org.springframework.context.annotation.AnnotationConfigApplicationContext}
* supports {@literal @Configuration}-annotated classes as a source of bean definitions.
*
* @author Juergen Hoeller
@ -60,7 +60,7 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
* @see AbstractXmlApplicationContext
* @see ClassPathXmlApplicationContext
* @see FileSystemXmlApplicationContext
* @see org.springframework.context.annotation.ConfigurationClassApplicationContext
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
*/
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@ -223,12 +223,12 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
* Load bean definitions into the given bean factory, typically through
* delegating to one or more bean definition readers.
* @param beanFactory the bean factory to load bean definitions into
* @throws IOException if loading of bean definition files failed
* @throws BeansException if parsing of the bean definitions failed
* @throws IOException if loading of bean definition files failed
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws IOException, BeansException;
throws BeansException, IOException;
}

View File

@ -66,7 +66,7 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

View File

@ -19,65 +19,36 @@ package org.springframework.context.annotation;
import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.matchers.JUnitMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import static org.junit.matchers.JUnitMatchers.*;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Chris Beams
*/
public class ConfigurationClassApplicationContextTests {
@Test(expected=IllegalStateException.class)
public void emptyConstructorRequiresManualRefresh() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext();
context.getBean("foo");
}
@Test
public void classesMissingConfigurationAnnotationAddedToContextAreDisallowed() {
ConfigurationClassApplicationContext ctx =
new ConfigurationClassApplicationContext(Config.class);
// should be fine
ctx.addConfigurationClass(ConfigWithCustomName.class);
// should cause immediate failure (no refresh necessary)
try {
ctx.addConfigurationClass(ConfigMissingAnnotation.class);
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
equalTo("Class [" + ConfigMissingAnnotation.class.getName() + "] " +
"is not annotated with @Configuration"));
}
}
@Test(expected=IllegalArgumentException.class)
public void classesMissingConfigurationAnnotationSuppliedToConstructorAreDisallowed() {
new ConfigurationClassApplicationContext(ConfigMissingAnnotation.class);
}
@Test(expected=IllegalArgumentException.class)
public void nullGetBeanParameterIsDisallowed() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(Config.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.getBean((Class<?>)null);
}
@Test
public void addConfigurationClass() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext();
context.addConfigurationClass(Config.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class, NameConfig.class);
context.refresh();
context.getBean("testBean");
context.addConfigurationClass(NameConfig.class);
context.refresh();
context.getBean("name");
}
@Test
public void getBeanByType() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(Config.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
TestBean testBean = context.getBean(TestBean.class);
assertNotNull("getBean() should not return null", testBean);
assertThat(testBean.name, equalTo("foo"));
@ -89,10 +60,10 @@ public class ConfigurationClassApplicationContextTests {
*/
@Test
public void defaultConfigClassBeanNameIsGeneratedProperly() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(Config.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// attempt to retrieve the instance by its generated bean name
Config configObject = (Config) context.getBean(Config.class.getName() + "#0");
Config configObject = (Config) context.getBean("configurationClassApplicationContextTests.Config");
assertNotNull(configObject);
}
@ -102,8 +73,8 @@ public class ConfigurationClassApplicationContextTests {
*/
@Test
public void explicitConfigClassBeanNameIsRespected() {
ConfigurationClassApplicationContext context =
new ConfigurationClassApplicationContext(ConfigWithCustomName.class);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ConfigWithCustomName.class);
// attempt to retrieve the instance by its specified name
ConfigWithCustomName configObject =
@ -113,7 +84,7 @@ public class ConfigurationClassApplicationContextTests {
@Test
public void getBeanByTypeRaisesNoSuchBeanDefinitionException() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(Config.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// attempt to retrieve a bean that does not exist
Class<?> targetType = java.util.regex.Pattern.class;
@ -121,7 +92,7 @@ public class ConfigurationClassApplicationContextTests {
Object bean = context.getBean(targetType);
fail("should have thrown NoSuchBeanDefinitionException, instead got: " + bean);
} catch (NoSuchBeanDefinitionException ex) {
assertThat(ex.getMessage(), equalTo(
assertThat(ex.getMessage(), containsString(
format("No unique bean of type [%s] is defined", targetType.getName())));
}
}
@ -129,7 +100,7 @@ public class ConfigurationClassApplicationContextTests {
@SuppressWarnings("unchecked")
@Test
public void getBeanByTypeAmbiguityRaisesException() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(TwoTestBeanConfig.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TwoTestBeanConfig.class);
try {
context.getBean(TestBean.class);
@ -137,10 +108,8 @@ public class ConfigurationClassApplicationContextTests {
assertThat(ex.getMessage(),
allOf(
containsString("No unique bean of type [" + TestBean.class.getName() + "] is defined"),
containsString("2 matching bean definitions found"),
containsString("tb1"),
containsString("tb2"),
containsString("Consider qualifying with")
containsString("tb2")
)
);
}
@ -148,7 +117,7 @@ public class ConfigurationClassApplicationContextTests {
@Test
public void autowiringIsEnabledByDefault() {
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(AutowiredConfig.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
assertThat(context.getBean(TestBean.class).name, equalTo("foo"));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -77,7 +77,7 @@ public class XmlPortletApplicationContext extends AbstractRefreshablePortletAppl
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

View File

@ -0,0 +1,136 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.support;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScopeMetadataResolver;
/**
* {@link org.springframework.web.context.WebApplicationContext} implementation
* which accepts annotated classes as input - in particular
* {@link org.springframework.context.annotation.Configuration @Configuration}-annotated
* classes, but also plain {@link org.springframework.stereotype.Component @Components}
* and JSR-330 compliant classes using {@literal javax.inject} annotations. Allows for
* registering classes one by one (specifying class names as config location) as well
* as for classpath scanning (specifying base packages as config location).
*
* <p>This is essentially the equivalent of
* {@link org.springframework.context.annotation.AnnotationConfigApplicationContext}
* for a web environment.
*
* <p>To make use of this application context, the "contextClass" context-param for
* ContextLoader and/or "contextClass" init-param for FrameworkServlet must be set to
* the fully-qualified name of this class.
*
* <p>Unlike {@link XmlWebApplicationContext}, no default configuration class locations
* are assumed. Rather, it is a requirement to set the "contextConfigLocation"
* context-param for ContextLoader and/or "contextConfigLocation" init-param for
* FrameworkServlet.
*
* <p>Note: In case of multiple {@literal @Configuration} classes, later {@literal @Bean}
* definitions will override ones defined in earlier loaded files. This can be leveraged
* to deliberately override certain bean definitions via an extra Configuration class.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
*/
public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/**
* Register a {@link BeanDefinition} for each class specified by {@link #getConfigLocations()},
* or scan each specified package for annotated classes. Enables the default set of
* annotation configuration post processors, such that {@literal @Autowired},
* {@literal @Required}, and associated annotations can be used.
* <p>Configuration class bean definitions are registered with generated bean definition
* names unless the {@literal value} attribute is provided to the stereotype annotation.
* @see #getConfigLocations()
* @see AnnotatedBeanDefinitionReader
* @see ClassPathBeanDefinitionScanner
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(beanFactory);
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
if (beanNameGenerator != null) {
reader.setBeanNameGenerator(beanNameGenerator);
scanner.setBeanNameGenerator(beanNameGenerator);
}
if (scopeMetadataResolver != null) {
reader.setScopeMetadataResolver(scopeMetadataResolver);
scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
try {
Class<?> clazz = getClassLoader().loadClass(configLocation);
if (logger.isInfoEnabled()) {
logger.info("Successfully resolved class for [" + configLocation + "]");
}
reader.register(clazz);
}
catch (ClassNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not load class for config location [" + configLocation +
"] - trying package scan. " + ex);
}
int count = scanner.scan(configLocation);
if (logger.isInfoEnabled()) {
if (count == 0) {
logger.info("No annotated classes found for specified class/package [" + configLocation + "]");
}
else {
logger.info("Found " + count + " annotated classes in package [" + configLocation + "]");
}
}
}
}
}
}
/**
* Provide a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader}
* and/or {@link ClassPathBeanDefinitionScanner}, if any.
* <p>Default is {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}.
* @see AnnotatedBeanDefinitionReader#setBeanNameGenerator
* @see ClassPathBeanDefinitionScanner#setBeanNameGenerator
*/
protected BeanNameGenerator getBeanNameGenerator() {
return null;
}
/**
* Provide a custom {@link ScopeMetadataResolver} for use with {@link AnnotatedBeanDefinitionReader}
* and/or {@link ClassPathBeanDefinitionScanner}, if any.
* <p>Default is {@link org.springframework.context.annotation.AnnotationScopeMetadataResolver}.
* @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver
* @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver
*/
protected ScopeMetadataResolver getScopeMetadataResolver() {
return null;
}
}

View File

@ -1,119 +0,0 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.support;
import java.io.IOException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link org.springframework.web.context.WebApplicationContext} implementation
* which takes its configuration from {@link Configuration @Configuration} classes.
* This is essentially the equivalent of
* {@link org.springframework.context.annotation.ConfigurationClassApplicationContext}
* for a web environment.
*
* <p>To make use of this application context, the "contextClass" context-param for
* ContextLoader and/or "contextClass" init-param for FrameworkServlet must be set to
* the fully-qualified name of this class.
*
* <p>Unlike {@link XmlWebApplicationContext}, no default configuration class locations
* are assumed. Rather, it is a requirement to set the "contextConfigLocation"
* context-param for ContextLoader and/or "contextConfigLocation" init-param for
* FrameworkServlet. If these params are not set, an IllegalArgumentException will be thrown.
*
* <p>Note: In case of multiple {@literal @Configuration} classes, later {@literal @Bean}
* definitions will override ones defined in earlier loaded files. This can be leveraged
* to deliberately override certain bean definitions via an extra Configuration class.
*
* @author Chris Beams
* @since 3.0
* @see ConfigurationClassApplicationContext
* @see ConfigurationClassApplicationContext.Delegate
*/
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 IOException if any one configLocation is not loadable as a class
* @throws IllegalArgumentException if any one loaded class is not annotated with {@literal @Configuration}
* @see #getConfigLocations()
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
* @see ConfigurationClassPostProcessor
* @see DefaultBeanNameGenerator
* @see Configuration#value()
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws IOException, BeansException {
Assert.notEmpty(getConfigLocations(),
"No config locations were specified. Is the 'contextConfigLocations' " +
"context-param and/or init-param set properly in web.xml?");
for (String configLocation : getConfigLocations()) {
try {
Class<?> configClass = ClassUtils.getDefaultClassLoader().loadClass(configLocation);
this.delegate.addConfigurationClass(configClass);
} catch (ClassNotFoundException ex) {
throw new IOException("Could not load @Configuration class [" + configLocation + "]", ex);
}
}
this.delegate.loadBeanDefinitions(beanFactory);
}
/**
* 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) {
return this.delegate.getBean(requiredType, this);
}
}

View File

@ -78,7 +78,7 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
@ -111,14 +111,13 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC
* therefore this method is just supposed to load and/or register bean definitions.
* <p>Delegates to a ResourcePatternResolver for resolving location patterns
* into Resource instances.
* @throws org.springframework.beans.BeansException in case of bean registration errors
* @throws java.io.IOException if the required XML document isn't found
* @see #refreshBeanFactory
* @see #getConfigLocations
* @see #getResources
* @see #getResourcePatternResolver
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {

View File

@ -16,25 +16,20 @@
package org.springframework.web.context.support;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.containsString;
import java.io.IOException;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Chris Beams
*/
public class ConfigurationClassWebApplicationContextTests {
@Test
public void testSingleWellFormedConfigLocation() {
ConfigurationClassWebApplicationContext ctx = new ConfigurationClassWebApplicationContext();
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.setConfigLocation(Config.class.getName());
ctx.refresh();
@ -42,45 +37,6 @@ public class ConfigurationClassWebApplicationContextTests {
assertNotNull(bean);
}
@Test
public void testWithoutExplicitlySettingConfigLocations() {
ConfigurationClassWebApplicationContext ctx = new ConfigurationClassWebApplicationContext();
try {
ctx.refresh();
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString(
"Is the 'contextConfigLocations' context-param " +
"and/or init-param set properly in web.xml?"));
}
}
@Test
public void testMalformedConfigLocation() {
ConfigurationClassWebApplicationContext ctx = new ConfigurationClassWebApplicationContext();
ctx.setConfigLocation("garbage");
try {
ctx.refresh();
fail("expected exception");
} catch (ApplicationContextException ex) {
assertThat(ex.getCause(), is(IOException.class));
assertThat(ex.getCause().getMessage(),
containsString("Could not load @Configuration class"));
}
}
@Test
public void testNonConfigurationClass() {
ConfigurationClassWebApplicationContext ctx = new ConfigurationClassWebApplicationContext();
ctx.setConfigLocation(NotConfigurationAnnotated.class.getName());
try {
ctx.refresh();
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
containsString("is not annotated with @Configuration"));
}
}
@Configuration
static class Config {
@ -92,7 +48,6 @@ public class ConfigurationClassWebApplicationContextTests {
static class NotConfigurationAnnotated { }
static class TestBean {
static class TestBean { }
}
}