Skip to content

Commit 52bef0b

Browse files
committed
Allow static modifier on @bean methods
Declaring @bean methods as 'static' is now permitted, whereas previously it raised an exception at @configuration class validation time. A static @bean method can be called by the container without requiring the instantiation of its declaring @configuration class. This is particularly useful when dealing with BeanFactoryPostProcessor beans, as they can interfere with the standard post-processing lifecycle necessary to handle @Autowired, @Inject, @value, @PostConstruct and other annotations. static @bean methods cannot recieve CGLIB enhancement for scoping and AOP concerns. This is acceptable in BFPP cases as they rarely if ever need it, and should not in typical cases ever be called by another @bean method. Once invoked by the container, the resulting bean will be cached as usual, but multiple invocations of the static @bean method will result in creation of multiple instances of the bean. static @bean methods may not, for obvious reasons, refer to normal instance @bean methods, but again this is not likely a concern for BFPP types. In the rare case that they do need a bean reference, parameter injection into the static @bean method is technically an option, but should be avoided as it will potentially cause premature instantiation of more beans that the user may have intended. Note particularly that a WARN-level log message is now issued for any non-static @bean method with a return type assignable to BFPP. This serves as a strong recommendation to users that they always mark BFPP @bean methods as static. See @bean Javadoc for complete details. Issue: SPR-8257, SPR-8269
1 parent 859185d commit 52bef0b

File tree

5 files changed

+173
-26
lines changed

5 files changed

