Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0-TPP-NATIVE-SNAPSHOT</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
Expand Down Expand Up @@ -116,7 +116,7 @@
<optional>true</optional>
</dependency>

<!-- Project Reactor -->
<!-- Project Reactor -->

<dependency>
<groupId>io.projectreactor</groupId>
Expand All @@ -130,7 +130,7 @@
<scope>test</scope>
</dependency>

<!-- RxJava -->
<!-- RxJava -->

<dependency>
<groupId>io.reactivex.rxjava3</groupId>
Expand Down Expand Up @@ -235,6 +235,7 @@
</dependency>

<!-- Groovy -->

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
Expand All @@ -243,6 +244,7 @@
</dependency>

<!-- Kotlin extension -->

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
Expand Down Expand Up @@ -288,13 +290,23 @@
</dependency>

<!-- Scala -->

<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala}</version>
<optional>true</optional>
</dependency>

<!-- Graal Native SDK -->

<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>25.0.2</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
Expand Down
6 changes: 4 additions & 2 deletions src/main/antora/modules/ROOT/pages/property-paths.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,7 @@ Limiting property paths to a specific domain type that is used within the curren
+
Whenever accepting or providing multiple property paths, consider using `TypedPropertyPath<T, ?>` to allow for properties within the context of the owning type `T` to limit property paths to a common owning type.

NOTE: When using Graal Native Image compilation, you need to provide reachability metadata for serializable `TypedPropertyPath` lambdas.
When using lambda expressions instead of method references you will have to include the Java source code of the class containing the lambda expression in the native image configuration.
NOTE: Graal Native Image compilation requires reachability metadata for serializable `TypedPropertyPath` lambdas.
Spring Data ships a built-in https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/hosted/Feature.html[feature] through `org.springframework.data.core.TypedPropertyPathFeature`.
The feature is auto-activated and registers required serialization- and reflection metadata for lambda parsing and referenced reflective members (fields and methods).
Note also, when using lambda expressions instead of method references you will have to include the Java source code of the class containing the lambda expression in the native image configuration yourself.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright 2026-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.data.core;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;

import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.graalvm.nativeimage.hosted.RuntimeSerialization;

import org.springframework.beans.BeanUtils;

/**
* GraalVM {@link Feature} that registers serializable {@link TypedPropertyPath} and {@link PropertyReference} lambdas.
* This allows to use typed property paths and property references in native images without the need to pre-compute them
* at build time.
* <p>
* This feature also registers Java Bean Properties (methods and fields) referenced by the property path or reference
* for reflection, so that they are available at runtime and therefore the underlying domain model does not require
* additional reachability configuration.
*
* @author Mark Paluch
* @since 4.1
*/
class TypedPropertyPathFeature implements Feature {

/**
* Token indicating a class is or is not a lambda.
*/
private static final String LAMBDA_CLASS_MARKER = "$$Lambda";

/**
* The offset from {@link #LAMBDA_CLASS_MARKER} where the end marker is found.
*/
private static final int LAMBDA_CLASS_END_MARKER = LAMBDA_CLASS_MARKER.length();

private final SerializableLambdaReader reader = new SerializableLambdaReader();

@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {

BiConsumer<DuringAnalysisAccess, Class<?>> serializableLambdaHandler = (ignore, cls) -> {

if (isLambdaClass(cls)) {

try {
registerLambdaSerialization(cls);
registerDomainModel(cls);
} catch (Exception e) {
e.printStackTrace();
}
}
};

access.registerSubtypeReachabilityHandler(serializableLambdaHandler, TypedPropertyPath.class);
access.registerSubtypeReachabilityHandler(serializableLambdaHandler, PropertyReference.class);
}

private void registerLambdaSerialization(Class<?> lambdaClass) {

RuntimeSerialization.register(lambdaClass);
RuntimeReflection.registerMethodLookup(lambdaClass, "writeReplace");
}

private void registerDomainModel(Class<?> cls) throws ReflectiveOperationException {
Constructor<?> declaredConstructor = cls.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object lambdaInstance = declaredConstructor.newInstance();

MemberDescriptor memberDescriptor = reader.read(lambdaInstance);
registerDomainModel(memberDescriptor);
}

private static void registerDomainModel(MemberDescriptor descriptor) {

PropertyDescriptor property = null;

if (descriptor.getMember() instanceof Field f) {
RuntimeReflection.register(f);
property = BeanUtils.getPropertyDescriptor(descriptor.getOwner(), f.getName());
}

if (descriptor.getMember() instanceof Method m) {
RuntimeReflection.register(m);
property = BeanUtils.findPropertyForMethod(m);
}

if (property != null) {
Method readMethod = property.getReadMethod();
Method writeMethod = property.getWriteMethod();

if (readMethod != null) {
RuntimeReflection.register(readMethod);
}
if (writeMethod != null) {
RuntimeReflection.register(writeMethod);
}

RuntimeReflection.registerFieldLookup(descriptor.getOwner(), property.getName());
}
}

/**
* Return true if the specified Class represents a raw lambda.
*
* @param cls class to inspect.
* @return true if the class represents a raw lambda.
*/
public static boolean isLambdaClass(Class<?> cls) {

String name = cls.getName();
int marker = name.indexOf(LAMBDA_CLASS_MARKER);
if (marker == -1) {
return false;
}

int noffset = marker + LAMBDA_CLASS_END_MARKER;
if (noffset > name.length()) {
return false;
}

char c = name.charAt(noffset);

// '$' character will be seen in releases between Java {8,20}
// '/' is used in Java 21
// See bug 35177243
return c == '$' || c == '/';
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Args=--features=org.springframework.data.core.TypedPropertyPathFeature
Loading