mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Add support for declaring reflection metadata for lambdas
Closes gh-36339
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.hint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A hint that describes the need of reflection for a Lambda.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public final class LambdaHint implements ConditionalHint {
|
||||
|
||||
private final TypeReference declaringClass;
|
||||
|
||||
private final @Nullable TypeReference reachableType;
|
||||
|
||||
private final @Nullable DeclaringMethod declaringMethod;
|
||||
|
||||
private final List<TypeReference> interfaces;
|
||||
|
||||
private LambdaHint(Builder builder) {
|
||||
this.declaringClass = builder.declaringClass;
|
||||
this.reachableType = builder.reachableType;
|
||||
this.declaringMethod = builder.declaringMethod;
|
||||
this.interfaces = List.copyOf(builder.interfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the class declaring the lambda.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @return a builder for the hint
|
||||
*/
|
||||
public static Builder of(TypeReference declaringClass) {
|
||||
return new Builder(declaringClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the class declaring the lambda.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @return a builder for the hint
|
||||
*/
|
||||
public static Builder of(Class<?> declaringClass) {
|
||||
return new Builder(TypeReference.of(declaringClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type declaring the lambda.
|
||||
* @return the declaring class
|
||||
*/
|
||||
public TypeReference getDeclaringClass() {
|
||||
return this.declaringClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TypeReference getReachableType() {
|
||||
return this.reachableType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method in which the lambda is defined, if any.
|
||||
* @return the declaring method
|
||||
*/
|
||||
public @Nullable DeclaringMethod getDeclaringMethod() {
|
||||
return this.declaringMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interfaces that are implemented by the lambda.
|
||||
* @return the interfaces
|
||||
*/
|
||||
public List<TypeReference> getInterfaces() {
|
||||
return this.interfaces;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final TypeReference declaringClass;
|
||||
|
||||
private @Nullable TypeReference reachableType;
|
||||
|
||||
private @Nullable DeclaringMethod declaringMethod;
|
||||
|
||||
private final List<TypeReference> interfaces = new ArrayList<>();
|
||||
|
||||
Builder(TypeReference declaringClass) {
|
||||
this.declaringClass = declaringClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this hint conditional on the fact that the specified type is in a
|
||||
* reachable code path from a static analysis point of view.
|
||||
* @param reachableType the type that should be reachable for this hint to apply
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder onReachableType(TypeReference reachableType) {
|
||||
this.reachableType = reachableType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this hint conditional on the fact that the specified type is in a
|
||||
* reachable code path from a static analysis point of view.
|
||||
* @param reachableType the type that should be reachable for this hint to apply
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder onReachableType(Class<?> reachableType) {
|
||||
this.reachableType = TypeReference.of(reachableType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method that declares the lambda.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types, if any.
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withDeclaringMethod(String name, List<TypeReference> parameterTypes) {
|
||||
this.declaringMethod = new DeclaringMethod(name, parameterTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method that declares the lambda.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types, if any.
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withDeclaringMethod(String name, Class<?>... parameterTypes) {
|
||||
return withDeclaringMethod(name, Arrays.stream(parameterTypes).map(TypeReference::of).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the lambda should implement.
|
||||
* @param interfaces the interfaces the lambda should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withInterfaces(TypeReference... interfaces) {
|
||||
this.interfaces.addAll(Arrays.asList(interfaces));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the lambda should implement.
|
||||
* @param interfaces the interfaces the lambda should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withInterfaces(Class<?>... interfaces) {
|
||||
this.interfaces.addAll(Arrays.stream(interfaces).map(TypeReference::of).toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaHint build() {
|
||||
return new LambdaHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a method.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types
|
||||
*/
|
||||
public record DeclaringMethod(String name, List<TypeReference> parameterTypes) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,10 @@ import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -45,6 +47,7 @@ public class ReflectionHints {
|
||||
|
||||
private final Map<TypeReference, TypeHint.Builder> types = new HashMap<>();
|
||||
|
||||
private final Set<LambdaHint> lambdaHints = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Return the types that require reflection.
|
||||
@@ -54,6 +57,16 @@ public class ReflectionHints {
|
||||
return this.types.values().stream().map(TypeHint.Builder::build);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the lambda hints.
|
||||
* @return a stream of {@link LambdaHint}
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public Stream<LambdaHint> lambdaHints() {
|
||||
return this.lambdaHints.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reflection hints for the type defined by the specified
|
||||
* {@link TypeReference}.
|
||||
@@ -242,6 +255,31 @@ public class ReflectionHints {
|
||||
typeHint -> typeHint.withJavaSerialization(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link LambdaHint}.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @param lambdaHint the consumer of the hint builder
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public ReflectionHints registerLambda(TypeReference declaringClass, Consumer<LambdaHint.Builder> lambdaHint) {
|
||||
LambdaHint.Builder builder = LambdaHint.of(declaringClass);
|
||||
lambdaHint.accept(builder);
|
||||
this.lambdaHints.add(builder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link LambdaHint}.
|
||||
* @param declaringClass the type declaring the lambda
|
||||
* @param lambdaHint the consumer of the hint builder
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @since 7.0.6
|
||||
*/
|
||||
public ReflectionHints registerLambda(Class<?> declaringClass, Consumer<LambdaHint.Builder> lambdaHint) {
|
||||
return this.registerLambda(TypeReference.of(declaringClass), lambdaHint);
|
||||
}
|
||||
|
||||
private List<TypeReference> mapParameters(Executable executable) {
|
||||
return TypeReference.listOf(executable.getParameterTypes());
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ package org.springframework.aot.hint;
|
||||
/**
|
||||
* Gather hints that can be used to optimize the application runtime.
|
||||
*
|
||||
* <p>Use of reflection can be recorded for individual members of a type, as
|
||||
* well as broader {@linkplain MemberCategory member categories}. Access to
|
||||
* resources can be specified using patterns or the base name of a resource
|
||||
* bundle.
|
||||
* <p>Use of reflection can be recorded for individual members of a type,
|
||||
* lambdas,as well as broader {@linkplain MemberCategory member categories}.
|
||||
* Access to resources can be specified using patterns or the base name of a
|
||||
* resource bundle.
|
||||
*
|
||||
* <p>Hints that require the need for Java serialization of proxies can be
|
||||
* recorded as well.
|
||||
|
||||
+27
-1
@@ -35,6 +35,7 @@ import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
import org.springframework.aot.hint.JavaSerializationHint;
|
||||
import org.springframework.aot.hint.JdkProxyHint;
|
||||
import org.springframework.aot.hint.LambdaHint;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
@@ -63,6 +64,12 @@ class ReflectionHintsAttributes {
|
||||
return leftSignature.compareTo(rightSignature);
|
||||
};
|
||||
|
||||
private static final Comparator<LambdaHint> LAMBDA_HINT_COMPARATOR =
|
||||
Comparator.comparing(LambdaHint::getDeclaringClass)
|
||||
.thenComparing(LambdaHint::getDeclaringMethod, Comparator.nullsFirst(
|
||||
Comparator.comparing(LambdaHint.DeclaringMethod::name)));
|
||||
|
||||
|
||||
public List<Map<String, Object>> reflection(RuntimeHints hints) {
|
||||
List<Map<String, Object>> reflectionHints = new ArrayList<>(reflectionHints(hints));
|
||||
reflectionHints.addAll(hints.proxies().jdkProxyHints()
|
||||
@@ -83,7 +90,9 @@ class ReflectionHintsAttributes {
|
||||
return currentAttributes;
|
||||
});
|
||||
});
|
||||
return allTypeHints.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).map(Map.Entry::getValue).toList();
|
||||
return Stream.concat(
|
||||
allTypeHints.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue),
|
||||
hints.reflection().lambdaHints().sorted(LAMBDA_HINT_COMPARATOR).map(this::toAttributes)).toList();
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> jni(RuntimeHints hints) {
|
||||
@@ -106,6 +115,23 @@ class ReflectionHintsAttributes {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private Map<String, Object> toAttributes(LambdaHint hint) {
|
||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
||||
Map<String, Object> lambdaAttributes = new LinkedHashMap<>();
|
||||
lambdaAttributes.put("declaringClass", hint.getDeclaringClass());
|
||||
LambdaHint.DeclaringMethod declaringMethod = hint.getDeclaringMethod();
|
||||
if (declaringMethod != null) {
|
||||
Map<String, Object> methodAttributes = new LinkedHashMap<>();
|
||||
methodAttributes.put("name", declaringMethod.name());
|
||||
methodAttributes.put("parameterTypes", declaringMethod.parameterTypes());
|
||||
lambdaAttributes.put("declaringMethod", methodAttributes);
|
||||
}
|
||||
lambdaAttributes.put("interfaces", hint.getInterfaces());
|
||||
|
||||
attributes.put("lambda", lambdaAttributes);
|
||||
return Map.of("type", attributes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private Map<String, Object> toAttributes(JavaSerializationHint serializationHint) {
|
||||
LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
|
||||
|
||||
@@ -20,7 +20,9 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -223,6 +225,30 @@ class ReflectionHintsTests {
|
||||
typeHint.getMemberCategories().contains(MemberCategory.INTROSPECT_PUBLIC_METHODS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerLambda() {
|
||||
this.reflectionHints.registerLambda(String.class, lambdaHint -> lambdaHint.withInterfaces(Supplier.class));
|
||||
assertThat(this.reflectionHints.lambdaHints()).singleElement().satisfies(lambdaHint -> {
|
||||
assertThat(lambdaHint.getDeclaringClass()).isEqualTo(TypeReference.of(String.class));
|
||||
assertThat(lambdaHint.getReachableType()).isNull();
|
||||
assertThat(lambdaHint.getDeclaringMethod()).isNull();
|
||||
assertThat(lambdaHint.getInterfaces()).containsExactly(TypeReference.of(Supplier.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerLambdaWithDeclaringMethod() {
|
||||
this.reflectionHints.registerLambda(TypeReference.of("com.example.Demo"), lambdaHint -> lambdaHint.withInterfaces(Supplier.class)
|
||||
.withDeclaringMethod("hello", String.class, Integer.class));
|
||||
assertThat(this.reflectionHints.lambdaHints()).singleElement().satisfies(lambdaHint -> {
|
||||
assertThat(lambdaHint.getDeclaringClass()).isEqualTo(TypeReference.of("com.example.Demo"));
|
||||
assertThat(lambdaHint.getReachableType()).isNull();
|
||||
assertThat(lambdaHint.getDeclaringMethod()).isEqualTo(new LambdaHint.DeclaringMethod("hello",
|
||||
List.of(TypeReference.of(String.class), TypeReference.of(Integer.class))));
|
||||
assertThat(lambdaHint.getInterfaces()).containsExactly(TypeReference.of(Supplier.class));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertTestTypeMethodHints(Consumer<ExecutableHint> methodHint) {
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
|
||||
+69
@@ -21,6 +21,7 @@ import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -293,6 +294,74 @@ class RuntimeHintsWriterTests {
|
||||
assertThat(writeJson(hints)).isEqualTo(writeJson(hints2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneLambda() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerLambda(Integer.class, builder -> builder.withDeclaringMethod("getCell", Integer.class, Integer.class).withInterfaces(Supplier.class));
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": {
|
||||
"lambda": {
|
||||
"declaringClass": "java.lang.Integer",
|
||||
"declaringMethod": {
|
||||
"name": "getCell",
|
||||
"parameterTypes": [ "java.lang.Integer", "java.lang.Integer" ]
|
||||
},
|
||||
"interfaces": [ "java.util.function.Supplier" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortLambdasByDeclaringClassAndMethods() throws JSONException {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerLambda(String.class, builder -> builder.withInterfaces(Callable.class));
|
||||
hints.reflection().registerLambda(Integer.class,
|
||||
builder -> builder.withDeclaringMethod("def").withInterfaces(Supplier.class));
|
||||
hints.reflection().registerLambda(Integer.class,
|
||||
builder -> builder.withDeclaringMethod("abc").withInterfaces(Function.class));
|
||||
|
||||
assertEquals("""
|
||||
{
|
||||
"reflection": [
|
||||
{
|
||||
"type": {
|
||||
"lambda": {
|
||||
"declaringClass": "java.lang.Integer",
|
||||
"declaringMethod": { "name": "abc" },
|
||||
"interfaces": [ "java.util.function.Function" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"lambda": {
|
||||
"declaringClass": "java.lang.Integer",
|
||||
"declaringMethod": { "name": "def" },
|
||||
"interfaces": [ "java.util.function.Supplier" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"lambda": {
|
||||
"declaringClass": "java.lang.String",
|
||||
"interfaces": [ "java.util.concurrent.Callable" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""", hints);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user