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
This commit is contained in:
Chris Beams
2011-05-10 11:55:41 +00:00
parent 859185d086
commit 52bef0b7b0
5 changed files with 173 additions and 26 deletions

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Tests semantics of declaring {@link BeanFactoryPostProcessor}-returning @Bean
* methods, specifically as regards static @Bean methods and the avoidance of
* container lifecycle issues when BFPPs are in the mix.
*
* @author Chris Beams
* @since 3.1
*/
public class ConfigurationClassAndBFPPTests {
@Test
public void autowiringFailsWithBFPPAsInstanceMethod() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(TestBeanConfig.class, AutowiredConfigWithBFPPAsInstanceMethod.class);
ctx.refresh();
// instance method BFPP interferes with lifecycle -> autowiring fails!
// WARN-level logging should have been issued about returning BFPP from non-static @Bean method
assertThat(ctx.getBean(AutowiredConfigWithBFPPAsInstanceMethod.class).autowiredTestBean, nullValue());
}
@Test
public void autowiringSucceedsWithBFPPAsStaticMethod() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(TestBeanConfig.class, AutowiredConfigWithBFPPAsStaticMethod.class);
ctx.refresh();
// static method BFPP does not interfere with lifecycle -> autowiring succeeds
assertThat(ctx.getBean(AutowiredConfigWithBFPPAsStaticMethod.class).autowiredTestBean, notNullValue());
}
@Configuration
static class TestBeanConfig {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@Configuration
static class AutowiredConfigWithBFPPAsInstanceMethod {
@Autowired TestBean autowiredTestBean;
@Bean
public BeanFactoryPostProcessor bfpp() {
return new BeanFactoryPostProcessor() {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// no-op
}
};
}
}
@Configuration
static class AutowiredConfigWithBFPPAsStaticMethod {
@Autowired TestBean autowiredTestBean;
@Bean
public static final BeanFactoryPostProcessor bfpp() {
return new BeanFactoryPostProcessor() {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// no-op
}
};
}
}
@Test
@SuppressWarnings("static-access")
public void staticBeanMethodsDoNotRespectScoping() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithStaticBeanMethod.class);
ctx.refresh();
ConfigWithStaticBeanMethod config = ctx.getBean(ConfigWithStaticBeanMethod.class);
assertThat(config.testBean(), not(sameInstance(config.testBean())));
}
@Configuration
static class ConfigWithStaticBeanMethod {
@Bean
public static TestBean testBean() {
return new TestBean("foo");
}
}
}