Chris Beams
15 years ago
6 changed files with 428 additions and 5 deletions
@ -0,0 +1,159 @@
@@ -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 @@
@@ -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 @@
@@ -1,7 +1,12 @@
|
||||
#Wed Jul 15 00:01:30 PDT 2009 |
||||
#Sun Oct 04 15:30:45 PDT 2009 |
||||
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.unusedLocal=preserve |
||||
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.enumIdentifier=error |
||||
org.eclipse.jdt.core.compiler.source=1.5 |
||||
|
Loading…
Reference in new issue