Skip to content

Commit

Permalink
Update AOT support after RuntimeHints changes
Browse files Browse the repository at this point in the history
This commit adapts AOT support in various modules after the RuntimeHints
and related deprecation changes.

`MemberCategory.INTROSPECT_*` hints are now removed and
`MemberCategory.*_FIELDS` are  replaced with
`MemberCategory.INVOKE*_FIELDS` when invocation is needed.

Usage of `RuntimeHintsAgent` are also deprecated.

Closes gh-33847
  • Loading branch information
bclozel committed Nov 29, 2024
1 parent 0759129 commit ba312f6
Show file tree
Hide file tree
Showing 31 changed files with 81 additions and 111 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
Expand All @@ -24,7 +24,6 @@
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
import org.springframework.core.SpringVersion;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -33,6 +32,7 @@
// method is only enabled if the RuntimeHintsAgent is loaded on the current JVM.
// It also tags tests with the "RuntimeHints" JUnit tag.
@EnabledIfRuntimeHintsAgent
@SuppressWarnings("removal")
class SampleReflectionRuntimeHintsTests {

@Test
Expand All @@ -43,7 +43,7 @@ void shouldRegisterReflectionHints() {
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));

// Invoke the relevant piece of code we want to test within a recording lambda
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> {
SampleReflection sample = new SampleReflection();
sample.performReflection();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
Expand All @@ -18,24 +18,23 @@

import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
import org.springframework.aot.test.agent.RuntimeHintsRecorder;

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


@EnabledIfRuntimeHintsAgent
@SuppressWarnings("removal")
class ReflectionInvocationsTests {

@Test
void sampleTest() {
RuntimeHints hints = new RuntimeHints();
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
hints.reflection().registerType(String.class);

RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> {
SampleReflection sample = new SampleReflection();
sample.sample(); // does Method[] methods = String.class.getMethods();
});
Expand All @@ -45,9 +44,9 @@ void sampleTest() {
@Test
void multipleCallsTest() {
RuntimeHints hints = new RuntimeHints();
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
hints.reflection().registerType(Integer.class,MemberCategory.INTROSPECT_PUBLIC_METHODS);
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
hints.reflection().registerType(String.class);
hints.reflection().registerType(Integer.class);
RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> {
SampleReflection sample = new SampleReflection();
sample.multipleCalls(); // does Method[] methods = String.class.getMethods(); methods = Integer.class.getMethods();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public AspectJAdvisorContribution(Class<?> beanClass) {

@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
generationContext.getRuntimeHints().reflection().registerType(this.beanClass, MemberCategory.DECLARED_FIELDS);
generationContext.getRuntimeHints().reflection().registerType(this.beanClass, MemberCategory.INVOKE_DECLARED_FIELDS);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class AspectJAdvisorBeanRegistrationAotProcessorTests {
@Test
void shouldProcessAspectJClass() {
process(AspectJClass.class);
assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.DECLARED_FIELDS))
assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS))
.accepts(this.runtimeHints);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,7 @@ private CodeBlock generateMethodStatementForMethod(CodeWarnings codeWarnings,
}
else {
codeWarnings.detectDeprecation(method);
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
hints.reflection().registerType(method.getDeclaringClass());
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(this.target,
method).generateCode(method.getParameterTypes());
CodeBlock injectionCode = CodeBlock.of("args -> $L.$L($L)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,10 @@ private void registerReflectionHints(RootBeanDefinition beanDefinition, Method w
// ReflectionUtils#findField searches recursively in the type hierarchy
Class<?> searchType = beanDefinition.getTargetType();
while (searchType != null && searchType != writeMethod.getDeclaringClass()) {
this.hints.reflection().registerType(searchType, MemberCategory.DECLARED_FIELDS);
this.hints.reflection().registerType(searchType, MemberCategory.INVOKE_DECLARED_FIELDS);
searchType = searchType.getSuperclass();
}
this.hints.reflection().registerType(writeMethod.getDeclaringClass(), MemberCategory.DECLARED_FIELDS);
this.hints.reflection().registerType(writeMethod.getDeclaringClass(), MemberCategory.INVOKE_DECLARED_FIELDS);
}

private void addQualifiers(CodeBlock.Builder code, RootBeanDefinition beanDefinition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.javapoet.ClassName;
Expand Down Expand Up @@ -100,8 +100,8 @@ private void generateRegisterHints(RuntimeHints runtimeHints, List<Registration>
registrations.forEach(registration -> {
ReflectionHints hints = runtimeHints.reflection();
Class<?> beanClass = registration.registeredBean.getBeanClass();
hints.registerType(beanClass, MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS);
hints.registerForInterfaces(beanClass, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
hints.registerType(beanClass);
hints.registerForInterfaces(beanClass, TypeHint.Builder::withMembers);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Cons
}

private CodeBlock generateCodeForAccessibleConstructor(String beanName, Constructor<?> constructor) {
this.generationContext.getRuntimeHints().reflection().registerConstructor(
constructor, ExecutableMode.INTROSPECT);
this.generationContext.getRuntimeHints().reflection().registerType(constructor.getDeclaringClass());

if (constructor.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) {
Expand Down Expand Up @@ -265,7 +264,7 @@ private CodeBlock generateCodeForFactoryMethod(
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
Method factoryMethod, Class<?> targetClass, @Nullable String factoryBeanName) {

this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INTROSPECT);
this.generationContext.getRuntimeHints().reflection().registerType(factoryMethod.getDeclaringClass());

if (factoryBeanName == null && factoryMethod.getParameterCount() == 0) {
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames

private void assertHasDeclaredFieldsHint(Class<?> beanType) {
assertThat(RuntimeHintsPredicates.reflection()
.onType(beanType).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.onType(beanType).withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS))
.accepts(this.generationContext.getRuntimeHints());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.generate.ValueCodeGenerationException;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
Expand Down Expand Up @@ -149,14 +148,11 @@ void applyToRegisterReflectionHints() {
registeredBean, null, List.of());
BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator);
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
assertThat(reflection().onType(Employee.class)
.withMemberCategories(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS))
assertThat(reflection().onType(Employee.class))
.accepts(this.generationContext.getRuntimeHints());
assertThat(reflection().onType(ITestBean.class)
.withMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS))
assertThat(reflection().onType(ITestBean.class))
.accepts(this.generationContext.getRuntimeHints());
assertThat(reflection().onType(AgeHolder.class)
.withMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS))
assertThat(reflection().onType(AgeHolder.class))
.accepts(this.generationContext.getRuntimeHints());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ void generateWhenHasDefaultConstructor() {
assertThat(compiled.getSourceFile())
.contains("InstanceSupplier.using(TestBean::new)");
});
assertThat(getReflectionHints().getTypeHint(TestBean.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(TestBean.class)).isNotNull();
}

@Test
Expand All @@ -112,8 +111,7 @@ void generateWhenHasConstructorWithParameter() {
InjectionComponent bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(InjectionComponent.class).extracting("bean").isEqualTo("injected");
});
assertThat(getReflectionHints().getTypeHint(InjectionComponent.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(InjectionComponent.class)).isNotNull();
}

@Test
Expand All @@ -126,8 +124,7 @@ void generateWhenHasConstructorWithInnerClassAndDefaultConstructor() {
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(InnerComponentConfiguration.class).new NoDependencyComponent()");
});
assertThat(getReflectionHints().getTypeHint(NoDependencyComponent.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(NoDependencyComponent.class)).isNotNull();
}

@Test
Expand All @@ -141,8 +138,7 @@ void generateWhenHasConstructorWithInnerClassAndParameter() {
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(");
});
assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponent.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponent.class)).isNotNull();
}

@Test
Expand Down Expand Up @@ -184,8 +180,7 @@ void generateWhenHasConstructorWithGeneric() {
assertThat(bean).extracting("number").isNull(); // No property actually set
assertThat(compiled.getSourceFile()).contains("NumberHolderFactoryBean::new");
});
assertThat(getReflectionHints().getTypeHint(NumberHolderFactoryBean.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(NumberHolderFactoryBean.class)).isNotNull();
}

@Test
Expand Down Expand Up @@ -215,8 +210,7 @@ void generateWhenHasFactoryMethodWithNoArg() {
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(\"config\", SimpleConfiguration.class).stringBean()");
});
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)).isNotNull();
}

