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:
parent
778ef02680
commit
8f62b63663
|
|
@ -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">
|
||||||
* @Bean(name={"b1","b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
|
* @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 {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue