Skip to content

Commit 15d3e8c

Browse files
sbrannenjhoeller
authored andcommitted
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 (cherry picked from commit 8f62b63)
1 parent 66b370e commit 15d3e8c

File tree

4 files changed

+113
-50
lines changed

4 files changed

+113
-50
lines changed

spring-context/src/main/java/org/springframework/context/annotation/Bean.java

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.beans.factory.annotation.Autowire;
2626
import org.springframework.beans.factory.support.AbstractBeanDefinition;
27+
import org.springframework.core.annotation.AliasFor;
2728

2829
/**
2930
* Indicates that a method produces a bean to be managed by the Spring container.
@@ -44,15 +45,15 @@
4445
*
4546
* <h3>Bean Names</h3>
4647
*
47-
* <p>While a {@link #name() name} attribute is available, the default strategy for
48+
* <p>While a {@link #name} attribute is available, the default strategy for
4849
* determining the name of a bean is to use the name of the {@code @Bean} method.
4950
* This is convenient and intuitive, but if explicit naming is desired, the
50-
* {@code name} attribute may be used. Also note that {@code name} accepts an array
51-
* of Strings. This is in order to allow for specifying multiple names (i.e., aliases)
52-
* for a single bean.
51+
* {@code name} attribute (or its alias {@code value}) may be used. Also note
52+
* that {@code name} accepts an array of Strings, allowing for multiple names
53+
* (i.e. a primary bean name plus one or more aliases) for a single bean.
5354
*
5455
* <pre class="code">
55-
* &#064;Bean(name={"b1","b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
56+
* &#064;Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
5657
* public MyBean myBean() {
5758
* // instantiate and configure MyBean obj
5859
* return obj;
@@ -78,9 +79,9 @@
7879
* <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3>
7980
*
8081
* <p>Typically, {@code @Bean} methods are declared within {@code @Configuration}
81-
* classes. In this case, bean methods may reference other {@code @Bean} methods
82-
* in the same class by calling them <i>directly</i>. This ensures that references between
83-
* beans are strongly typed and navigable. Such so-called <em>'inter-bean references'</em> are
82+
* classes. In this case, bean methods may reference other {@code @Bean} methods in the
83+
* same class by calling them <i>directly</i>. This ensures that references between beans
84+
* are strongly typed and navigable. Such so-called <em>'inter-bean references'</em> are
8485
* guaranteed to respect scoping and AOP semantics, just like {@code getBean()} lookups
8586
* would. These are the semantics known from the original 'Spring JavaConfig' project
8687
* which require CGLIB subclassing of each such configuration class at runtime. As a
@@ -190,10 +191,24 @@
190191
public @interface Bean {
191192

192193
/**
193-
* The name of this bean, or if plural, aliases for this bean. If left unspecified
194-
* the name of the bean is the name of the annotated method. If specified, the method
195-
* name is ignored.
194+
* Alias for {@link #name}.
195+
* <p>Intended to be used when no other attributes are needed, for example:
196+
* {@code @Bean("customBeanName")}.
197+
* @since 4.3.3
198+
* @see #name
196199
*/
200+
@AliasFor("name")
201+
String[] value() default {};
202+
203+
/**
204+
* The name of this bean, or if several names, a primary bean name plus aliases.
205+
* <p>If left unspecified, the name of the bean is the name of the annotated method.
206+
* If specified, the method name is ignored.
207+
* <p>The bean name and aliases may also be configured via the {@link #value}
208+
* attribute if no other attributes are declared.
209+
* @see #value
210+
*/
211+
@AliasFor("value")
197212
String[] name() default {};
198213