@Test
Expand All @@ -232,8 +226,7 @@ void generateWhenHasFactoryMethodOnInterface() {
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(\"config\", DefaultSimpleBeanContract.class).simpleBean()");
});
assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class)).isNotNull();
}

@Test
Expand Down Expand Up @@ -268,8 +261,7 @@ void generateWhenHasStaticFactoryMethodWithNoArg() {
assertThat(compiled.getSourceFile())
.contains("(registeredBean) -> SimpleConfiguration.integerBean()");
});
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)).isNotNull();
}

@Test
Expand All @@ -287,8 +279,7 @@ void generateWhenHasStaticFactoryMethodWithArg() {
assertThat(bean).isEqualTo("42test");
assertThat(compiled.getSourceFile()).contains("SampleFactory.create(");
});
assertThat(getReflectionHints().getTypeHint(SampleFactory.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(SampleFactory.class)).isNotNull();
}

@Test
Expand All @@ -305,8 +296,7 @@ void generateWhenHasFactoryMethodCheckedException() {
assertThat(bean).isEqualTo(42);
assertThat(compiled.getSourceFile()).doesNotContain(") throws Exception {");
});
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)).isNotNull();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ class InstanceSupplierCodeGeneratorKotlinTests {
Assertions.assertThat(bean).isInstanceOf(KotlinTestBean::class.java)
Assertions.assertThat(compiled.sourceFile).contains("InstanceSupplier.using(KotlinTestBean::new)")
}
Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT))
Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java)).isNotNull
}

