Skip to content

Commit aff9ac7

Browse files
committed
Avoid unnecessary CGLIB processing on configuration classes
Closes gh-34486
1 parent f895d76 commit aff9ac7

File tree

5 files changed

+75
-23
lines changed

5 files changed

+75
-23
lines changed

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -203,6 +203,15 @@ Set<BeanMethod> getBeanMethods() {
203203
return this.beanMethods;
204204
}
205205

206+
boolean hasNonStaticBeanMethods() {
207+
for (BeanMethod beanMethod : this.beanMethods) {
208+
if (!beanMethod.getMetadata().isStatic()) {
209+
return true;
210+
}
211+
}
212+
return false;
213+
}
214+
206215
void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
207216
this.importedResources.put(importedResource, readerClass);
208217
}
@@ -223,8 +232,9 @@ Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRe
223232
void validate(ProblemReporter problemReporter) {
224233
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
225234

226-
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
227-
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && this.metadata.isFinal()) {
235+
// A configuration class may not be final (CGLIB limitation) unless it does not have to proxy bean methods
236+
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && hasNonStaticBeanMethods() &&
237+
this.metadata.isFinal()) {
228238
problemReporter.error(new FinalConfigurationProblem());
229239
}
230240

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

+25-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -167,14 +167,22 @@ public void parse(Set<BeanDefinitionHolder> configCandidates) {
167167
for (BeanDefinitionHolder holder : configCandidates) {
168168
BeanDefinition bd = holder.getBeanDefinition();
169169
try {
170+
ConfigurationClass configClass;
170171
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
171-
parse(annotatedBeanDef, holder.getBeanName());
172+
configClass = parse(annotatedBeanDef, holder.getBeanName());
172173
}
173174
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
174-
parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
175+
configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
175176
}
176177
else {
177-
parse(bd.getBeanClassName(), holder.getBeanName());
178+
configClass = parse(bd.getBeanClassName(), holder.getBeanName());
179+
}
180+
181+
// Downgrade to lite (no enhancement) in case of no instance-level @Bean methods.
182+
if (!configClass.hasNonStaticBeanMethods() && ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
183+
bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
184+
bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
185+
ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
178186
}
179187
}
180188
catch (BeanDefinitionStoreException ex) {
@@ -189,20 +197,25 @@ else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef
189197
this.deferredImportSelectorHandler.process();
190198
}
191199

192-
private void parse(AnnotatedBeanDefinition beanDef, String beanName) {
193-
processConfigurationClass(
194-
new ConfigurationClass(beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition)),
195-
DEFAULT_EXCLUSION_FILTER);
200+
private ConfigurationClass parse(AnnotatedBeanDefinition beanDef, String beanName) {
201+
ConfigurationClass configClass = new ConfigurationClass(
202+
beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition));
203+
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
204+
return configClass;
196205
}
197206

