/*
* Copyright 2002-2017 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.configuration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import javax.inject.Provider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.NestedTestBean;
import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
/**
* Miscellaneous system tests covering {@link Bean} naming, aliases, scoping and
* error handling within {@link Configuration} class definitions.
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class ConfigurationClassProcessingTests {
@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void customBeanNameIsRespectedWhenConfiguredViaNameAttribute() {
customBeanNameIsRespected(ConfigWithBeanWithCustomName.class,
() -> ConfigWithBeanWithCustomName.testBean, "customName");
}
@Test
public void customBeanNameIsRespectedWhenConfiguredViaValueAttribute() {
customBeanNameIsRespected(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class,
() -> ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.testBean, "enigma");
}
private void customBeanNameIsRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) {
GenericApplicationContext ac = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
ac.registerBeanDefinition("config", new RootBeanDefinition(testClass));
ac.refresh();
assertSame(testBeanSupplier.get(), ac.getBean(beanName));
// method name should not be registered
exception.expect(NoSuchBeanDefinitionException.class);
ac.getBean("methodName");
}
@Test
public void aliasesAreRespectedWhenConfiguredViaNameAttribute() {
aliasesAreRespected(ConfigWithBeanWithAliases.class,
() -> ConfigWithBeanWithAliases.testBean, "name1");
}
@Test
public void aliasesAreRespectedWhenConfiguredViaValueAttribute() {
aliasesAreRespected(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class,
() -> ConfigWithBeanWithAliasesConfiguredViaValueAttribute.testBean, "enigma");
}
private void aliasesAreRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) {
TestBean testBean = testBeanSupplier.get();
BeanFactory factory = initBeanFactory(testClass);
assertSame(testBean, factory.getBean(beanName));
Arrays.stream(factory.getAliases(beanName)).map(factory::getBean).forEach(alias -> assertSame(testBean, alias));
// method name should not be registered
exception.expect(NoSuchBeanDefinitionException.class);
factory.getBean("methodName");
}
@Test // SPR-11830
public void configWithBeanWithProviderImplementation() {
GenericApplicationContext ac = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithBeanWithProviderImplementation.class));
ac.refresh();
assertSame(ac.getBean("customName"), ConfigWithBeanWithProviderImplementation.testBean);
}
@Test // SPR-11830
public void configWithSetWithProviderImplementation() {
GenericApplicationContext ac = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithSetWithProviderImplementation.class));
ac.refresh();
assertSame(ac.getBean("customName"), ConfigWithSetWithProviderImplementation.set);
}
@Test
public void testFinalBeanMethod() {
exception.expect(BeanDefinitionParsingException.class);
initBeanFactory(ConfigWithFinalBean.class);
}
@Test
public void simplestPossibleConfig() {
BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class);
String stringBean = factory.getBean("stringBean", String.class);
assertEquals(stringBean, "foo");
}
@Test
public void configWithObjectReturnType() {
BeanFactory factory = initBeanFactory(ConfigWithNonSpecificReturnTypes.class);
assertEquals(Object.class, factory.getType("stringBean"));
assertFalse(factory.isTypeMatch("stringBean", String.class));
String stringBean = factory.getBean("stringBean", String.class);
assertEquals(stringBean, "foo");
}
@Test
public void configWithFactoryBeanReturnType() {
ListableBeanFactory factory = initBeanFactory(ConfigWithNonSpecificReturnTypes.class);
assertEquals(List.class, factory.getType("factoryBean"));
assertTrue(factory.isTypeMatch("factoryBean", List.class));
assertEquals(FactoryBean.class, factory.getType("&factoryBean"));
assertTrue(factory.isTypeMatch("&factoryBean", FactoryBean.class));
assertFalse(factory.isTypeMatch("&factoryBean", BeanClassLoaderAware.class));
assertFalse(factory.isTypeMatch("&factoryBean", ListFactoryBean.class));
assertTrue(factory.getBean("factoryBean") instanceof List);
String[] beanNames = factory.getBeanNamesForType(FactoryBean.class);
assertEquals(1, beanNames.length);
assertEquals("&factoryBean", beanNames[0]);
beanNames = factory.getBeanNamesForType(BeanClassLoaderAware.class);
assertEquals(1, beanNames.length);
assertEquals("&factoryBean", beanNames[0]);
beanNames = factory.getBeanNamesForType(ListFactoryBean.class);
assertEquals(1, beanNames.length);
assertEquals("&factoryBean", beanNames[0]);
beanNames = factory.getBeanNamesForType(List.class);
assertEquals("factoryBean", beanNames[0]);
}
@Test
public void configurationWithPrototypeScopedBeans() {
BeanFactory factory = initBeanFactory(ConfigWithPrototypeBean.class);
TestBean foo = factory.getBean("foo", TestBean.class);
ITestBean bar = factory.getBean("bar", ITestBean.class);
ITestBean baz = factory.getBean("baz", ITestBean.class);
assertSame(foo.getSpouse(), bar);
assertNotSame(bar.getSpouse(), baz);
}
@Test
public void configurationWithAdaptivePrototypes() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithPrototypeBean.class, AdaptiveInjectionPoints.class);
ctx.refresh();
AdaptiveInjectionPoints adaptive = ctx.getBean(AdaptiveInjectionPoints.class);
assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName());
assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName());
adaptive = ctx.getBean(AdaptiveInjectionPoints.class);
assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName());
assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName());
ctx.close();
}
@Test
public void configurationWithPostProcessor() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithPostProcessor.class);
RootBeanDefinition placeholderConfigurer = new RootBeanDefinition(PropertyPlaceholderConfigurer.class);
placeholderConfigurer.getPropertyValues().add("properties", "myProp=myValue");
ctx.registerBeanDefinition("placeholderConfigurer", placeholderConfigurer);
ctx.refresh();
TestBean foo = ctx.getBean("foo", TestBean.class);
ITestBean bar = ctx.getBean("bar", ITestBean.class);
ITestBean baz = ctx.getBean("baz", ITestBean.class);
assertEquals("foo-processed-myValue", foo.getName());
assertEquals("bar-processed-myValue", bar.getName());
assertEquals("baz-processed-myValue", baz.getName());
SpousyTestBean listener = ctx.getBean("listenerTestBean", SpousyTestBean.class);
assertTrue(listener.refreshed);
ctx.close();
}
@Test
public void configurationWithFunctionalRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithFunctionalRegistration.class);
ctx.refresh();
assertSame(ctx.getBean("spouse"), ctx.getBean(TestBean.class).getSpouse());
assertEquals("functional", ctx.getBean(NestedTestBean.class).getCompany());
}
/**
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition}
* for each of the given {@link Configuration} {@code configClasses}, and then
* post-processes the factory using JavaConfig's {@link ConfigurationClassPostProcessor}.
* When complete, the factory is ready to service requests for any {@link Bean} methods
* declared by {@code configClasses}.
*/
private DefaultListableBeanFactory initBeanFactory(Class<?>... configClasses) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
for (Class<?> configClass : configClasses) {
String configBeanName = configClass.getName();
factory.registerBeanDefinition(configBeanName, new RootBeanDefinition(configClass));
}
ConfigurationClassPostProcessor ccpp = new ConfigurationClassPostProcessor();
ccpp.postProcessBeanDefinitionRegistry(factory);
ccpp.postProcessBeanFactory(factory);
RequiredAnnotationBeanPostProcessor rapp = new RequiredAnnotationBeanPostProcessor();
rapp.setBeanFactory(factory);
factory.addBeanPostProcessor(rapp);
factory.freezeConfiguration();
return factory;
}
@Configuration
static class ConfigWithBeanWithCustomName {
static TestBean testBean = new TestBean(ConfigWithBeanWithCustomName.class.getSimpleName());
@Bean(name = "customName")
public TestBean methodName() {
return testBean;
}
}
@Configuration
static class ConfigWithBeanWithCustomNameConfiguredViaValueAttribute {
static TestBean testBean = new TestBean(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class.getSimpleName());
@Bean("enigma")
public TestBean methodName() {
return testBean;
}
}
@Configuration
static class ConfigWithBeanWithAliases {
static TestBean testBean = new TestBean(ConfigWithBeanWithAliases.class.getSimpleName());
@Bean(name = { "name1", "alias1", "alias2", "alias3" })
public TestBean methodName() {
return testBean;
}
}
@Configuration
static class ConfigWithBeanWithAliasesConfiguredViaValueAttribute {
static TestBean testBean = new TestBean(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class.getSimpleName());
@Bean({ "enigma", "alias1", "alias2", "alias3" })
public TestBean methodName() {
return testBean;
}
}
@Configuration
static class ConfigWithBeanWithProviderImplementation implements Provider<TestBean> {
static TestBean testBean = new TestBean(ConfigWithBeanWithProviderImplementation.class.getSimpleName());
@Bean(name = "customName")
public TestBean get() {
return testBean;
}
}
@Configuration
static class ConfigWithSetWithProviderImplementation implements Provider<Set<String>> {
static Set<String> set = Collections.singleton("value");
@Bean(name = "customName")
public Set<String> get() {
return set;
}
}
@Configuration
static class ConfigWithFinalBean {
public final @Bean TestBean testBean() {
return new TestBean();
}
}
@Configuration
static class SimplestPossibleConfig {
public @Bean String stringBean() {
return "foo";
}
}
@Configuration
static class ConfigWithNonSpecificReturnTypes {
public @Bean Object stringBean() {
return "foo";
}
public @Bean FactoryBean<?> factoryBean() {
ListFactoryBean fb = new ListFactoryBean();
fb.setSourceList(Arrays.asList("element1", "element2"));
return fb;
}
}
@Configuration
static class ConfigWithPrototypeBean {
public @Bean TestBean foo() {
TestBean foo = new SpousyTestBean("foo");
foo.setSpouse(bar());
return foo;
}
public @Bean TestBean bar() {
TestBean bar = new SpousyTestBean("bar");
bar.setSpouse(baz());
return bar;
}
@Bean @Scope("prototype")
public TestBean baz() {
return new TestBean("baz");
}
@Bean @Scope("prototype")
public TestBean adaptive1(InjectionPoint ip) {
return new TestBean(ip.getMember().getName());
}
@Bean @Scope("prototype")
public TestBean adaptive2(DependencyDescriptor dd) {
return new TestBean(dd.getMember().getName());
}
}
@Scope("prototype")
static class AdaptiveInjectionPoints {
@Autowired @Qualifier("adaptive1")
public TestBean adaptiveInjectionPoint1;
public TestBean adaptiveInjectionPoint2;
@Autowired @Qualifier("adaptive2")
public void setAdaptiveInjectionPoint2(TestBean adaptiveInjectionPoint2) {
this.adaptiveInjectionPoint2 = adaptiveInjectionPoint2;
}
}
static class ConfigWithPostProcessor extends ConfigWithPrototypeBean {
@Value("${myProp}")
private String myProp;
@Bean
public POBPP beanPostProcessor() {
return new POBPP() {
String nameSuffix = "-processed-" + myProp;
public void setNameSuffix(String nameSuffix) {
this.nameSuffix = nameSuffix;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof ITestBean) {
((ITestBean) bean).setName(((ITestBean) bean).getName() + nameSuffix);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
public int getOrder() {
return 0;
}
};
}
// @Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor() {
return new BeanFactoryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
BeanDefinition bd = beanFactory.getBeanDefinition("beanPostProcessor");
bd.getPropertyValues().addPropertyValue("nameSuffix", "-processed-" + myProp);
}
};
}
@Bean
public ITestBean listenerTestBean() {
return new SpousyTestBean("listener");
}
}
public interface POBPP extends BeanPostProcessor {
}
private static class SpousyTestBean extends TestBean implements ApplicationListener<ContextRefreshedEvent> {
public boolean refreshed = false;
public SpousyTestBean(String name) {
super(name);
}
@Override
@Required
public void setSpouse(ITestBean spouse) {
super.setSpouse(spouse);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshed = true;
}
}
@Configuration
static class ConfigWithFunctionalRegistration {
@Autowired
void register(GenericApplicationContext ctx) {
ctx.registerBean("spouse", TestBean.class,
() -> new TestBean("functional"));
ctx.registerBean(TestBean.class,
() -> new TestBean(ctx.getBean("spouse", TestBean.class)),
bd -> bd.setPrimary(true));
}
@Bean
public NestedTestBean nestedTestBean(TestBean testBean) {
return new NestedTestBean(testBean.getSpouse().getName());
}
}
}