@Test
Expand Down Expand Up @@ -90,8 +89,7 @@ class InstanceSupplierCodeGeneratorKotlinTests {
"getBeanFactory().getBean(\"config\", KotlinConfiguration.class).stringBean()"
)
}
Assertions.assertThat<TypeHint?>(getReflectionHints().getTypeHint(KotlinConfiguration::class.java))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT))
Assertions.assertThat<TypeHint?>(getReflectionHints().getTypeHint(KotlinConfiguration::class.java)).isNotNull
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ private CodeBlock generateMethodStatementForMethod(ClassName targetClassName,
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
}
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
hints.reflection().registerType(method.getDeclaringClass());
return CodeBlock.of("$L.$L($L.resolve($L))", INSTANCE_PARAMETER,
method.getName(), resolver, REGISTERED_BEAN_PARAMETER);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ResourceHints;
import org.springframework.aot.hint.RuntimeHints;
Expand Down Expand Up @@ -812,7 +811,7 @@ private InstantiationDescriptor proxyInstantiationDescriptor(
Executable userExecutable = instantiationDescriptor.executable();
if (userExecutable instanceof Constructor<?> userConstructor) {
try {
runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT);
runtimeHints.reflection().registerType(userConstructor.getDeclaringClass());
Constructor<?> constructor = this.proxyClass.getConstructor(userExecutable.getParameterTypes());
return new InstantiationDescriptor(constructor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.springframework.context.aot;

import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
Expand Down Expand Up @@ -61,7 +60,7 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be

private void registerHints(Class<?> type, RuntimeHints runtimeHints) {
if (KotlinDetector.isKotlinType(type)) {
runtimeHints.reflection().registerType(type, MemberCategory.INTROSPECT_DECLARED_METHODS);
runtimeHints.reflection().registerType(type);
}
Class<?> superClass = type.getSuperclass();
if (superClass != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public AotContribution(Collection<Class<?>> validatedClasses,
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
ReflectionHints hints = generationContext.getRuntimeHints().reflection();
for (Class<?> validatedClass : this.validatedClasses) {
hints.registerType(validatedClass, MemberCategory.DECLARED_FIELDS);
hints.registerType(validatedClass, MemberCategory.INVOKE_DECLARED_FIELDS);
}
for (Class<? extends ConstraintValidator<?, ?>> constraintValidatorClass : this.constraintValidatorClasses) {
hints.registerType(constraintValidatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ void refreshForAotRegisterHintsForCglibProxy() {
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))
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_FIELDS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(CglibConfiguration.class)
.withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
Expand Down Expand Up @@ -102,7 +102,7 @@ private Consumer<Path> hasGeneratedAssetsForSampleApplication() {
assertThat(directory.resolve(
"source/org/springframework/context/aot/ContextAotProcessorTests_SampleApplication__BeanFactoryRegistrations.java"))
.exists().isRegularFile();
assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reflect-config.json"))
assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reachability-metadata.json"))
.exists().isRegularFile();
Path nativeImagePropertiesFile = directory
.resolve("resource/META-INF/native-image/com.example/example/native-image.properties");
Expand Down
Loading

0 comments on commit ba312f6

Please sign in to comment.