Introduce 'value' alias for @Bean's 'name' attribute

In order to simplify configuration for use cases involving @Bean where
only a bean name or aliases are supplied as an attribute, this commit
introduces a new 'value' attribute that is an @AliasFor 'name' in @Bean.

Issue: SPR-14728
This commit is contained in:
Sam Brannen 2016-09-17 16:01:08 +02:00
parent 778ef02680
commit 8f62b63663
4 changed files with 114 additions and 53 deletions

View File

@ -24,6 +24,7 @@ import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.annotation.AliasFor;
/** /**
* Indicates that a method produces a bean to be managed by the Spring container. * Indicates that a method produces a bean to be managed by the Spring container.
@ -44,15 +45,15 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
* *
* <h3>Bean Names</h3> * <h3>Bean Names</h3>
* *
* <p>While a {@link #name() name} attribute is available, the default strategy for * <p>While a {@link #name} attribute is available, the default strategy for
* determining the name of a bean is to use the name of the {@code @Bean} method. * determining the name of a bean is to use the name of the {@code @Bean} method. This
* This is convenient and intuitive, but if explicit naming is desired, the * is convenient and intuitive, but if explicit naming is desired, the {@code name}
* {@code name} attribute may be used. Also note that {@code name} accepts an array * attribute (or its alias {@code value}) may be used. Also note that {@code name}
* of Strings. This is in order to allow for specifying multiple names (i.e., aliases) * accepts an array of Strings. This is in order to allow for specifying multiple names
* for a single bean. * (i.e., aliases) for a single bean.
* *
* <pre class="code"> * <pre class="code">
* &#064;Bean(name={"b1","b2"}) // bean available as 'b1' and 'b2', but not 'myBean' * &#064;Bean({"b1","b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
* public MyBean myBean() { * public MyBean myBean() {
* // instantiate and configure MyBean obj * // instantiate and configure MyBean obj
* return obj; * return obj;
@ -190,10 +191,24 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
public @interface Bean { public @interface Bean {
/** /**
* The name of this bean, or if plural, aliases for this bean. If left unspecified * Alias for {@link #name}.
* the name of the bean is the name of the annotated method. If specified, the method * <p>Intended to be used when no other attributes are needed, for example:
* name is ignored. * {@code @Bean("customBeanName")}.
* @since 5.0
* @see #name
*/ */
@AliasFor("name")
String[] value() default {};
/**
* The name of this bean, or if plural, aliases for this bean.
* <p>If left unspecified the name of the bean is the name of the annotated method.
* If specified, the method name is ignored.
* <p>The bean name and aliases may also be configured via the {@link #value}
* attribute if no other attributes are declared.
* @see #value
*/
@AliasFor("value")
String[] name() default {}; String[] name() default {};
/** /**

View File

@ -20,9 +20,13 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import javax.inject.Provider; import javax.inject.Provider;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
@ -65,6 +69,7 @@ import static org.junit.Assert.*;
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
*/ */
public class ConfigurationClassProcessingTests { public class ConfigurationClassProcessingTests {
@ -92,40 +97,57 @@ public class ConfigurationClassProcessingTests {
} }
@Test @Rule
public void customBeanNameIsRespected() { public final ExpectedException exception = ExpectedException.none();
GenericApplicationContext ac = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithBeanWithCustomName.class));
ac.refresh();
assertSame(ac.getBean("customName"), ConfigWithBeanWithCustomName.testBean);
// method name should not be registered
try { @Test
ac.getBean("methodName"); public void customBeanNameIsRespectedWhenConfiguredViaNameAttribute() {
fail("bean should not have been registered with 'methodName'"); customBeanNameIsRespected(ConfigWithBeanWithCustomName.class,
} () -> ConfigWithBeanWithCustomName.testBean, "customName");
catch (NoSuchBeanDefinitionException ex) {
// expected
}
} }
@Test @Test
public void aliasesAreRespected() { public void customBeanNameIsRespectedWhenConfiguredViaValueAttribute() {
BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class); customBeanNameIsRespected(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class,
assertSame(factory.getBean("name1"), ConfigWithBeanWithAliases.testBean); () -> ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.testBean, "enigma");
String[] aliases = factory.getAliases("name1"); }
for (String alias : aliases)
assertSame(factory.getBean(alias), ConfigWithBeanWithAliases.testBean); 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 // method name should not be registered
try { exception.expect(NoSuchBeanDefinitionException.class);
factory.getBean("methodName"); ac.getBean("methodName");
fail("bean should not have been registered with 'methodName'"); }
}
catch (NoSuchBeanDefinitionException ex) { @Test
// expected 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 @Test // SPR-11830
@ -146,8 +168,9 @@ public class ConfigurationClassProcessingTests {
assertSame(ac.getBean("customName"), ConfigWithSetWithProviderImplementation.set); assertSame(ac.getBean("customName"), ConfigWithSetWithProviderImplementation.set);
} }
@Test(expected = BeanDefinitionParsingException.class) @Test
public void testFinalBeanMethod() { public void testFinalBeanMethod() {
exception.expect(BeanDefinitionParsingException.class);
initBeanFactory(ConfigWithFinalBean.class); initBeanFactory(ConfigWithFinalBean.class);
} }
@ -219,6 +242,7 @@ public class ConfigurationClassProcessingTests {
adaptive = factory.getBean(AdaptiveInjectionPoints.class); adaptive = factory.getBean(AdaptiveInjectionPoints.class);
assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName()); assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName());
assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName()); assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName());
factory.close();
} }
@Test @Test
@ -240,27 +264,49 @@ public class ConfigurationClassProcessingTests {
SpousyTestBean listener = factory.getBean("listenerTestBean", SpousyTestBean.class); SpousyTestBean listener = factory.getBean("listenerTestBean", SpousyTestBean.class);
assertTrue(listener.refreshed); assertTrue(listener.refreshed);
factory.close();
} }
@Configuration @Configuration
static class ConfigWithBeanWithCustomName { static class ConfigWithBeanWithCustomName {
static TestBean testBean = new TestBean(); static TestBean testBean = new TestBean(ConfigWithBeanWithCustomName.class.getSimpleName());
@Bean(name="customName") @Bean(name = "customName")
public TestBean methodName() { public TestBean methodName() {
return testBean; return testBean;
} }
} }
@Configuration
static class ConfigWithBeanWithCustomNameConfiguredViaValueAttribute {
static TestBean testBean = new TestBean(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class.getSimpleName());
@Bean("enigma")
public TestBean methodName() {
return testBean;
}
}
@Configuration @Configuration
static class ConfigWithBeanWithAliases { static class ConfigWithBeanWithAliases {
static TestBean testBean = new TestBean(); static TestBean testBean = new TestBean(ConfigWithBeanWithAliases.class.getSimpleName());
@Bean(name={"name1", "alias1", "alias2", "alias3"}) @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() { public TestBean methodName() {
return testBean; return testBean;
} }
@ -270,9 +316,9 @@ public class ConfigurationClassProcessingTests {
@Configuration @Configuration
static class ConfigWithBeanWithProviderImplementation implements Provider<TestBean> { static class ConfigWithBeanWithProviderImplementation implements Provider<TestBean> {
static TestBean testBean = new TestBean(); static TestBean testBean = new TestBean(ConfigWithBeanWithProviderImplementation.class.getSimpleName());
@Bean(name="customName") @Bean(name = "customName")
public TestBean get() { public TestBean get() {
return testBean; return testBean;
} }
@ -284,7 +330,7 @@ public class ConfigurationClassProcessingTests {
static Set<String> set = Collections.singleton("value"); static Set<String> set = Collections.singleton("value");
@Bean(name="customName") @Bean(name = "customName")
public Set<String> get() { public Set<String> get() {
return set; return set;
} }

View File

@ -228,7 +228,7 @@ public class EnableMBeanExportConfigurationTests {
return new MBeanServerFactoryBean(); return new MBeanServerFactoryBean();
} }
@Bean(name="bean:name=testBean4") @Bean("bean:name=testBean4")
@Lazy @Lazy
public AnnotationTestBean testBean4() { public AnnotationTestBean testBean4() {
AnnotationTestBean bean = new AnnotationTestBean(); AnnotationTestBean bean = new AnnotationTestBean();
@ -237,7 +237,7 @@ public class EnableMBeanExportConfigurationTests {
return bean; return bean;
} }
@Bean(name="bean:name=testBean5") @Bean("bean:name=testBean5")
public AnnotationTestBeanFactory testBean5() throws Exception { public AnnotationTestBeanFactory testBean5() throws Exception {
return new AnnotationTestBeanFactory(); return new AnnotationTestBeanFactory();
} }

View File

@ -333,10 +333,10 @@ public class WebMvcConfigurationSupportTests {
@EnableWebMvc @EnableWebMvc
@Configuration @SuppressWarnings("unused") @Configuration
static class WebConfig { static class WebConfig {
@Bean(name="/testController") @Bean("/testController")
public TestController testController() { public TestController testController() {
return new TestController(); return new TestController();
} }
@ -350,7 +350,7 @@ public class WebMvcConfigurationSupportTests {
} }
@Configuration @SuppressWarnings("unused") @Configuration
static class ViewResolverConfig { static class ViewResolverConfig {
@Bean @Bean
@ -387,7 +387,7 @@ public class WebMvcConfigurationSupportTests {
} }
@Controller @SuppressWarnings("unused") @Controller
private static class TestController { private static class TestController {
@RequestMapping("/") @RequestMapping("/")
@ -413,7 +413,7 @@ public class WebMvcConfigurationSupportTests {
@Controller @Controller
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS) @Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
static class ScopedProxyController { static class ScopedProxyController {
@RequestMapping("/scopedProxy") @RequestMapping("/scopedProxy")