Browse Source

Do not require full type reflection when listing methods/fields

Prior to this commit, the `RuntimeHintsAgent` and its testing
infrastructure would assume that calling `MyClass.class.getMethods()`
requires a reflection hint on the class for introspecting public/private
methods.

GraalVM does not require this, in fact this call only returns methods
that have reflection hints in the native image.

This commit refines the agent behavior for `Class.getMethods()`,
`Class.getDeclaredMethods()`, `Class.getFields()` and
`Class.getDeclaredFields()`. With this change, registering at least one
method/field for reflection is enough to match.

During the execution of Java tests, all methods and fields will be
provided, regardless of hints being registered or not. This could cause
false negatives where we're missing reflection hints on methods or
fields.
This risk is mitigated thanks to additional instrumentation on
`Method.getAnnotations()`, `Method.getParameterTypes()` and
`Method.invoke()`. If a method is found reflectively, chances are it
will be used for further reflection.

Closes gh-29091
pull/29130/head
Brian Clozel 3 years ago
parent
commit
323d1907c1
  1. 16
      integration-tests/src/test/java/org/springframework/aot/RuntimeHintsAgentTests.java
  2. 27
      spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedBridgeMethods.java
  3. 90
      spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java
  4. 136
      spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java
  5. 23
      spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java
  6. 50
      spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java

16
integration-tests/src/test/java/org/springframework/aot/RuntimeHintsAgentTests.java

