diff --git a/spring-boot-project/spring-boot-devtools/build.gradle b/spring-boot-project/spring-boot-devtools/build.gradle index d4a315540bd..8befce0b92a 100644 --- a/spring-boot-project/spring-boot-devtools/build.gradle +++ b/spring-boot-project/spring-boot-devtools/build.gradle @@ -70,6 +70,7 @@ dependencies { testImplementation("org.thymeleaf:thymeleaf-spring5") testImplementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") + testRuntimeOnly("org.aspectj:aspectjweaver") testRuntimeOnly("org.yaml:snakeyaml") } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoader.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoader.java index 3ec58716ae9..1021b20ceec 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoader.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoader.java @@ -173,6 +173,11 @@ public class RestartClassLoader extends URLClassLoader implements SmartClassLoad return defineClass(name, b, 0, b.length, protectionDomain); } + @Override + public ClassLoader getOriginalClassLoader() { + return getParent(); + } + private URL createFileUrl(String name, ClassLoaderFile file) { try { return new URL("reloaded", null, -1, "/" + name, new ClassLoaderFileURLStreamHandler(file)); diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java index b9fbbc7b32d..9d967fbaf96 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/classloader/RestartClassLoaderTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; @@ -36,7 +37,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.AopUtils; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; @@ -210,6 +218,20 @@ class RestartClassLoaderTests { // Warning would happen outside the boundary of the test } + @Test + void packagePrivateClassLoadedByParentClassLoaderCanBeProxied() throws MalformedURLException { + new ApplicationContextRunner() + .withClassLoader(new RestartClassLoader(ExampleTransactional.class.getClassLoader(), + new URL[] { this.sampleJarFile.toURI().toURL() }, this.updatedFiles)) + .withUserConfiguration(ProxyConfiguration.class).run((context) -> { + assertThat(context).hasNotFailed(); + ExampleTransactional transactional = context.getBean(ExampleTransactional.class); + assertThat(AopUtils.isCglibProxy(transactional)).isTrue(); + assertThat(transactional.getClass().getClassLoader()) + .isEqualTo(ExampleTransactional.class.getClassLoader()); + }); + } + private String readString(InputStream in) throws IOException { return new String(FileCopyUtils.copyToByteArray(in)); } @@ -218,4 +240,32 @@ class RestartClassLoaderTests { return (enumeration != null) ? Collections.list(enumeration) : Collections.emptyList(); } + @Configuration(proxyBeanMethods = false) + @EnableAspectJAutoProxy(proxyTargetClass = true) + @EnableTransactionManagement + static class ProxyConfiguration { + + @Bean + ExampleTransactional exampleTransactional() { + return new ExampleTransactional(); + } + + } + + static class ExampleTransactional implements ExampleInterface { + + @Override + @Transactional + public String doIt() { + return "hello"; + } + + } + + interface ExampleInterface { + + String doIt(); + + } + }