Skip to content

Commit dc58074

Browse files
committed
Merge branch '6.2.x'
2 parents 466ac6b + aff9ac7 commit dc58074

File tree

5 files changed

+74
-22
lines changed

5 files changed

+74
-22
lines changed

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

Lines changed: 13 additions & 3 deletions
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.
@@ -202,6 +202,15 @@ Set<BeanMethod> getBeanMethods() {
202202
return this.beanMethods;
203203
}
204204

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

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

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,22 @@ public void parse(Set<BeanDefinitionHolder> configCandidates) {
166166
for (BeanDefinitionHolder holder : configCandidates) {
167167
BeanDefinition bd = holder.getBeanDefinition();
168168
try {
169+
ConfigurationClass configClass;
169170
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
170-
parse(annotatedBeanDef, holder.getBeanName());
171+
configClass = parse(annotatedBeanDef, holder.getBeanName());
171172
}
172173
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
173-
parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
174+
configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
174175
}
175176
else {
176-
parse(bd.getBeanClassName(), holder.getBeanName());
177+
configClass = parse(bd.getBeanClassName(), holder.getBeanName());
178+
}
179+
180+
// Downgrade to lite (no enhancement) in case of no instance-level @Bean methods.
181+
if (!configClass.hasNonStaticBeanMethods() && ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
182+
bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
183+
bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
184+
ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
177185
}
178186
}
179187
catch (BeanDefinitionStoreException ex) {
@@ -188,20 +196,25 @@ else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef
188196
this.deferredImportSelectorHandler.process();
189197
}
190198

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

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

201-
final void parse(@Nullable String className, String beanName) throws IOException {
212+
final ConfigurationClass parse(@Nullable String className, String beanName) throws IOException {
202213
Assert.notNull(className, "No bean class name for configuration class bean definition");
203214
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
204-
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
215+
ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
216+
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
217+
return configClass;
205218
}
206219

207220
/**

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

Lines changed: 22 additions & 2 deletions
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

Lines changed: 7 additions & 5 deletions
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

Lines changed: 8 additions & 1 deletion
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)