Skip to content

Commit 1a8b3fb

Browse files
committed
Configuration classes can opt into lite mode (proxyBeanMethods=false)
Closes gh-22461
1 parent f5248ff commit 1a8b3fb

File tree

3 files changed

+61
-39
lines changed

3 files changed

+61
-39
lines changed

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

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -344,9 +344,9 @@
344344
*
345345
* <p>By default, {@code @Bean} methods will be <em>eagerly instantiated</em> at container
346346
* bootstrap time. To avoid this, {@code @Configuration} may be used in conjunction with
347-
* the {@link Lazy @Lazy} annotation to indicate that all {@code @Bean} methods declared within
348-
* the class are by default lazily initialized. Note that {@code @Lazy} may be used on
349-
* individual {@code @Bean} methods as well.
347+
* the {@link Lazy @Lazy} annotation to indicate that all {@code @Bean} methods declared
348+
* within the class are by default lazily initialized. Note that {@code @Lazy} may be used
349+
* on individual {@code @Bean} methods as well.
350350
*
351351
* <h2>Testing support for {@code @Configuration} classes</h2>
352352
*
@@ -391,7 +391,9 @@
391391
* <ul>
392392
* <li>Configuration classes must be provided as classes (i.e. not as instances returned
393393
* from factory methods), allowing for runtime enhancements through a generated subclass.
394-
* <li>Configuration classes must be non-final.
394+
* <li>Configuration classes must be non-final (allowing for subclasses at runtime),
395+
* unless the {@link #proxyBeanMethods() proxyBeanMethods} flag is set to {@code false}
396+
* in which case no runtime-generated subclass is necessary.
395397
* <li>Configuration classes must be non-local (i.e. may not be declared within a method).
396398
* <li>Any nested configuration classes must be declared as {@code static}.
397399
* <li>{@code @Bean} methods may not in turn create further configuration classes
@@ -401,6 +403,7 @@
401403
*
402404
* @author Rod Johnson
403405
* @author Chris Beams
406+
* @author Juergen Hoeller
404407
* @since 3.0
405408
* @see Bean
406409
* @see Profile
@@ -435,4 +438,25 @@
435438
@AliasFor(annotation = Component.class)
436439
String value() default "";
437440

441+
/**
442+
* Specify whether {@code @Bean} methods should get proxied in order to enforce
443+
* bean lifecycle behavior, e.g. to return shared singleton bean instances even
444+
* in case of direct {@code @Bean} method calls in user code. This feature
445+
* requires method interception, implemented through a runtime-generated CGLIB
446+
* subclass which comes with limitations such as the configuration class and
447+
* its methods not being allowed to declare {@code final}.
448+
* <p>The default is {@code true}, allowing for 'inter-bean references' within
449+
* the configuration class as well as for external calls to this configuration's
450+
* {@code @Bean} methods, e.g. from another configuration class. If this is not
451+
* needed since each of this particular configuration's {@code @Bean} methods
452+
* is self-contained and designed as a plain factory method for container use,
453+
* switch this flag to {@code false} in order to avoid CGLIB subclass processing.
454+
* <p>Turning off bean method interception effectively processes {@code @Bean}
455+
* methods individually like when declared on non-{@code @Configuration} classes,
456+
* a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
457+
* behaviorally equivalent to removing the {@code @Configuration} stereotype.
458+
* @since 5.2
459+
*/
460+
boolean proxyBeanMethods() default true;
461+
438462
}

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

+6-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -112,10 +112,11 @@ else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)
112112
}
113113
}
114114

115-
if (isFullConfigurationCandidate(metadata)) {
115+
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
116+
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
116117
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
117118
}
118-
else if (isLiteConfigurationCandidate(metadata)) {
119+
else if (config != null || isConfigurationCandidate(metadata)) {
119120
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
120121
}
121122
else {
@@ -135,33 +136,10 @@ else if (isLiteConfigurationCandidate(metadata)) {
135136
* Check the given metadata for a configuration class candidate
136137
* (or nested component class declared within a configuration/component class).
137138
* @param metadata the metadata of the annotated class
138-
* @return {@code true} if the given class is to be registered as a
139-
* reflection-detected bean definition; {@code false} otherwise
139+
* @return {@code true} if the given class is to be registered for
140+
* configuration class processing; {@code false} otherwise
140141
*/
141142
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
142-
return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata));
143-
}
144-
145-
/**
146-
* Check the given metadata for a full configuration class candidate
147-
* (i.e. a class annotated with {@code @Configuration}).
148-
* @param metadata the metadata of the annotated class
149-
* @return {@code true} if the given class is to be processed as a full
150-
* configuration class, including cross-method call interception
151-
*/
152-
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
153-
return metadata.isAnnotated(Configuration.class.getName());
154-
}
155-
156-
/**
157-
* Check the given metadata for a lite configuration class candidate
158-
* (e.g. a class annotated with {@code @Component} or just having
159-
* {@code @Import} declarations or {@code @Bean methods}).
160-
* @param metadata the metadata of the annotated class
161-
* @return {@code true} if the given class is to be processed as a lite
162-
* configuration class, just registering it and scanning it for {@code @Bean} methods
163-
*/
164-
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
165143
// Do not consider an interface or an annotation...
166144
if (metadata.isInterface()) {
167145
return false;

spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java

+26-6
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ public void setup() {
9090
* <p>Technically, {@link ConfigurationClassPostProcessor} could fail to enhance the
9191
* registered Configuration classes and many use cases would still work.
9292
* Certain cases, however, like inter-bean singleton references would not.
93-
* We test for such a case below, and in doing so prove that enhancement is
94-
* working.
93+
* We test for such a case below, and in doing so prove that enhancement is working.
9594
*/
9695
@Test
9796
public void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
@@ -104,6 +103,16 @@ public void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
104103
assertTrue(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar"));
105104
}
106105

106+
@Test
107+
public void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() {
108+
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class));
109+
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
110+
pp.postProcessBeanFactory(beanFactory);
111+
Foo foo = beanFactory.getBean("foo", Foo.class);
112+
Bar bar = beanFactory.getBean("bar", Bar.class);
113+
assertNotSame(foo, bar.foo);
114+
}
115+
107116
@Test
108117
public void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
109118
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
@@ -115,8 +124,8 @@ public void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
115124
}
116125

117126
/**
118-
* Tests the fix for SPR-5655, a special workaround that prefers reflection
119-
* over ASM if a bean class is already loaded.
127+
* Tests the fix for SPR-5655, a special workaround that prefers reflection over ASM
128+
* if a bean class is already loaded.
120129
*/
121130
@Test
122131
public void alreadyLoadedConfigurationClasses() {
@@ -129,8 +138,7 @@ public void alreadyLoadedConfigurationClasses() {
129138
}
130139

131140
/**
132-
* Tests whether a bean definition without a specified bean class is handled
133-
* correctly.
141+
* Tests whether a bean definition without a specified bean class is handled correctly.
134142
*/
135143
@Test
136144
public void postProcessorIntrospectsInheritedDefinitionsCorrectly() {
@@ -1070,6 +1078,18 @@ static class SingletonBeanConfig {
10701078
}
10711079
}
10721080

1081+
@Configuration(proxyBeanMethods = false)
1082+
static class NonEnhancedSingletonBeanConfig {
1083+
1084+
public @Bean Foo foo() {
1085+
return new Foo();
1086+
}
1087+
1088+
public @Bean Bar bar() {
1089+
return new Bar(foo());
1090+
}
1091+
}
1092+
10731093
@Configuration
10741094
@Order(2)
10751095
static class OverridingSingletonBeanConfig {

0 commit comments

Comments
 (0)