Skip to content

Commit

Permalink
Configuration classes can opt into lite mode (proxyBeanMethods=false)
Browse files Browse the repository at this point in the history
Closes gh-22461
  • Loading branch information
jhoeller committed Mar 8, 2019
1 parent f5248ff commit 1a8b3fb
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -344,9 +344,9 @@
*
* <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>
*
Expand Down Expand Up @@ -391,7 +391,9 @@
* <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
Expand All @@ -401,6 +403,7 @@
*
* @author Rod Johnson
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Bean
* @see Profile
Expand Down Expand Up @@ -435,4 +438,25 @@
@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;

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -112,10 +112,11 @@ else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)
}
}

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 {
Expand All @@ -135,33 +136,10 @@ else if (isLiteConfigurationCandidate(metadata)) {
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ public void setup() {
* <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() {
Expand All @@ -104,6 +103,16 @@ public void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
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"));
Expand All @@ -115,8 +124,8 @@ public void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
}

/**
* 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() {
Expand All @@ -129,8 +138,7 @@ public void alreadyLoadedConfigurationClasses() {
}

/**
* 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() {
Expand Down Expand Up @@ -1070,6 +1078,18 @@ static class SingletonBeanConfig {
}
}

@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 {
Expand Down

0 comments on commit 1a8b3fb

Please sign in to comment.