From 8f62b63663a67f2aad80c11119ddf602bd8a9a3f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 17 Sep 2016 16:01:08 +0200 Subject: [PATCH] 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 --- .../context/annotation/Bean.java | 35 ++++-- .../ConfigurationClassProcessingTests.java | 108 +++++++++++++----- .../EnableMBeanExportConfigurationTests.java | 4 +- .../WebMvcConfigurationSupportTests.java | 10 +- 4 files changed, 109 insertions(+), 48 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java index 8cbaa0da3b..8b4f45ac1d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java @@ -24,6 +24,7 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Autowire; 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. @@ -44,15 +45,15 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; * *

Bean Names

* - *

While a {@link #name() name} attribute is available, the default strategy for - * determining the name of a bean is to use the name of the {@code @Bean} method. - * This is convenient and intuitive, but if explicit naming is desired, the - * {@code name} attribute may be used. Also note that {@code name} accepts an array - * of Strings. This is in order to allow for specifying multiple names (i.e., aliases) - * for a single bean. + *

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. This + * is convenient and intuitive, but if explicit naming is desired, the {@code name} + * attribute (or its alias {@code value}) may be used. Also note that {@code name} + * accepts an array of Strings. This is in order to allow for specifying multiple names + * (i.e., aliases) for a single bean. * *

- *     @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() {
  *         // instantiate and configure MyBean obj
  *         return obj;
@@ -190,10 +191,24 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
 public @interface Bean {
 
 	/**
-	 * The name of this bean, or if plural, aliases for this bean. If left unspecified
-	 * the name of the bean is the name of the annotated method. If specified, the method
-	 * name is ignored.
+	 * Alias for {@link #name}.
+	 * 

Intended to be used when no other attributes are needed, for example: + * {@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. + *

If left unspecified the name of the bean is the name of the annotated method. + * If specified, the method name is ignored. + *

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 {}; /** diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 1bf46027c1..b1360a5bf4 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -20,9 +20,13 @@ 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; @@ -65,6 +69,7 @@ import static org.junit.Assert.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen */ public class ConfigurationClassProcessingTests { @@ -92,40 +97,57 @@ public class ConfigurationClassProcessingTests { } + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test - public void customBeanNameIsRespected() { + 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 testBeanSupplier, String beanName) { GenericApplicationContext ac = new GenericApplicationContext(); AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); - ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithBeanWithCustomName.class)); + ac.registerBeanDefinition("config", new RootBeanDefinition(testClass)); ac.refresh(); - assertSame(ac.getBean("customName"), ConfigWithBeanWithCustomName.testBean); + + assertSame(testBeanSupplier.get(), ac.getBean(beanName)); // method name should not be registered - try { - ac.getBean("methodName"); - fail("bean should not have been registered with 'methodName'"); - } - catch (NoSuchBeanDefinitionException ex) { - // expected - } + exception.expect(NoSuchBeanDefinitionException.class); + ac.getBean("methodName"); } @Test - public void aliasesAreRespected() { - BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class); - assertSame(factory.getBean("name1"), ConfigWithBeanWithAliases.testBean); - String[] aliases = factory.getAliases("name1"); - for (String alias : aliases) - assertSame(factory.getBean(alias), ConfigWithBeanWithAliases.testBean); + 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 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 - try { - factory.getBean("methodName"); - fail("bean should not have been registered with 'methodName'"); - } - catch (NoSuchBeanDefinitionException ex) { - // expected - } + exception.expect(NoSuchBeanDefinitionException.class); + factory.getBean("methodName"); } @Test // SPR-11830 @@ -146,8 +168,9 @@ public class ConfigurationClassProcessingTests { assertSame(ac.getBean("customName"), ConfigWithSetWithProviderImplementation.set); } - @Test(expected = BeanDefinitionParsingException.class) + @Test public void testFinalBeanMethod() { + exception.expect(BeanDefinitionParsingException.class); initBeanFactory(ConfigWithFinalBean.class); } @@ -219,6 +242,7 @@ public class ConfigurationClassProcessingTests { adaptive = factory.getBean(AdaptiveInjectionPoints.class); assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName()); assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName()); + factory.close(); } @Test @@ -240,27 +264,49 @@ public class ConfigurationClassProcessingTests { SpousyTestBean listener = factory.getBean("listenerTestBean", SpousyTestBean.class); assertTrue(listener.refreshed); + factory.close(); } @Configuration 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() { 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(); + 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(name={"name1", "alias1", "alias2", "alias3"}) + @Bean({ "enigma", "alias1", "alias2", "alias3" }) public TestBean methodName() { return testBean; } @@ -270,9 +316,9 @@ public class ConfigurationClassProcessingTests { @Configuration static class ConfigWithBeanWithProviderImplementation implements Provider { - static TestBean testBean = new TestBean(); + static TestBean testBean = new TestBean(ConfigWithBeanWithProviderImplementation.class.getSimpleName()); - @Bean(name="customName") + @Bean(name = "customName") public TestBean get() { return testBean; } @@ -284,7 +330,7 @@ public class ConfigurationClassProcessingTests { static Set set = Collections.singleton("value"); - @Bean(name="customName") + @Bean(name = "customName") public Set get() { return set; } diff --git a/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java b/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java index 9048882952..d9d93306fd 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java @@ -228,7 +228,7 @@ public class EnableMBeanExportConfigurationTests { return new MBeanServerFactoryBean(); } - @Bean(name="bean:name=testBean4") + @Bean("bean:name=testBean4") @Lazy public AnnotationTestBean testBean4() { AnnotationTestBean bean = new AnnotationTestBean(); @@ -237,7 +237,7 @@ public class EnableMBeanExportConfigurationTests { return bean; } - @Bean(name="bean:name=testBean5") + @Bean("bean:name=testBean5") public AnnotationTestBeanFactory testBean5() throws Exception { return new AnnotationTestBeanFactory(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index 96177ee9d3..b31d35ffd4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -333,10 +333,10 @@ public class WebMvcConfigurationSupportTests { @EnableWebMvc - @Configuration @SuppressWarnings("unused") + @Configuration static class WebConfig { - @Bean(name="/testController") + @Bean("/testController") public TestController testController() { return new TestController(); } @@ -350,7 +350,7 @@ public class WebMvcConfigurationSupportTests { } - @Configuration @SuppressWarnings("unused") + @Configuration static class ViewResolverConfig { @Bean @@ -387,7 +387,7 @@ public class WebMvcConfigurationSupportTests { } - @Controller @SuppressWarnings("unused") + @Controller private static class TestController { @RequestMapping("/") @@ -413,7 +413,7 @@ public class WebMvcConfigurationSupportTests { @Controller - @Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS) + @Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) static class ScopedProxyController { @RequestMapping("/scopedProxy")