Configuration classes can opt into lite mode (proxyBeanMethods=false)
Closes gh-22461
This commit is contained in:
parent
f5248ff13f
commit
1a8b3fba94
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
|
@ -344,9 +344,9 @@ import org.springframework.stereotype.Component;
|
|||
*
|
||||
* <p>By default, {@code @Bean} methods will be <em>eagerly instantiated</em> at container
|
||||
* bootstrap time. To avoid this, {@code @Configuration} may be used in conjunction with
|
||||
* the {@link Lazy @Lazy} annotation to indicate that all {@code @Bean} methods declared within
|
||||
* the class are by default lazily initialized. Note that {@code @Lazy} may be used on
|
||||
* individual {@code @Bean} methods as well.
|
||||
* the {@link Lazy @Lazy} annotation to indicate that all {@code @Bean} methods declared
|
||||
* within the class are by default lazily initialized. Note that {@code @Lazy} may be used
|
||||
* on individual {@code @Bean} methods as well.
|
||||
*
|
||||
* <h2>Testing support for {@code @Configuration} classes</h2>
|
||||
*
|
||||
|
|
@ -391,7 +391,9 @@ import org.springframework.stereotype.Component;
|
|||
* <ul>
|
||||
* <li>Configuration classes must be provided as classes (i.e. not as instances returned
|
||||
* from factory methods), allowing for runtime enhancements through a generated subclass.
|
||||
* <li>Configuration classes must be non-final.
|
||||
* <li>Configuration classes must be non-final (allowing for subclasses at runtime),
|
||||
* unless the {@link #proxyBeanMethods() proxyBeanMethods} flag is set to {@code false}
|
||||
* in which case no runtime-generated subclass is necessary.
|
||||
* <li>Configuration classes must be non-local (i.e. may not be declared within a method).
|
||||
* <li>Any nested configuration classes must be declared as {@code static}.
|
||||
* <li>{@code @Bean} methods may not in turn create further configuration classes
|
||||
|
|
@ -401,6 +403,7 @@ import org.springframework.stereotype.Component;
|
|||
*
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see Bean
|
||||
* @see Profile
|
||||
|
|
@ -435,4 +438,25 @@ public @interface Configuration {
|
|||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Specify whether {@code @Bean} methods should get proxied in order to enforce
|
||||
* bean lifecycle behavior, e.g. to return shared singleton bean instances even
|
||||
* in case of direct {@code @Bean} method calls in user code. This feature
|
||||
* requires method interception, implemented through a runtime-generated CGLIB
|
||||
* subclass which comes with limitations such as the configuration class and
|
||||
* its methods not being allowed to declare {@code final}.
|
||||
* <p>The default is {@code true}, allowing for 'inter-bean references' within
|
||||
* the configuration class as well as for external calls to this configuration's
|
||||
* {@code @Bean} methods, e.g. from another configuration class. If this is not
|
||||
* needed since each of this particular configuration's {@code @Bean} methods
|
||||
* is self-contained and designed as a plain factory method for container use,
|
||||
* switch this flag to {@code false} in order to avoid CGLIB subclass processing.
|
||||
* <p>Turning off bean method interception effectively processes {@code @Bean}
|
||||
* methods individually like when declared on non-{@code @Configuration} classes,
|
||||
* a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
|
||||
* behaviorally equivalent to removing the {@code @Configuration} stereotype.
|
||||
* @since 5.2
|
||||
*/
|
||||
boolean proxyBeanMethods() default true;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
|
@ -112,10 +112,11 @@ abstract class ConfigurationClassUtils {
|
|||
}
|
||||
}
|
||||
|
||||
if (isFullConfigurationCandidate(metadata)) {
|
||||
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
|
||||
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
|
||||
}
|
||||
else if (isLiteConfigurationCandidate(metadata)) {
|
||||
else if (config != null || isConfigurationCandidate(metadata)) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
|
||||
}
|
||||
else {
|
||||
|
|
@ -135,33 +136,10 @@ abstract class ConfigurationClassUtils {
|
|||
* Check the given metadata for a configuration class candidate
|
||||
* (or nested component class declared within a configuration/component class).
|
||||
* @param metadata the metadata of the annotated class
|
||||
* @return {@code true} if the given class is to be registered as a
|
||||
* reflection-detected bean definition; {@code false} otherwise
|
||||
* @return {@code true} if the given class is to be registered for
|
||||
* configuration class processing; {@code false} otherwise
|
||||
*/
|
||||
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
|
||||
return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given metadata for a full configuration class candidate
|
||||
* (i.e. a class annotated with {@code @Configuration}).
|
||||
* @param metadata the metadata of the annotated class
|
||||
* @return {@code true} if the given class is to be processed as a full
|
||||
* configuration class, including cross-method call interception
|
||||
*/
|
||||
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
|
||||
return metadata.isAnnotated(Configuration.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given metadata for a lite configuration class candidate
|
||||
* (e.g. a class annotated with {@code @Component} or just having
|
||||
* {@code @Import} declarations or {@code @Bean methods}).
|
||||
* @param metadata the metadata of the annotated class
|
||||
* @return {@code true} if the given class is to be processed as a lite
|
||||
* configuration class, just registering it and scanning it for {@code @Bean} methods
|
||||
*/
|
||||
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
|
||||
// Do not consider an interface or an annotation...
|
||||
if (metadata.isInterface()) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -90,8 +90,7 @@ public class ConfigurationClassPostProcessorTests {
|
|||
* <p>Technically, {@link ConfigurationClassPostProcessor} could fail to enhance the
|
||||
* registered Configuration classes and many use cases would still work.
|
||||
* Certain cases, however, like inter-bean singleton references would not.
|
||||
* We test for such a case below, and in doing so prove that enhancement is
|
||||
* working.
|
||||
* We test for such a case below, and in doing so prove that enhancement is working.
|
||||
*/
|
||||
@Test
|
||||
public void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
|
||||
|
|
@ -104,6 +103,16 @@ public class ConfigurationClassPostProcessorTests {
|
|||
assertTrue(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertNotSame(foo, bar.foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
|
||||
|
|
@ -115,8 +124,8 @@ public class ConfigurationClassPostProcessorTests {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests the fix for SPR-5655, a special workaround that prefers reflection
|
||||
* over ASM if a bean class is already loaded.
|
||||
* Tests the fix for SPR-5655, a special workaround that prefers reflection over ASM
|
||||
* if a bean class is already loaded.
|
||||
*/
|
||||
@Test
|
||||
public void alreadyLoadedConfigurationClasses() {
|
||||
|
|
@ -129,8 +138,7 @@ public class ConfigurationClassPostProcessorTests {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests whether a bean definition without a specified bean class is handled
|
||||
* correctly.
|
||||
* Tests whether a bean definition without a specified bean class is handled correctly.
|
||||
*/
|
||||
@Test
|
||||
public void postProcessorIntrospectsInheritedDefinitionsCorrectly() {
|
||||
|
|
@ -1070,6 +1078,18 @@ public class ConfigurationClassPostProcessorTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NonEnhancedSingletonBeanConfig {
|
||||
|
||||
public @Bean Foo foo() {
|
||||
return new Foo();
|
||||
}
|
||||
|
||||
public @Bean Bar bar() {
|
||||
return new Bar(foo());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
static class OverridingSingletonBeanConfig {
|
||||
|
|
|
|||
Loading…
Reference in New Issue