Avoid unnecessary CGLIB processing on configuration classes

Closes gh-34486
This commit is contained in:
Juergen Hoeller
2025-02-25 16:20:12 +01:00
parent f895d762cd
commit aff9ac72ec
5 changed files with 75 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -203,6 +203,15 @@ final class ConfigurationClass {
return this.beanMethods; return this.beanMethods;
} }
boolean hasNonStaticBeanMethods() {
for (BeanMethod beanMethod : this.beanMethods) {
if (!beanMethod.getMetadata().isStatic()) {
return true;
}
}
return false;
}
void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) { void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
this.importedResources.put(importedResource, readerClass); this.importedResources.put(importedResource, readerClass);
} }
@@ -223,8 +232,9 @@ final class ConfigurationClass {
void validate(ProblemReporter problemReporter) { void validate(ProblemReporter problemReporter) {
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName()); Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false // A configuration class may not be final (CGLIB limitation) unless it does not have to proxy bean methods
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && this.metadata.isFinal()) { if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && hasNonStaticBeanMethods() &&
this.metadata.isFinal()) {
problemReporter.error(new FinalConfigurationProblem()); problemReporter.error(new FinalConfigurationProblem());
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -167,14 +167,22 @@ class ConfigurationClassParser {
for (BeanDefinitionHolder holder : configCandidates) { for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition(); BeanDefinition bd = holder.getBeanDefinition();
try { try {
ConfigurationClass configClass;
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) { if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
parse(annotatedBeanDef, holder.getBeanName()); configClass = parse(annotatedBeanDef, holder.getBeanName());
} }
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) { else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
parse(abstractBeanDef.getBeanClass(), holder.getBeanName()); configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
} }
else { else {
parse(bd.getBeanClassName(), holder.getBeanName()); configClass = parse(bd.getBeanClassName(), holder.getBeanName());
}
// Downgrade to lite (no enhancement) in case of no instance-level @Bean methods.
if (!configClass.hasNonStaticBeanMethods() && ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
} }
} }
catch (BeanDefinitionStoreException ex) { catch (BeanDefinitionStoreException ex) {
@@ -189,20 +197,25 @@ class ConfigurationClassParser {
this.deferredImportSelectorHandler.process(); this.deferredImportSelectorHandler.process();
} }
private void parse(AnnotatedBeanDefinition beanDef, String beanName) { private ConfigurationClass parse(AnnotatedBeanDefinition beanDef, String beanName) {
processConfigurationClass( ConfigurationClass configClass = new ConfigurationClass(
new ConfigurationClass(beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition)), beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition));
DEFAULT_EXCLUSION_FILTER); processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
return configClass;
} }
private void parse(Class<?> clazz, String beanName) { private ConfigurationClass parse(Class<?> clazz, String beanName) {
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER); ConfigurationClass configClass = new ConfigurationClass(clazz, beanName);
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
return configClass;
} }
final void parse(@Nullable String className, String beanName) throws IOException { final ConfigurationClass parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition"); Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER); ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
return configClass;
} }
/** /**

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -67,6 +67,7 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -104,6 +105,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory); pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class); Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class); Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo); assertThat(bar.foo).isSameAs(foo);
@@ -118,6 +120,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory); pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class); Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class); Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo); assertThat(bar.foo).isSameAs(foo);
@@ -132,6 +135,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory); pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class); Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class); Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isNotSameAs(foo); assertThat(bar.foo).isNotSameAs(foo);
@@ -143,6 +147,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory); pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class); Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class); Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isNotSameAs(foo); assertThat(bar.foo).isNotSameAs(foo);
@@ -154,6 +159,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory); pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
Foo foo = beanFactory.getBean("foo", Foo.class); Foo foo = beanFactory.getBean("foo", Foo.class);
@@ -167,6 +173,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory); pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue(); assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
Foo foo = beanFactory.getBean("foo", Foo.class); Foo foo = beanFactory.getBean("foo", Foo.class);
@@ -174,6 +181,15 @@ class ConfigurationClassPostProcessorTests {
assertThat(bar.foo).isNotSameAs(foo); assertThat(bar.foo).isNotSameAs(foo);
} }
@Test
void enhancementIsNotPresentWithEmptyConfig() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(EmptyConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
}
@Test @Test
void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() { void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig")); beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
@@ -1166,7 +1182,7 @@ class ConfigurationClassPostProcessorTests {
} }
@Configuration @Configuration
static class StaticSingletonBeanConfig { static final class StaticSingletonBeanConfig {
@Bean @Bean
public static Foo foo() { public static Foo foo() {
@@ -1179,6 +1195,10 @@ class ConfigurationClassPostProcessorTests {
} }
} }
@Configuration
static final class EmptyConfig {
}
@Configuration @Configuration
@Order(2) @Order(2)
static class OverridingSingletonBeanConfig { static class OverridingSingletonBeanConfig {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -37,16 +37,18 @@ class InvalidConfigurationClassDefinitionTests {
@Test @Test
void configurationClassesMayNotBeFinal() { void configurationClassesMayNotBeFinal() {
@Configuration @Configuration
final class Config { } final class Config {
@Bean String dummy() { return "dummy"; }
}
BeanDefinition configBeanDef = rootBeanDefinition(Config.class).getBeanDefinition(); BeanDefinition configBeanDef = rootBeanDefinition(Config.class).getBeanDefinition();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("config", configBeanDef); beanFactory.registerBeanDefinition("config", configBeanDef);
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> assertThatExceptionOfType(BeanDefinitionParsingException.class)
pp.postProcessBeanFactory(beanFactory)) .isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
.withMessageContaining("Remove the final modifier"); .withMessageContaining("Remove the final modifier");
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.context.testfixture.context.annotation; package org.springframework.context.testfixture.context.annotation;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@@ -31,4 +32,10 @@ public class ValueCglibConfiguration {
public String getName() { public String getName() {
return this.name; return this.name;
} }
@Bean
public String dummy() {
return "dummy";
}
} }