Browse Source

Configuration classes can opt into lite mode (proxyBeanMethods=false)

Closes gh-22461
pull/22549/head
Juergen Hoeller 6 years ago
parent
commit
1a8b3fba94
  1. 34
      spring-context/src/main/java/org/springframework/context/annotation/Configuration.java
  2. 34
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java
  3. 32
      spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java

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

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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; @@ -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 { @@ -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;
}

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

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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;

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

@ -90,8 +90,7 @@ public class ConfigurationClassPostProcessorTests { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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…
Cancel
Save