From 77964374f9823fa44d16f6c7dfbc26ab7031dd33 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 14 Jun 2024 22:07:46 +0200 Subject: [PATCH] Do not attempt to load pre-enhanced class for reloadable classes Closes gh-33024 (cherry picked from commit 089e4e69f1adbbe45d3a17807e7268a57e9a5ddf) --- .../ConfigurationClassEnhancer.java | 11 ++- .../ConfigurationClassEnhancerTests.java | 87 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 2521a0f2f5a6..db2f77a20ff4 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -47,6 +47,7 @@ import org.springframework.cglib.proxy.NoOp; import org.springframework.cglib.transform.ClassEmitterTransformer; import org.springframework.cglib.transform.TransformingClassGenerator; +import org.springframework.core.SmartClassLoader; import org.springframework.lang.Nullable; import org.springframework.objenesis.ObjenesisException; import org.springframework.objenesis.SpringObjenesis; @@ -123,13 +124,21 @@ private Enhancer newEnhancer(Class configSuperClass, @Nullable ClassLoader cl enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setAttemptLoad(true); + enhancer.setAttemptLoad(!isClassReloadable(configSuperClass, classLoader)); enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); return enhancer; } + /** + * Checks whether the given configuration class is reloadable. + */ + private boolean isClassReloadable(Class configSuperClass, @Nullable ClassLoader classLoader) { + return (classLoader instanceof SmartClassLoader smartClassLoader && + smartClassLoader.isClassReloadable(configSuperClass)); + } + /** * Uses enhancer to generate a subclass of superclass, * ensuring that callbacks are registered for the new subclass. diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java new file mode 100644 index 000000000000..96051f161729 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.io.IOException; +import java.io.InputStream; +import java.security.SecureClassLoader; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.SmartClassLoader; +import org.springframework.util.StreamUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Phillip Webb + * @author Juergen Hoeller + */ +class ConfigurationClassEnhancerTests { + + @Test + void enhanceReloadedClass() throws Exception { + ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer(); + ClassLoader parentClassLoader = getClass().getClassLoader(); + CustomClassLoader classLoader = new CustomClassLoader(parentClassLoader); + Class myClass = parentClassLoader.loadClass(MyConfig.class.getName()); + configurationClassEnhancer.enhance(myClass, parentClassLoader); + Class myReloadedClass = classLoader.loadClass(MyConfig.class.getName()); + Class enhancedReloadedClass = configurationClassEnhancer.enhance(myReloadedClass, classLoader); + assertThat(enhancedReloadedClass.getClassLoader()).isEqualTo(classLoader); + } + + + @Configuration + static class MyConfig { + + @Bean + public String myBean() { + return "bean"; + } + } + + + static class CustomClassLoader extends SecureClassLoader implements SmartClassLoader { + + CustomClassLoader(ClassLoader parent) { + super(parent); + } + + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.contains("MyConfig")) { + String path = name.replace('.', '/').concat(".class"); + try (InputStream in = super.getResourceAsStream(path)) { + byte[] bytes = StreamUtils.copyToByteArray(in); + if (bytes.length > 0) { + return defineClass(name, bytes, 0, bytes.length); + } + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + return super.loadClass(name, resolve); + } + + @Override + public boolean isClassReloadable(Class clazz) { + return clazz.getName().contains("MyConfig"); + } + } + +}