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

@@ -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);