198-
private void parse(Class<?> clazz, String beanName) {
199-
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
207+
private ConfigurationClass parse(Class<?> clazz, String beanName) {
208+
ConfigurationClass configClass = new ConfigurationClass(clazz, beanName);
209+
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
210+
return configClass;
200211
}
201212

202-
final void parse(@Nullable String className, String beanName) throws IOException {
213+
final ConfigurationClass parse(@Nullable String className, String beanName) throws IOException {
203214
Assert.notNull(className, "No bean class name for configuration class bean definition");
204215
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
205-
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
216+
ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
217+
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
218+
return configClass;
206219
}
207220

208221
/**

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

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -67,6 +67,7 @@
6767
import org.springframework.core.task.SyncTaskExecutor;
6868
import org.springframework.stereotype.Component;
6969
import org.springframework.util.Assert;
70+
import org.springframework.util.ClassUtils;
7071

7172
import static org.assertj.core.api.Assertions.assertThat;
7273
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -104,6 +105,7 @@ void enhancementIsPresentBecauseSingletonSemanticsAreRespected() {
104105
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
105106
pp.postProcessBeanFactory(beanFactory);
106107
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
108+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
107109
Foo foo = beanFactory.getBean("foo", Foo.class);
108110
Bar bar = beanFactory.getBean("bar", Bar.class);
109111
assertThat(bar.foo).isSameAs(foo);
@@ -118,6 +120,7 @@ void enhancementIsPresentBecauseSingletonSemanticsAreRespectedUsingAsm() {
118120
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
119121
pp.postProcessBeanFactory(beanFactory);
120122
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
123+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
121124
Foo foo = beanFactory.getBean("foo", Foo.class);
122125
Bar bar = beanFactory.getBean("bar", Bar.class);
123126
assertThat(bar.foo).isSameAs(foo);
@@ -132,6 +135,7 @@ void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() {
132135
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
133136
pp.postProcessBeanFactory(beanFactory);
134137
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
138+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
135139
Foo foo = beanFactory.getBean("foo", Foo.class);
136140
Bar bar = beanFactory.getBean("bar", Bar.class);
137141
assertThat(bar.foo).isNotSameAs(foo);
@@ -143,6 +147,7 @@ void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalseUsingAsm() {
143147
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
144148
pp.postProcessBeanFactory(beanFactory);
145149
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
150+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
146151
Foo foo = beanFactory.getBean("foo", Foo.class);
147152
Bar bar = beanFactory.getBean("bar", Bar.class);
148153
assertThat(bar.foo).isNotSameAs(foo);
@@ -154,6 +159,7 @@ void enhancementIsNotPresentForStaticMethods() {
154159
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
155160
pp.postProcessBeanFactory(beanFactory);
156161
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
162+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
157163
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
158164
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
159165
Foo foo = beanFactory.getBean("foo", Foo.class);
@@ -167,13 +173,23 @@ void enhancementIsNotPresentForStaticMethodsUsingAsm() {
167173
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
168174
pp.postProcessBeanFactory(beanFactory);
169175
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
176+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
170177
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
171178
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
172179
Foo foo = beanFactory.getBean("foo", Foo.class);
173180
Bar bar = beanFactory.getBean("bar", Bar.class);
174181
assertThat(bar.foo).isNotSameAs(foo);
175182
}
176183

184+
@Test
185+
void enhancementIsNotPresentWithEmptyConfig() {
186+
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(EmptyConfig.class));
187+
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
188+
pp.postProcessBeanFactory(beanFactory);
189+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
190+
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
191+
}
192+
177193
@Test
178194
void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
179195
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
@@ -1166,7 +1182,7 @@ static class NonEnhancedSingletonBeanConfig {
11661182
}
11671183

11681184
@Configuration
1169-
static class StaticSingletonBeanConfig {
1185+
static final class StaticSingletonBeanConfig {
11701186

11711187
@Bean
11721188
public static Foo foo() {
@@ -1179,6 +1195,10 @@ public static Bar bar() {
11791195
}
11801196
}
11811197

1198+
@Configuration
1199+
static final class EmptyConfig {
1200+
}
1201+
11821202
@Configuration
11831203
@Order(2)
11841204
static class OverridingSingletonBeanConfig {

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -37,16 +37,18 @@ class InvalidConfigurationClassDefinitionTests {
3737
@Test
3838
void configurationClassesMayNotBeFinal() {
3939
@Configuration
40-
final class Config { }
40+
final class Config {
41+
@Bean String dummy() { return "dummy"; }
42+
}
4143

4244
BeanDefinition configBeanDef = rootBeanDefinition(Config.class).getBeanDefinition();
4345
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
4446
beanFactory.registerBeanDefinition("config", configBeanDef);
4547

4648
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
47-
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
48-
pp.postProcessBeanFactory(beanFactory))
49-
.withMessageContaining("Remove the final modifier");
49+
assertThatExceptionOfType(BeanDefinitionParsingException.class)
50+
.isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
51+
.withMessageContaining("Remove the final modifier");
5052
}
5153

5254
}

spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/ValueCglibConfiguration.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -17,6 +17,7 @@
1717
package org.springframework.context.testfixture.context.annotation;
1818

1919
import org.springframework.beans.factory.annotation.Value;
20+
import org.springframework.context.annotation.Bean;
2021
import org.springframework.context.annotation.Configuration;
2122

2223
@Configuration
@@ -31,4 +32,10 @@ public ValueCglibConfiguration(@Value("${name:World}") String name) {
3132
public String getName() {
3233
return this.name;
3334
}
35+
36+
@Bean
37+
public String dummy() {
38+
return "dummy";
39+
}
40+
3441
}

0 commit comments

Comments
 (0)