Handle hints for CGLIB proxies consistently

This commit makes sure that hints are registered for CGLIB proxies even
if the proxy itself is not created. This typically happens when AOT runs
on an existing classpath, and a previous run already created the proxy.

Closes gh-29295
This commit is contained in:
Stephane Nicoll
2022-10-10 13:46:26 +02:00
parent e8ce86a6f0
commit 4eca87baa3
7 changed files with 142 additions and 52 deletions

View File

@@ -21,7 +21,10 @@ import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,6 +34,8 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation6.ComponentForScanning;
import org.springframework.context.annotation6.ConfigForScanning;
import org.springframework.context.annotation6.Jsr330NamedForScanning;
import org.springframework.context.testfixture.context.annotation.CglibConfiguration;
import org.springframework.context.testfixture.context.annotation.LambdaBeanConfiguration;
import org.springframework.core.ResolvableType;
import org.springframework.util.ObjectUtils;
@@ -450,6 +455,40 @@ class AnnotationConfigApplicationContextTests {
assertThat(bean.applicationContext).isSameAs(context);
}
@Test
void refreshForAotRegisterHintsForCglibProxy() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(CglibConfiguration.class);
RuntimeHints runtimeHints = new RuntimeHints();
context.refreshForAotProcessing(runtimeHints);
TypeReference cglibType = TypeReference.of(CglibConfiguration.class.getName() + "$$SpringCGLIB$$0");
assertThat(RuntimeHintsPredicates.reflection().onType(cglibType)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS))
.accepts(runtimeHints);
}
@Test
void refreshForAotRegisterHintsForTargetOfCglibProxy() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(CglibConfiguration.class);
RuntimeHints runtimeHints = new RuntimeHints();
context.refreshForAotProcessing(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(CglibConfiguration.class))
.withMemberCategories(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS))
.accepts(runtimeHints);
}
@Test
void refreshForAotRegisterDoesNotConsiderLambdaBeanAsCglibProxy() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(LambdaBeanConfiguration.class);
RuntimeHints runtimeHints = new RuntimeHints();
context.refreshForAotProcessing(runtimeHints);
assertThat(runtimeHints.reflection().typeHints()).isEmpty();
}
@Configuration
static class Config {

View File

@@ -297,10 +297,15 @@ class ApplicationContextAotGeneratorTests {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
TestGenerationContext context = processAheadOfTime(applicationContext);
String proxyClassName = CglibConfiguration.class.getName() + "$$SpringCGLIB$$0";
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$0");
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$1");
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$2");
}
private void isRegisteredCglibClass(TestGenerationContext context, String cglibClassName) throws IOException {
assertThat(context.getGeneratedFiles()
.getGeneratedFileContent(Kind.CLASS, proxyClassName.replace('.', '/') + ".class")).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(proxyClassName))
.getGeneratedFileContent(Kind.CLASS, cglibClassName.replace('.', '/') + ".class")).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(cglibClassName))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
}

View File

@@ -30,57 +30,46 @@ import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.core.io.InputStreamSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link GeneratedClassHandler}.
* Tests for {@link CglibClassHandler}.
*
* @author Stephane Nicoll
*/
class GeneratedClassHandlerTests {
class CglibClassHandlerTests {
private static final byte[] TEST_CONTENT = new byte[] { 'a' };
private final TestGenerationContext generationContext;
private final GeneratedClassHandler handler;
private final CglibClassHandler handler;
public GeneratedClassHandlerTests() {
public CglibClassHandlerTests() {
this.generationContext = new TestGenerationContext();
this.handler = new GeneratedClassHandler(this.generationContext);
this.handler = new CglibClassHandler(this.generationContext);
}
@Test
void handlerGenerateRuntimeHintsForProxy() {
void handlerGeneratedClassCreatesRuntimeHintsForProxy() {
String className = "com.example.Test$$SpringCGLIB$$0";
this.handler.accept(className, TEST_CONTENT);
this.handler.handleGeneratedClass(className, TEST_CONTENT);
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(className))
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS))
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
.accepts(this.generationContext.getRuntimeHints());
}
@Test
void handlerGenerateRuntimeHintsForTargetType() {
String className = "com.example.Test$$SpringCGLIB$$0";
this.handler.accept(className, TEST_CONTENT);
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of("com.example.Test"))
.withMemberCategories(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS))
void handlerLoadedClassCreatesRuntimeHintsForProxy() {
this.handler.handleLoadedClass(CglibClassHandler.class);
assertThat(RuntimeHintsPredicates.reflection().onType(CglibClassHandler.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
.accepts(this.generationContext.getRuntimeHints());
}
@Test
void handlerFailsWithInvalidProxyClassName() {
String className = "com.example.Test$$AnotherProxy$$0";
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.accept(className, TEST_CONTENT))
.withMessageContaining("Failed to extract target type");
}
@Test
void handlerRegisterGeneratedClass() throws IOException {
String className = "com.example.Test$$SpringCGLIB$$0";
this.handler.accept(className, TEST_CONTENT);
this.handler.handleGeneratedClass(className, TEST_CONTENT);
InMemoryGeneratedFiles generatedFiles = this.generationContext.getGeneratedFiles();
assertThat(generatedFiles.getGeneratedFiles(Kind.SOURCE)).isEmpty();
assertThat(generatedFiles.getGeneratedFiles(Kind.RESOURCE)).isEmpty();