diff --git a/integration-tests/src/test/java/org/springframework/aot/RuntimeHintsAgentTests.java b/integration-tests/src/test/java/org/springframework/aot/RuntimeHintsAgentTests.java index 2309cbc01d..e29518bae3 100644 --- a/integration-tests/src/test/java/org/springframework/aot/RuntimeHintsAgentTests.java +++ b/integration-tests/src/test/java/org/springframework/aot/RuntimeHintsAgentTests.java @@ -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()); diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedBridgeMethods.java b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedBridgeMethods.java index df2d880c6e..30c9aef2c5 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedBridgeMethods.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedBridgeMethods.java @@ -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 { * 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; diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java index c242d8575a..f7fc7e8a51 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java @@ -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 { 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 { 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 { if (constructor == null) { return runtimeHints -> false; } - return RuntimeHintsPredicates.reflection().onConstructor(constructor).introspect(); + return reflection().onConstructor(constructor).introspect(); } ), @@ -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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { CLASSLOADER_GETRESOURCE(ClassLoader.class, "getResource", HintType.RESOURCE_PATTERN, invocation -> { String resourceName = invocation.getArgument(0); - return RuntimeHintsPredicates.resource().forResource(resourceName); + return resource().forResource(resourceName); }), /** diff --git a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java index e2839c46ad..0a3863a08c 100644 --- a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java +++ b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java @@ -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 { 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 { 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 { RecordedInvocation stringGetScaleMethod; + RecordedInvocation stringGetMethods; + @BeforeEach void setup() throws Exception { this.stringGetToStringMethod = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHOD) @@ -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 { } @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 classGetMethodShouldMatchInstrospectPublicMethodsHint() throws NoSuchMethodException { + void classGetMethodsShouldNotMatchTypeReflectionHint() { + hints.reflection().registerType(String.class); + assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); + } + + @Test + 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 { 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 { RecordedInvocation stringGetDeclaredField; + RecordedInvocation stringGetDeclaredFields; + + RecordedInvocation stringGetFields; + @BeforeEach void setup() throws Exception { this.getPublicField = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD) @@ -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 { @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 { 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); + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index 9d35519a0f..c745cff465 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -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 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 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 withAnyField() { + return this.and(hints -> getTypeHint(hints).fields().findAny().isPresent()); + } } public abstract static class ExecutableHintPredicate implements Predicate { diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java index 957c58aa1a..aa15264535 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java @@ -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 { 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 { 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 { 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 { 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 { 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 predicate) {