199214
/**

spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java

Lines changed: 80 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222
import java.util.Set;
23+
import java.util.function.Supplier;
2324
import javax.inject.Provider;
2425

26+
import org.junit.Rule;
2527
import org.junit.Test;
28+
import org.junit.rules.ExpectedException;
2629

2730
import org.springframework.beans.factory.BeanClassLoaderAware;
2831
import org.springframework.beans.factory.BeanFactory;
@@ -65,6 +68,7 @@
6568
*
6669
* @author Chris Beams
6770
* @author Juergen Hoeller
71+
* @author Sam Brannen
6872
*/
6973
public class ConfigurationClassProcessingTests {
7074

@@ -92,40 +96,57 @@ private DefaultListableBeanFactory initBeanFactory(Class<?>... configClasses) {
9296
}
9397

9498

99+
@Rule
100+
public final ExpectedException exception = ExpectedException.none();
101+
102+
103+
@Test
104+
public void customBeanNameIsRespectedWhenConfiguredViaNameAttribute() {
105+
customBeanNameIsRespected(ConfigWithBeanWithCustomName.class,
106+
() -> ConfigWithBeanWithCustomName.testBean, "customName");
107+
}
108+
95109
@Test
96-
public void customBeanNameIsRespected() {
110+
public void customBeanNameIsRespectedWhenConfiguredViaValueAttribute() {
111+
customBeanNameIsRespected(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class,
112+
() -> ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.testBean, "enigma");
113+
}
114+
115+
private void customBeanNameIsRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) {
97116
GenericApplicationContext ac = new GenericApplicationContext();
98117
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
99-
ac.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithBeanWithCustomName.class));
118+
ac.registerBeanDefinition("config", new RootBeanDefinition(testClass));
100119
ac.refresh();
101-
assertSame(ac.getBean("customName"), ConfigWithBeanWithCustomName.testBean);
120+
121+
assertSame(testBeanSupplier.get(), ac.getBean(beanName));
102122

103123
// method name should not be registered
104-
try {
105-
ac.getBean("methodName");
106-
fail("bean should not have been registered with 'methodName'");
107-
}
108-
catch (NoSuchBeanDefinitionException ex) {
109-
// expected
110-
}
124+
exception.expect(NoSuchBeanDefinitionException.class);
125+
ac.getBean("methodName");
111126
}
112127

113128
@Test
114-
public void aliasesAreRespected() {
115-
BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class);
116-
assertSame(factory.getBean("name1"), ConfigWithBeanWithAliases.testBean);
117-
String[] aliases = factory.getAliases("name1");
118-
for (String alias : aliases)
119-
assertSame(factory.getBean(alias), ConfigWithBeanWithAliases.testBean);
129+
public void aliasesAreRespectedWhenConfiguredViaNameAttribute() {
130+
aliasesAreRespected(ConfigWithBeanWithAliases.class,
131+
() -> ConfigWithBeanWithAliases.testBean, "name1");
132+
}
133+
134+
@Test
135+
public void aliasesAreRespectedWhenConfiguredViaValueAttribute() {
136+
aliasesAreRespected(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class,
137+
() -> ConfigWithBeanWithAliasesConfiguredViaValueAttribute.testBean, "enigma");
138+
}
139+
140+
private void aliasesAreRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) {
141+
TestBean testBean = testBeanSupplier.get();
142+
BeanFactory factory = initBeanFactory(testClass);
143+
144+
assertSame(testBean, factory.getBean(beanName));
145+
Arrays.stream(factory.getAliases(beanName)).map(factory::getBean).forEach(alias -> assertSame(testBean, alias));
120146

121147
// method name should not be registered
122-
try {
123-
factory.getBean("methodName");
124-
fail("bean should not have been registered with 'methodName'");
125-
}
126-
catch (NoSuchBeanDefinitionException ex) {
127-
// expected
128-
}
148+
exception.expect(NoSuchBeanDefinitionException.class);
149+
factory.getBean("methodName");
129150
}
130151

131152
@Test // SPR-11830
@@ -146,8 +167,9 @@ public void configWithSetWithProviderImplementation() {
146167
assertSame(ac.getBean("customName"), ConfigWithSetWithProviderImplementation.set);
147168
}
148169

149-
@Test(expected=BeanDefinitionParsingException.class)
170+
@Test
150171
public void testFinalBeanMethod() {
172+
exception.expect(BeanDefinitionParsingException.class);
151173
initBeanFactory(ConfigWithFinalBean.class);
152174
}
153175

@@ -219,6 +241,7 @@ public void configurationWithAdaptivePrototypes() {
219241
adaptive = factory.getBean(AdaptiveInjectionPoints.class);
220242
assertEquals("adaptiveInjectionPoint1", adaptive.adaptiveInjectionPoint1.getName());
221243
assertEquals("setAdaptiveInjectionPoint2", adaptive.adaptiveInjectionPoint2.getName());
244+
factory.close();
222245
}
223246

224247
@Test
@@ -240,15 +263,28 @@ public void configurationWithPostProcessor() {
240263

241264
SpousyTestBean listener = factory.getBean("listenerTestBean", SpousyTestBean.class);
242265
assertTrue(listener.refreshed);
266+
factory.close();
243267
}
244268

245269