+173
-26
lines changed

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
*
3838
* <p>While a {@link #name()} attribute is available, the default strategy for determining
3939
* the name of a bean is to use the name of the Bean method. This is convenient and
40-
* intuitive, but if explicit naming is desired, the {@link #name()} attribute may be used.
41-
* Also note that {@link #name()} accepts an array of Strings. This is in order to allow
40+
* intuitive, but if explicit naming is desired, the {@code name()} attribute may be used.
41+
* Also note that {@code name()} accepts an array of Strings. This is in order to allow
4242
* for specifying multiple names (i.e., aliases) for a single bean.
4343
*
4444
* <p>The <code>@Bean</code> annotation may be used on any methods in an <code>@Component</code>
@@ -56,6 +56,27 @@
5656
* subclassing of each such configuration class at runtime. As a consequence, configuration
5757
* classes and their factory methods must not be marked as final or private in this mode.
5858
*
59+
* <h3>A note on {@code BeanFactoryPostProcessor}-returning {@code @Bean} methods</h3>
60+
* <p>Special consideration must be taken for {@code @Bean} methods that return Spring
61+
* {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor BeanFactoryPostProcessor}
62+
* ({@code BFPP}) types. Because {@code BFPP} objects must be instantiated very early in the
63+
* container lifecycle, they can interfere with processing of annotations such as {@code @Autowired},
64+
* {@code @Value}, and {@code @PostConstruct} within {@code @Configuration} classes. To avoid these
65+
* lifecycle issues, mark {@code BFPP}-returning {@code @Bean} methods as {@code static}. For example:
66+
* <pre class="code">
67+
* &#064;Bean
68+
* public static PropertyPlaceholderConfigurer ppc() {
69+
* // instantiate, configure and return ppc ...
70+
* }
71+
* </pre>
72+
* By marking this method as {@code static}, it can be invoked without causing instantiation of its
73+
* declaring {@code @Configuration} class, thus avoiding the above-mentioned lifecycle conflicts.
74+
* Note however that {@code static} {@code @Bean} methods will not be enhanced for scoping and AOP
75+
* semantics as mentioned above. This works out in {@code BFPP} cases, as they are not typically
76+
* referenced by other {@code @Bean} methods. As a reminder, a WARN-level log message will be
77+
* issued for any non-static {@code @Bean} methods having a return type assignable to
78+
* {@code BeanFactoryPostProcessor}.
79+
*
5980
* @author Rod Johnson
6081
* @author Costin Leau
6182
* @author Chris Beams

org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java

+8-22
Original file line numberDiff line numberDiff line change
@@ -39,39 +39,25 @@ public BeanMethod(MethodMetadata metadata, ConfigurationClass configurationClass
3939

4040
@Override
4141
public void validate(ProblemReporter problemReporter) {
42+
if (getMetadata().isStatic()) {
43+
// static @Bean methods have no constraints to validate -> return immediately
44+
return;
45+
}
46+
4247
if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) {
4348
if (!getMetadata().isOverridable()) {
49+
// instance @Bean methods within @Configuration classes must be overridable to accommodate CGLIB
4450
problemReporter.error(new NonOverridableMethodError());
4551
}
4652
}
47-
else {
48-
if (getMetadata().isStatic()) {
49-
problemReporter.error(new StaticMethodError());
50-
}
51-
}
5253
}
5354

54-
/**
55-
* {@link Bean} methods must be overridable in order to accommodate CGLIB.
56-
*/
55+
5756
private class NonOverridableMethodError extends Problem {
5857

5958
public NonOverridableMethodError() {
60-
super(String.format("Method '%s' must not be private, final or static; change the method's modifiers to continue",
61-
getMetadata().getMethodName()), getResourceLocation());
62-
}
63-
}
64-
65-
66-
/**
67-
* {@link Bean} methods must at least not be static in the non-CGLIB case.
68-
*/
69-
private class StaticMethodError extends Problem {
70-
71-
public StaticMethodError() {
72-
super(String.format("Method '%s' must not be static; remove the method's static modifier to continue",
59+
super(String.format("@Bean method '%s' must not be private or final; change the method's modifiers to continue",
7360
getMetadata().getMethodName()), getResourceLocation());
7461
}
7562
}
76-
7763
}

org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,16 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
162162
RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass);
163163
beanDef.setResource(configClass.getResource());
164164
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
165-
beanDef.setFactoryBeanName(configClass.getBeanName());
166-
beanDef.setUniqueFactoryMethodName(metadata.getMethodName());
165+
if (metadata.isStatic()) {
166+
// static @Bean method
167+
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
168+
beanDef.setFactoryMethodName(metadata.getMethodName());
169+
}
170+
else {
171+
// instance @Bean method
172+
beanDef.setFactoryBeanName(configClass.getBeanName());
173+
beanDef.setUniqueFactoryMethodName(metadata.getMethodName());
174+
}
167175
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
168176
beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
169177

org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java

+10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.beans.factory.BeanFactory;
3434
import org.springframework.beans.factory.DisposableBean;
3535
import org.springframework.beans.factory.FactoryBean;
36+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3637
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3738
import org.springframework.core.annotation.AnnotationUtils;
3839
import org.springframework.util.Assert;
@@ -239,6 +240,15 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
239240
return this.beanFactory.getBean(beanName);
240241
}
241242

243+
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
244+
logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
245+
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
246+
"result in a failure to process annotations such as @Autowired, " +
247+
"@Resource and @PostConstruct within the method's declaring " +
248+
"@Configuration class. Add the 'static' modifier to this method to avoid" +
249+
"these container lifecycle issues; see @Bean Javadoc for complete details",
250+
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
251+
}
242252
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
243253
}
244254

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2002-2011 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.annotation;
18+
19+
import static org.hamcrest.CoreMatchers.not;
20+
import static org.hamcrest.CoreMatchers.notNullValue;
21+
import static org.hamcrest.CoreMatchers.nullValue;
22+
import static org.hamcrest.CoreMatchers.sameInstance;
23+
import static org.junit.Assert.assertThat;
24+
25+
import org.junit.Test;
26+
import org.springframework.beans.BeansException;
27+
import org.springframework.beans.TestBean;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
30+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
31+
32+
/**
33+
* Tests semantics of declaring {@link BeanFactoryPostProcessor}-returning @Bean
34+
* methods, specifically as regards static @Bean methods and the avoidance of
35+
* container lifecycle issues when BFPPs are in the mix.
36+
*
37+
* @author Chris Beams
38+
* @since 3.1
39+
*/
40+
public class ConfigurationClassAndBFPPTests {
41+
42+
@Test
43+
public void autowiringFailsWithBFPPAsInstanceMethod() {
44+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
45+
ctx.register(TestBeanConfig.class, AutowiredConfigWithBFPPAsInstanceMethod.class);
46+
ctx.refresh();
47+
// instance method BFPP interferes with lifecycle -> autowiring fails!
48+
// WARN-level logging should have been issued about returning BFPP from non-static @Bean method
49+
assertThat(ctx.getBean(AutowiredConfigWithBFPPAsInstanceMethod.class).autowiredTestBean, nullValue());
50+
}
51+
52+
@Test
53+
public void autowiringSucceedsWithBFPPAsStaticMethod() {
54+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
55+
ctx.register(TestBeanConfig.class, AutowiredConfigWithBFPPAsStaticMethod.class);
56+
ctx.refresh();
57+
// static method BFPP does not interfere with lifecycle -> autowiring succeeds
58+
assertThat(ctx.getBean(AutowiredConfigWithBFPPAsStaticMethod.class).autowiredTestBean, notNullValue());
59+
}
60+
61+
62+
@Configuration
63+
static class TestBeanConfig {
64+
@Bean
65+
public TestBean testBean() {
66+
return new TestBean();
67+
}
68+
}
69+
70+
71+
@Configuration
72+
static class AutowiredConfigWithBFPPAsInstanceMethod {
73+
@Autowired TestBean autowiredTestBean;
74+
75+
@Bean
76+
public BeanFactoryPostProcessor bfpp() {
77+
return new BeanFactoryPostProcessor() {
78+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
79+
// no-op
80+
}
81+
};
82+
}
83+
}
84+
85+
86+
@Configuration
87+
static class AutowiredConfigWithBFPPAsStaticMethod {
88+
@Autowired TestBean autowiredTestBean;
89+
90+
@Bean
91+
public static final BeanFactoryPostProcessor bfpp() {
92+
return new BeanFactoryPostProcessor() {
93+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
94+
// no-op
95+
}
96+
};
97+
}
98+
}
99+
100+
101+
@Test
102+
@SuppressWarnings("static-access")
103+
public void staticBeanMethodsDoNotRespectScoping() {
104+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
105+
ctx.register(ConfigWithStaticBeanMethod.class);
106+
ctx.refresh();
107+
108+
ConfigWithStaticBeanMethod config = ctx.getBean(ConfigWithStaticBeanMethod.class);
109+
assertThat(config.testBean(), not(sameInstance(config.testBean())));
110+
}
111+
112+
113+
@Configuration
114+
static class ConfigWithStaticBeanMethod {
115+
@Bean
116+
public static TestBean testBean() {
117+
return new TestBean("foo");
118+
}
119+
}
120+
121+
122+
}

0 commit comments

Comments
 (0)