@ -164,6 +164,22 @@ public class RuntimeHintsAgentTests { @@ -164,6 +164,22 @@ public class RuntimeHintsAgentTests {
throw new RuntimeException(e);
}
}, MethodReference.of(Method.class, "invoke")),
Arguments.of((Runnable) () -> {
try {
toStringMethod.getAnnotations();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Method.class, "getAnnotations")),
Arguments.of((Runnable) () -> {
try {
toStringMethod.getParameterTypes();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Method.class, "getParameterTypes")),
Arguments.of((Runnable) () -> {
try {
privateGreetMethod.invoke(new PrivateClass());

27
spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedBridgeMethods.java

@ -18,6 +18,7 @@ package org.springframework.aot.agent; @@ -18,6 +18,7 @@ package org.springframework.aot.agent;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
@ -335,6 +336,32 @@ public abstract class InstrumentedBridgeMethods { @@ -335,6 +336,32 @@ public abstract class InstrumentedBridgeMethods {
* Bridge methods for java.lang.reflect.Method
*/
public static Annotation[] methodgetAnnotations(Method method) {
Annotation[] result = null;
try {
result = method.getAnnotations();
}
finally {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_GETANNOTATIONS)
.onInstance(method).returnValue(result).build();
RecordedInvocationsPublisher.publish(invocation);
}
return result;
}
public static Class<?>[] methodgetParameterTypes(Method method) {
Class<?>[] result = null;
try {
result = method.getParameterTypes();
}
finally {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_GETPARAMETERTYPES)
.onInstance(method).returnValue(result).build();
RecordedInvocationsPublisher.publish(invocation);
}
return result;
}
public static Object methodinvoke(Method method, Object object, Object... arguments) throws InvocationTargetException, IllegalAccessException {
Object result = null;
boolean accessibilityChanged = false;

90
spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java

@ -31,6 +31,9 @@ import org.springframework.aot.hint.RuntimeHints; @@ -31,6 +31,9 @@ import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource;
/**
* Java method that is instrumented by the {@link RuntimeHintsAgent}.
*
@ -55,7 +58,7 @@ enum InstrumentedMethod { @@ -55,7 +58,7 @@ enum InstrumentedMethod {
CLASS_FORNAME(Class.class, "forName", HintType.REFLECTION,
invocation -> {
String className = invocation.getArgument(0);
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(className));
return reflection().onType(TypeReference.of(className));
}),
/**
@ -64,7 +67,7 @@ enum InstrumentedMethod { @@ -64,7 +67,7 @@ enum InstrumentedMethod {
CLASS_GETCLASSES(Class.class, "getClasses", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
return reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.DECLARED_CLASSES, MemberCategory.PUBLIC_CLASSES);
}
),
@ -78,7 +81,7 @@ enum InstrumentedMethod { @@ -78,7 +81,7 @@ enum InstrumentedMethod {
if (constructor == null) {
return runtimeHints -> false;
}
return RuntimeHintsPredicates.reflection().onConstructor(constructor).introspect();
return reflection().onConstructor(constructor).introspect();
}
),
@ -88,9 +91,10 @@ enum InstrumentedMethod { @@ -88,9 +91,10 @@ enum InstrumentedMethod {
CLASS_GETCONSTRUCTORS(Class.class, "getConstructors", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory(
return reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory(
MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
.or(reflection().onType(TypeReference.of(thisClass)).withAnyConstructor());
}
),
@ -100,7 +104,7 @@ enum InstrumentedMethod { @@ -100,7 +104,7 @@ enum InstrumentedMethod {
CLASS_GETDECLAREDCLASSES(Class.class, "getDeclaredClasses", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_CLASSES);
return reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_CLASSES);
}
),
@ -114,8 +118,8 @@ enum InstrumentedMethod { @@ -114,8 +118,8 @@ enum InstrumentedMethod {
return runtimeHints -> false;
}
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS)
.or(RuntimeHintsPredicates.reflection().onConstructor(constructor).introspect());
return reflection().onType(thisType).withMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS)
.or(reflection().onConstructor(constructor).introspect());
}
),
@ -125,8 +129,9 @@ enum InstrumentedMethod { @@ -125,8 +129,9 @@ enum InstrumentedMethod {
CLASS_GETDECLAREDCONSTRUCTORS(Class.class, "getDeclaredConstructors", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
return reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
.or(reflection().onType(TypeReference.of(thisClass)).withAnyConstructor());
}),
/**
@ -139,8 +144,8 @@ enum InstrumentedMethod { @@ -139,8 +144,8 @@ enum InstrumentedMethod {
return runtimeHints -> false;
}
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS)
.or(RuntimeHintsPredicates.reflection().onField(field));
return reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS)
.or(reflection().onField(field));
}
),
@ -150,7 +155,8 @@ enum InstrumentedMethod { @@ -150,7 +155,8 @@ enum InstrumentedMethod {
CLASS_GETDECLAREDFIELDS(Class.class, "getDeclaredFields", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_FIELDS);
return reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_FIELDS)
.or(reflection().onType(TypeReference.of(thisClass)).withAnyField());
}
),
@ -164,9 +170,9 @@ enum InstrumentedMethod { @@ -164,9 +170,9 @@ enum InstrumentedMethod {
return runtimeHints -> false;
}
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType)
return reflection().onType(thisType)
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)
.or(RuntimeHintsPredicates.reflection().onMethod(method).introspect());
.or(reflection().onMethod(method).introspect());
}
),
@ -176,8 +182,9 @@ enum InstrumentedMethod { @@ -176,8 +182,9 @@ enum InstrumentedMethod {
CLASS_GETDECLAREDMETHODS(Class.class, "getDeclaredMethods", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS);
return reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)
.or(reflection().onType(TypeReference.of(thisClass)).withAnyMethod());
}
),
@ -191,10 +198,10 @@ enum InstrumentedMethod { @@ -191,10 +198,10 @@ enum InstrumentedMethod {
return runtimeHints -> false;
}
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.PUBLIC_FIELDS)
return reflection().onType(thisType).withMemberCategory(MemberCategory.PUBLIC_FIELDS)
.and(runtimeHints -> Modifier.isPublic(field.getModifiers()))
.or(RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.or(RuntimeHintsPredicates.reflection().onField(invocation.getReturnValue()));
.or(reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.or(reflection().onField(invocation.getReturnValue()));
}),
/**
@ -203,8 +210,9 @@ enum InstrumentedMethod { @@ -203,8 +210,9 @@ enum InstrumentedMethod {
CLASS_GETFIELDS(Class.class, "getFields", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS);
return reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS)
.or(reflection().onType(TypeReference.of(thisClass)).withAnyField());
}
),
@ -218,11 +226,11 @@ enum InstrumentedMethod { @@ -218,11 +226,11 @@ enum InstrumentedMethod {
return runtimeHints -> false;
}
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withAnyMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)
return reflection().onType(thisType).withAnyMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)
.and(runtimeHints -> Modifier.isPublic(method.getModifiers()))
.or(RuntimeHintsPredicates.reflection().onType(thisType).withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS))
.or(RuntimeHintsPredicates.reflection().onMethod(method).introspect())
.or(RuntimeHintsPredicates.reflection().onMethod(method).invoke());
.or(reflection().onType(thisType).withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS))
.or(reflection().onMethod(method).introspect())
.or(reflection().onMethod(method).invoke());
}
),
@ -232,9 +240,10 @@ enum InstrumentedMethod { @@ -232,9 +240,10 @@ enum InstrumentedMethod {
CLASS_GETMETHODS(Class.class, "getMethods", HintType.REFLECTION,
invocation -> {
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory(
return reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory(
MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS,
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS);
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)
.or(reflection().onType(TypeReference.of(thisClass)).withAnyMethod());
}
),
@ -247,32 +256,41 @@ enum InstrumentedMethod { @@ -247,32 +256,41 @@ enum InstrumentedMethod {
if (klass == null) {
return runtimeHints -> false;
}
return RuntimeHintsPredicates.reflection().onType(klass);
return reflection().onType(klass);
}),
/**
* {@link Constructor#newInstance(Object...)}.
*/
CONSTRUCTOR_NEWINSTANCE(Constructor.class, "newInstance", HintType.REFLECTION,
invocation -> RuntimeHintsPredicates.reflection().onConstructor(invocation.getInstance()).invoke()),
invocation -> reflection().onConstructor(invocation.getInstance()).invoke()),
/**
* {@link Method#getParameterTypes()}.
*/
METHOD_GETANNOTATIONS(Method.class, "getAnnotations", HintType.REFLECTION,
invocation -> reflection().onMethod(invocation.getInstance())),
METHOD_GETPARAMETERTYPES(Method.class, "getParameterTypes", HintType.REFLECTION,
invocation -> reflection().onMethod(invocation.getInstance())),
/**
* {@link Method#invoke(Object, Object...)}.
*/
METHOD_INVOKE(Method.class, "invoke", HintType.REFLECTION,
invocation -> RuntimeHintsPredicates.reflection().onMethod(invocation.getInstance()).invoke()),
invocation -> reflection().onMethod(invocation.getInstance()).invoke()),
/**
* {@link Field#get(Object)}.
*/
FIELD_GET(Field.class, "get", HintType.REFLECTION,
invocation -> RuntimeHintsPredicates.reflection().onField(invocation.getInstance())),
invocation -> reflection().onField(invocation.getInstance())),
/**
* {@link Field#set(Object, Object)}.
*/
FIELD_SET(Field.class, "set", HintType.REFLECTION,
invocation -> RuntimeHintsPredicates.reflection().onField(invocation.getInstance()).withWriteMode()),
invocation -> reflection().onField(invocation.getInstance()).withWriteMode()),
/*
@ -285,7 +303,7 @@ enum InstrumentedMethod { @@ -285,7 +303,7 @@ enum InstrumentedMethod {
RESOURCEBUNDLE_GETBUNDLE(ResourceBundle.class, "getBundle", HintType.RESOURCE_BUNDLE,
invocation -> {
String bundleName = invocation.getArgument(0);
return RuntimeHintsPredicates.resource().forBundle(bundleName);
return resource().forBundle(bundleName);
}),
/*
@ -299,7 +317,7 @@ enum InstrumentedMethod { @@ -299,7 +317,7 @@ enum InstrumentedMethod {
invocation -> {
Class<?> thisClass = invocation.getInstance();
String resourceName = invocation.getArgument(0);
return RuntimeHintsPredicates.resource().forResource(TypeReference.of(thisClass), resourceName);
return resource().forResource(TypeReference.of(thisClass), resourceName);
}),
/**
@ -315,7 +333,7 @@ enum InstrumentedMethod { @@ -315,7 +333,7 @@ enum InstrumentedMethod {
CLASSLOADER_GETRESOURCE(ClassLoader.class, "getResource", HintType.RESOURCE_PATTERN,
invocation -> {
String resourceName = invocation.getArgument(0);
return RuntimeHintsPredicates.resource().forResource(resourceName);
return resource().forResource(resourceName);
}),
/**

136
spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.aot.agent;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Comparator;
@ -185,6 +186,18 @@ class InstrumentedMethodTests { @@ -185,6 +186,18 @@ class InstrumentedMethodTests {
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetConstructorsShouldNotMatchTypeReflectionHint() {
hints.reflection().registerType(String.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetConstructorsShouldMatchConstructorReflectionHint() throws Exception {
hints.reflection().registerConstructor(String.class.getConstructor());
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetDeclaredConstructorShouldMatchIntrospectDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS);
@ -229,6 +242,17 @@ class InstrumentedMethodTests { @@ -229,6 +242,17 @@ class InstrumentedMethodTests {
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
}
@Test
void classGetDeclaredConstructorsShouldNotMatchTypeReflectionHint() {
hints.reflection().registerType(String.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
}
@Test
void classGetDeclaredConstructorsShouldMatchConstructorReflectionHint() throws Exception {
hints.reflection().registerConstructor(String.class.getConstructor());
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
}
@Test
void constructorNewInstanceShouldMatchInvokeHintOnConstructor() throws NoSuchMethodException {
@ -257,6 +281,8 @@ class InstrumentedMethodTests { @@ -257,6 +281,8 @@ class InstrumentedMethodTests {
RecordedInvocation stringGetScaleMethod;
RecordedInvocation stringGetMethods;
@BeforeEach
void setup() throws Exception {
this.stringGetToStringMethod = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHOD)
@ -265,6 +291,8 @@ class InstrumentedMethodTests { @@ -265,6 +291,8 @@ class InstrumentedMethodTests {
this.stringGetScaleMethod = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDMETHOD)
.onInstance(String.class).withArguments("scale", new Class[] { int.class, float.class })
.returnValue(String.class.getDeclaredMethod("scale", int.class, float.class)).build();
this.stringGetMethods = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS)
.onInstance(String.class).returnValue(String.class.getMethods()).build();
}
@Test
@ -309,60 +337,79 @@ class InstrumentedMethodTests { @@ -309,60 +337,79 @@ class InstrumentedMethodTests {
}
@Test
void classGetDeclaredMethodsShouldMatchIntrospectPublicMethodsHint() {
void classGetDeclaredMethodsShouldNotMatchIntrospectPublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodsShouldNotMatchTypeReflectionHint() {
hints.reflection().registerType(String.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodsShouldMatchMethodReflectionHint() throws Exception {
hints.reflection().registerMethod(String.class.getMethod("toString"));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod);
}
@Test
void classGetMethodsShouldMatchInstrospectDeclaredMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldMatchInstrospectPublicMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldMatchInvokeDeclaredMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, MemberCategory.INVOKE_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldMatchInvokePublicMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldNotMatchForWrongType() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(Integer.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, invocation);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldNotMatchTypeReflectionHint() {
hints.reflection().registerType(String.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodShouldMatchInstrospectPublicMethodsHint() throws NoSuchMethodException {
void classGetMethodsShouldMatchMethodReflectionHint() throws Exception {
hints.reflection().registerMethod(String.class.getMethod("toString"));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodShouldMatchInstrospectPublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchInvokePublicMethodsHint() throws NoSuchMethodException {
void classGetMethodShouldMatchInvokePublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchInstrospectDeclaredMethodsHint() throws NoSuchMethodException {
void classGetMethodShouldMatchIntrospectDeclaredMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@ -405,6 +452,28 @@ class InstrumentedMethodTests { @@ -405,6 +452,28 @@ class InstrumentedMethodTests {
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void methodGetAnnotationsShouldMatchIntrospectHintOnMethod() throws NoSuchMethodException {
Method toString = String.class.getMethod("toString");
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_GETANNOTATIONS)
.onInstance(toString).withArguments("testString")
.returnValue(toString.getAnnotations()).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMethod("toString",
Collections.emptyList(), ExecutableMode.INTROSPECT));
assertThatInvocationMatches(InstrumentedMethod.METHOD_GETANNOTATIONS, invocation);
}
@Test
void methodGetParameterTypesShouldMatchIntrospectHintOnMethod() throws NoSuchMethodException {
Method toString = String.class.getMethod("toString");
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_GETPARAMETERTYPES)
.onInstance(toString).withArguments("testString")
.returnValue(toString.getParameterTypes()).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMethod("toString",
Collections.emptyList(), ExecutableMode.INTROSPECT));
assertThatInvocationMatches(InstrumentedMethod.METHOD_GETPARAMETERTYPES, invocation);
}
@Test
void methodInvokeShouldMatchInvokeHintOnMethod() throws NoSuchMethodException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_INVOKE)
@ -432,6 +501,10 @@ class InstrumentedMethodTests { @@ -432,6 +501,10 @@ class InstrumentedMethodTests {
RecordedInvocation stringGetDeclaredField;
RecordedInvocation stringGetDeclaredFields;
RecordedInvocation stringGetFields;
@BeforeEach
void setup() throws Exception {
this.getPublicField = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD)
@ -439,6 +512,10 @@ class InstrumentedMethodTests { @@ -439,6 +512,10 @@ class InstrumentedMethodTests {
.returnValue(PublicField.class.getField("field")).build();
this.stringGetDeclaredField = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELD)
.onInstance(String.class).withArgument("value").returnValue(String.class.getDeclaredField("value")).build();
this.stringGetDeclaredFields = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELDS)
.onInstance(String.class).returnValue(String.class.getDeclaredFields()).build();
this.stringGetFields = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELDS)
.onInstance(String.class).returnValue(String.class.getFields()).build();
}
@Test
@ -461,16 +538,26 @@ class InstrumentedMethodTests { @@ -461,16 +538,26 @@ class InstrumentedMethodTests {
@Test
void classGetDeclaredFieldsShouldMatchDeclaredFieldsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELDS).onInstance(String.class).build();
hints.reflection().registerType(String.class, MemberCategory.DECLARED_FIELDS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, invocation);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields);
}
@Test
void classGetDeclaredFieldsShouldNotMatchPublicFieldsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELDS).onInstance(String.class).build();
hints.reflection().registerType(String.class, MemberCategory.PUBLIC_FIELDS);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, invocation);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields);
}
@Test
void classGetDeclaredFieldsShouldNotMatchTypeHint() {
hints.reflection().registerType(String.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields);
}
@Test
void classGetDeclaredFieldsShouldMatchFieldHint() throws Exception {
hints.reflection().registerField(String.class.getDeclaredField("value"));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields);
}
@Test
@ -531,6 +618,19 @@ class InstrumentedMethodTests { @@ -531,6 +618,19 @@ class InstrumentedMethodTests {
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, invocation);
}
@Test
void classGetFieldsShouldNotMatchTypeHint() {
hints.reflection().registerType(String.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELDS, this.stringGetFields);
}
@Test
void classGetFieldsShouldMatchFieldHint() throws Exception {
hints.reflection().registerField(String.class.getDeclaredField("value"));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, this.stringGetFields);
}
}

23
spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java

@ -212,6 +212,29 @@ public class ReflectionHintsPredicates { @@ -212,6 +212,29 @@ public class ReflectionHintsPredicates {
.anyMatch(memberCategory -> getTypeHint(hints).getMemberCategories().contains(memberCategory)));
}
/**
* Refine the current predicate to only match if a hint is present for any of its constructors.
* @return the refined {@link RuntimeHints} predicate
*/
public Predicate<RuntimeHints> withAnyConstructor() {
return this.and(hints -> getTypeHint(hints).constructors().findAny().isPresent());
}
/**
* Refine the current predicate to only match if a hint is present for any of its methods.
* @return the refined {@link RuntimeHints} predicate
*/
public Predicate<RuntimeHints> withAnyMethod() {
return this.and(hints -> getTypeHint(hints).methods().findAny().isPresent());
}
/**
* Refine the current predicate to only match if a hint is present for any of its fields.
* @return the refined {@link RuntimeHints} predicate
*/
public Predicate<RuntimeHints> withAnyField() {
return this.and(hints -> getTypeHint(hints).fields().findAny().isPresent());
}
}
public abstract static class ExecutableHintPredicate<T extends Executable> implements Predicate<RuntimeHints> {

50
spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java

@ -18,6 +18,8 @@ package org.springframework.aot.hint.predicate; @@ -18,6 +18,8 @@ package org.springframework.aot.hint.predicate;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.function.Predicate;
@ -45,6 +47,14 @@ class ReflectionHintsPredicatesTests { @@ -45,6 +47,14 @@ class ReflectionHintsPredicatesTests {
private static Constructor<?> publicConstructor;
private static Method privateMethod;
private static Method publicMethod;
private static Field privateField;
private static Field publicField;
private final ReflectionHintsPredicates reflection = new ReflectionHintsPredicates();
private final RuntimeHints runtimeHints = new RuntimeHints();
@ -54,6 +64,10 @@ class ReflectionHintsPredicatesTests { @@ -54,6 +64,10 @@ class ReflectionHintsPredicatesTests {
static void setupAll() throws Exception {
privateConstructor = SampleClass.class.getDeclaredConstructor(String.class);
publicConstructor = SampleClass.class.getConstructor();
privateMethod = SampleClass.class.getDeclaredMethod("privateMethod");
publicMethod = SampleClass.class.getMethod("publicMethod");
privateField = SampleClass.class.getDeclaredField("privateField");
publicField = SampleClass.class.getField("publicField");
}
@Nested
@ -289,6 +303,18 @@ class ReflectionHintsPredicatesTests { @@ -289,6 +303,18 @@ class ReflectionHintsPredicatesTests {
assertPredicateMatches(reflection.onConstructor(privateConstructor).invoke());
}
@Test
void reflectionOnAnyConstructorDoesNotMatchTypeReflection() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyConstructor());
}
@Test
void reflectionOnAnyConstructorMatchesConstructorReflection() {
runtimeHints.reflection().registerConstructor(publicConstructor);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyConstructor());
}
}
@Nested
@ -432,6 +458,18 @@ class ReflectionHintsPredicatesTests { @@ -432,6 +458,18 @@ class ReflectionHintsPredicatesTests {
assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
}
@Test
void reflectionOnAnyMethodDoesNotMatchTypeReflection() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyMethod());
}
@Test
void reflectionOnAnyMethodMatchesMethodReflection() {
runtimeHints.reflection().registerMethod(publicMethod);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyMethod());
}
}
@Nested
@ -505,6 +543,18 @@ class ReflectionHintsPredicatesTests { @@ -505,6 +543,18 @@ class ReflectionHintsPredicatesTests {
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
}
@Test
void reflectionOnAnyFieldDoesNotMatchTypeReflection() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyField());
}
@Test
void reflectionOnAnyFieldMatchesFieldReflection() {
runtimeHints.reflection().registerField(publicField);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyField());
}
}
private void assertPredicateMatches(Predicate<RuntimeHints> predicate) {

Loading…
Cancel
Save