246270
@Configuration
247271
static class ConfigWithBeanWithCustomName {
248272

249-
static TestBean testBean = new TestBean();
273+
static TestBean testBean = new TestBean(ConfigWithBeanWithCustomName.class.getSimpleName());
250274

251-
@Bean(name="customName")
275+
@Bean(name = "customName")
276+
public TestBean methodName() {
277+
return testBean;
278+
}
279+
}
280+
281+
282+
@Configuration
283+
static class ConfigWithBeanWithCustomNameConfiguredViaValueAttribute {
284+
285+
static TestBean testBean = new TestBean(ConfigWithBeanWithCustomNameConfiguredViaValueAttribute.class.getSimpleName());
286+
287+
@Bean("enigma")
252288
public TestBean methodName() {
253289
return testBean;
254290
}
@@ -258,9 +294,21 @@ public TestBean methodName() {
258294
@Configuration
259295
static class ConfigWithBeanWithAliases {
260296

261-
static TestBean testBean = new TestBean();
297+
static TestBean testBean = new TestBean(ConfigWithBeanWithAliases.class.getSimpleName());
298+
299+
@Bean(name = { "name1", "alias1", "alias2", "alias3" })
300+
public TestBean methodName() {
301+
return testBean;
302+
}
303+
}
304+
305+
306+
@Configuration
307+
static class ConfigWithBeanWithAliasesConfiguredViaValueAttribute {
308+
309+
static TestBean testBean = new TestBean(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class.getSimpleName());
262310

263-
@Bean(name={"name1", "alias1", "alias2", "alias3"})
311+
@Bean({ "enigma", "alias1", "alias2", "alias3" })
264312
public TestBean methodName() {
265313
return testBean;
266314
}
@@ -270,9 +318,9 @@ public TestBean methodName() {
270318
@Configuration
271319
static class ConfigWithBeanWithProviderImplementation implements Provider<TestBean> {
272320

273-
static TestBean testBean = new TestBean();
321+
static TestBean testBean = new TestBean(ConfigWithBeanWithProviderImplementation.class.getSimpleName());
274322

275-
@Bean(name="customName")
323+
@Bean(name = "customName")
276324
public TestBean get() {
277325
return testBean;
278326
}
@@ -284,7 +332,7 @@ static class ConfigWithSetWithProviderImplementation implements Provider<Set<Str
284332

285333
static Set<String> set = Collections.singleton("value");
286334

287-
@Bean(name="customName")
335+
@Bean(name = "customName")
288336
public Set<String> get() {
289337
return set;
290338
}
@@ -406,7 +454,7 @@ public int getOrder() {
406454
};
407455
}
408456

409-
//@Bean
457+
// @Bean
410458
public BeanFactoryPostProcessor beanFactoryPostProcessor() {
411459
return new BeanFactoryPostProcessor() {
412460
@Override

spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ public MBeanServerFactoryBean server() throws Exception {
228228
return new MBeanServerFactoryBean();
229229
}
230230

231-
@Bean(name="bean:name=testBean4")
231+
@Bean("bean:name=testBean4")
232232
@Lazy
233233
public AnnotationTestBean testBean4() {
234234
AnnotationTestBean bean = new AnnotationTestBean();
@@ -237,7 +237,7 @@ public AnnotationTestBean testBean4() {
237237
return bean;
238238
}
239239

240-
@Bean(name="bean:name=testBean5")
240+
@Bean("bean:name=testBean5")
241241
public AnnotationTestBeanFactory testBean5() throws Exception {
242242
return new AnnotationTestBeanFactory();
243243
}

spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,10 @@ private ApplicationContext initContext(Class<?>... configClasses) {
333333

334334

335335
@EnableWebMvc
336-
@Configuration @SuppressWarnings("unused")
336+
@Configuration
337337
static class WebConfig {
338338

339-
@Bean(name="/testController")
339+
@Bean("/testController")
340340
public TestController testController() {
341341
return new TestController();
342342
}
@@ -350,7 +350,7 @@ public MessageSource messageSource() {
350350
}
351351

352352

353-
@Configuration @SuppressWarnings("unused")
353+
@Configuration
354354
static class ViewResolverConfig {
355355

356356
@Bean
@@ -387,7 +387,7 @@ public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handler
387387
}
388388

389389

390-
@Controller @SuppressWarnings("unused")
390+
@Controller
391391
private static class TestController {
392392

393393
@RequestMapping("/")
@@ -413,7 +413,7 @@ public void handle() {
413413

414414

415415
@Controller
416-
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
416+
@Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
417417
static class ScopedProxyController {
418418

419419
@RequestMapping("/scopedProxy")

0 commit comments

Comments
 (0)