Skip to content

ConfigurationClassEnhancer does not use correct ClassLoader when called multiple times #33024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
philwebb opened this issue Jun 14, 2024 · 8 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@philwebb
Copy link
Member

Discovered whilst looking into spring-projects/spring-boot#39733

The following test will pass with Spring Framework 5.3.36 but fail with Spring Framework 6.0.12:

package org.springframework.context.annotation;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.io.InputStream;
import java.security.SecureClassLoader;

import org.junit.jupiter.api.Test;
import org.springframework.util.StreamUtils;

class ConfigurationClassEnhancerTests {

	@Test
	void test() throws Exception {
		ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
		ClassLoader parentClassLoader = getClass().getClassLoader();
		MyClassLoader classLoader = new MyClassLoader(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 MyClassLoader extends SecureClassLoader {

		MyClassLoader(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);
		}

	}

}

I haven't managed to exactly track down why, but I suspect that b31a158 might have introduced the problem. Perhaps this key?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 14, 2024
@snicoll snicoll added the in: core Issues in core modules (aop, beans, core, context, expression) label Jun 14, 2024
@jhoeller jhoeller changed the title ConfigurationClassEnhancer doesn't not use correct classLoader when called muliple times ConfigurationClassEnhancer doesn't not use correct ClassLoader when called multiple times Jun 14, 2024
@jhoeller
Copy link
Contributor

This is caused by the enhancer.setAttemptLoad(true) setting which we apply as of 6.0 for AOT purposes. Without that flag, the test passes on 6.x as well.

AttemptLoad seems to load a class with the same name irrespective of the specified ClassLoader. Maybe we need to conditionally set it to false depending on the ClassLoader arrangement.

@jhoeller jhoeller added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jun 14, 2024
@jhoeller jhoeller added this to the 6.1.10 milestone Jun 14, 2024
@jhoeller jhoeller self-assigned this Jun 14, 2024
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-6.0.x labels Jun 14, 2024
@philwebb
Copy link
Member Author

I guess we're setting setAttemptLoad(true) to pick up any class files that we generated during the AOT phase. In this case, I think the first run is generating class bytecode in the parent loader and the second run is then picking that up. Ideally we want a way to pick up classes that were AOT generated but not those that were generated in-memory.

@jhoeller
Copy link
Contributor

jhoeller commented Jun 14, 2024

Since we are primarily talking about reload scenarios, maybe we should simply set AttemptLoad to false if the current configuration class passes SmartClassLoader.isClassReloadable. That would cover Boot's scenario, wouldn't it?

In general, cleanly differentiating between pre-loaded classes and pre-defined classes is impossible for an arbitrary target ClassLoader. The ClassLoader API is trying hard to treat those the same, with only the protected findLoadedClass allowing to differentiate within the ClassLoader implementation itself. We got that problem in ContextTypeMatchClassLoader already, and it would be good to avoid any further variations of this... not least of it all: for compatibility with the module system.

@jhoeller
Copy link
Contributor

A modified test case where the custom ClassLoader implements SmartClassLoader.isClassReloadable passes fine with a corresponding check in ConfigurationClassEnhancer. I intend to go with this for 6.1.10 and 6.0.23.

@philwebb
Copy link
Member Author

Sounds good. Once that's in I can update Boot and test the read sample again.

@philwebb
Copy link
Member Author

Actually we already implement that method so we may not need any Boot changes.

@jhoeller
Copy link
Contributor

I would expect Boot's RestartClassLoader to be naturally compliant already, indeed. Alright, about to push this to 6.1.x here.

jhoeller added a commit that referenced this issue Jun 14, 2024

Verified

This commit was signed with the committer’s verified signature.
ppkarwasz Piotr P. Karwasz
Closes gh-33024

(cherry picked from commit 089e4e6)
@philwebb
Copy link
Member Author

Tested and all looks good 👍

@sbrannen sbrannen changed the title ConfigurationClassEnhancer doesn't not use correct ClassLoader when called multiple times ConfigurationClassEnhancer does not use correct ClassLoader when called multiple times Aug 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants