Browse Source
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@2048 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/head
6 changed files with 428 additions and 5 deletions
@ -0,0 +1,159 @@ |
|||||||
|
/* |
||||||
|
* 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.io.IOException; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.beans.BeansException; |
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException; |
||||||
|
import org.springframework.beans.factory.config.BeanDefinition; |
||||||
|
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.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}-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 Set<Class<?>> configClasses = new LinkedHashSet<Class<?>>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 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) { |
||||||
|
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) { |
||||||
|
Assert.notNull( |
||||||
|
AnnotationUtils.findAnnotation(configClass, Configuration.class), |
||||||
|
"Class [" + configClass.getName() + "] is not annotated with @Configuration"); |
||||||
|
this.configClasses.add(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. |
||||||
|
* |
||||||
|
* @see AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry) |
||||||
|
* @see ConfigurationClassPostProcessor |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) |
||||||
|
throws IOException, BeansException { |
||||||
|
|
||||||
|
// @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); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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) |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public <T> T getBean(Class<T> requiredType) { |
||||||
|
Assert.notNull(requiredType, "requiredType may not be null"); |
||||||
|
|
||||||
|
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"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,231 @@ |
|||||||
|
/* |
||||||
|
* 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 static java.lang.String.format; |
||||||
|
import static org.hamcrest.CoreMatchers.equalTo; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
import static org.junit.Assert.assertThat; |
||||||
|
import static org.junit.Assert.fail; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
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); |
||||||
|
context.getBean((Class<?>)null); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void addConfigurationClass() { |
||||||
|
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(); |
||||||
|
context.addConfigurationClass(Config.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); |
||||||
|
TestBean testBean = context.getBean(TestBean.class); |
||||||
|
assertNotNull("getBean() should not return null", testBean); |
||||||
|
assertThat(testBean.name, equalTo("foo")); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests that Configuration classes are registered according to convention |
||||||
|
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator#generateBeanName |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void defaultConfigClassBeanNameIsGeneratedProperly() { |
||||||
|
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(Config.class); |
||||||
|
|
||||||
|
// attempt to retrieve the instance by its generated bean name
|
||||||
|
Config configObject = (Config) context.getBean(Config.class.getName() + "#0"); |
||||||
|
assertNotNull(configObject); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests that specifying @Configuration(value="foo") results in registering |
||||||
|
* the configuration class with bean name 'foo'. |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void explicitConfigClassBeanNameIsRespected() { |
||||||
|
ConfigurationClassApplicationContext context = |
||||||
|
new ConfigurationClassApplicationContext(ConfigWithCustomName.class); |
||||||
|
|
||||||
|
// attempt to retrieve the instance by its specified name
|
||||||
|
ConfigWithCustomName configObject = |
||||||
|
(ConfigWithCustomName) context.getBean("customConfigBeanName"); |
||||||
|
assertNotNull(configObject); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getBeanByTypeRaisesNoSuchBeanDefinitionException() { |
||||||
|
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(Config.class); |
||||||
|
|
||||||
|
// attempt to retrieve a bean that does not exist
|
||||||
|
Class<?> targetType = java.util.regex.Pattern.class; |
||||||
|
try { |
||||||
|
Object bean = context.getBean(targetType); |
||||||
|
fail("should have thrown NoSuchBeanDefinitionException, instead got: " + bean); |
||||||
|
} catch (NoSuchBeanDefinitionException ex) { |
||||||
|
assertThat(ex.getMessage(), equalTo( |
||||||
|
format("No unique bean of type [%s] is defined", targetType.getName()))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getBeanByTypeAmbiguityRaisesException() { |
||||||
|
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(TwoTestBeanConfig.class); |
||||||
|
|
||||||
|
try { |
||||||
|
context.getBean(TestBean.class); |
||||||
|
} catch (RuntimeException ex) { |
||||||
|
assertThat(ex.getMessage(), equalTo( |
||||||
|
"No unique bean of type [" + TestBean.class.getName() + "] is defined: " + |
||||||
|
"2 matching bean definitions found (tb1,tb2). Consider qualifying with " + |
||||||
|
"getBean(Class<T> beanType, String beanName) or declaring one bean definition as " + |
||||||
|
"@" + Primary.class.getSimpleName())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void autowiringIsEnabledByDefault() { |
||||||
|
ConfigurationClassApplicationContext context = new ConfigurationClassApplicationContext(AutowiredConfig.class); |
||||||
|
assertThat(context.getBean(TestBean.class).name, equalTo("foo")); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Configuration |
||||||
|
static class Config { |
||||||
|
@Bean |
||||||
|
public TestBean testBean() { |
||||||
|
TestBean testBean = new TestBean(); |
||||||
|
testBean.name = "foo"; |
||||||
|
return testBean; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration("customConfigBeanName") |
||||||
|
static class ConfigWithCustomName { |
||||||
|
@Bean |
||||||
|
public TestBean testBean() { |
||||||
|
return new TestBean(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class ConfigMissingAnnotation { |
||||||
|
@Bean |
||||||
|
public TestBean testBean() { |
||||||
|
return new TestBean(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration |
||||||
|
static class TwoTestBeanConfig { |
||||||
|
@Bean TestBean tb1() { return new TestBean(); } |
||||||
|
@Bean TestBean tb2() { return new TestBean(); } |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration |
||||||
|
static class NameConfig { |
||||||
|
@Bean String name() { return "foo"; } |
||||||
|
} |
||||||
|
|
||||||
|
@Configuration |
||||||
|
@Import(NameConfig.class) |
||||||
|
static class AutowiredConfig { |
||||||
|
@Autowired String autowiredName; |
||||||
|
|
||||||
|
@Bean TestBean testBean() { |
||||||
|
TestBean testBean = new TestBean(); |
||||||
|
testBean.name = autowiredName; |
||||||
|
return testBean; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class TestBean { |
||||||
|
String name; |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
final int prime = 31; |
||||||
|
int result = 1; |
||||||
|
result = prime * result + ((name == null) ? 0 : name.hashCode()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (this == obj) |
||||||
|
return true; |
||||||
|
if (obj == null) |
||||||
|
return false; |
||||||
|
if (getClass() != obj.getClass()) |
||||||
|
return false; |
||||||
|
TestBean other = (TestBean) obj; |
||||||
|
if (name == null) { |
||||||
|
if (other.name != null) |
||||||
|
return false; |
||||||
|
} else if (!name.equals(other.name)) |
||||||
|
return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -1,7 +1,12 @@ |
|||||||
#Wed Jul 15 00:01:30 PDT 2009 |
#Sun Oct 04 15:30:45 PDT 2009 |
||||||
eclipse.preferences.version=1 |
eclipse.preferences.version=1 |
||||||
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled |
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 |
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 |
||||||
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve |
||||||
org.eclipse.jdt.core.compiler.compliance=1.5 |
org.eclipse.jdt.core.compiler.compliance=1.5 |
||||||
|
org.eclipse.jdt.core.compiler.debug.lineNumber=generate |
||||||
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate |
||||||
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate |
||||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error |
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error |
||||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error |
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error |
||||||
org.eclipse.jdt.core.compiler.source=1.5 |
org.eclipse.jdt.core.compiler.source=1.5 |
||||||
|
|||||||
Loading…
Reference in new issue