Skip to content

Commit 0262380

Browse files
Add native image runtime hints.
We provide an initial set of runtime hints required to spin up data repositories on GraalVM native image. Additionally we skip synthetic types during type inspection. Original Pull Request: #2624
1 parent d8118de commit 0262380

14 files changed

+220
-62
lines changed

src/main/java/org/springframework/data/annotation/Persistent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.lang.annotation.Target;
2222

23+
import org.springframework.aot.hint.annotation.Reflective;
2324
import org.springframework.stereotype.Indexed;
2425

2526
/**
@@ -30,6 +31,7 @@
3031
*/
3132
@Indexed
3233
@Retention(RetentionPolicy.RUNTIME)
34+
@Reflective
3335
@Target(value = { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER })
3436
public @interface Persistent {
3537
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.aot;
17+
18+
import java.util.Arrays;
19+
import java.util.Properties;
20+
21+
import org.springframework.aop.SpringProxy;
22+
import org.springframework.aop.framework.Advised;
23+
import org.springframework.aot.hint.MemberCategory;
24+
import org.springframework.aot.hint.RuntimeHints;
25+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
26+
import org.springframework.aot.hint.TypeReference;
27+
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.core.DecoratingProxy;
29+
import org.springframework.core.io.InputStreamSource;
30+
import org.springframework.data.domain.AuditorAware;
31+
import org.springframework.data.mapping.context.MappingContext;
32+
import org.springframework.data.repository.core.RepositoryMetadata;
33+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
34+
import org.springframework.data.repository.core.support.RepositoryFragment;
35+
import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
36+
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
37+
import org.springframework.data.repository.query.QueryByExampleExecutor;
38+
import org.springframework.lang.Nullable;
39+
40+
/**
41+
* @author Christoph Strobl
42+
* @since 3.0
43+
*/
44+
public class DataRuntimeHints implements RuntimeHintsRegistrar {
45+
46+
/*
47+
NativeHint(trigger = MappingContext.class,
48+
types = {
49+
@TypeHint(types = {
50+
RepositoryFactoryBeanSupport.class,
51+
RepositoryFragmentsFactoryBean.class,
52+
RepositoryFragment.class,
53+
TransactionalRepositoryFactoryBeanSupport.class,
54+
QueryByExampleExecutor.class,
55+
MappingContext.class,
56+
RepositoryMetadata.class,
57+
RepositoryMetadata.class,
58+
}),
59+
@TypeHint(types = {ReadingConverter.class, WritingConverter.class}),
60+
@TypeHint(types = {Properties.class, BeanFactory.class, InputStreamSource[].class}),
61+
@TypeHint(types = Throwable.class, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_FIELDS}),
62+
@TypeHint(typeNames = {
63+
"org.springframework.data.projection.SpelEvaluatingMethodInterceptor$TargetWrapper",
64+
}, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_METHODS, TypeAccess.PUBLIC_METHODS })
65+
},
66+
jdkProxies = @JdkProxyHint(typeNames = {
67+
"org.springframework.data.annotation.QueryAnnotation",
68+
"org.springframework.core.annotation.SynthesizedAnnotation" }
69+
),
70+
initialization = @InitializationHint(types = AbstractMappingContext.class, initTime = InitializationTime.BUILD)
71+
)
72+
*/
73+
74+
@Override
75+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
76+
77+
System.out.println("Spring data Runtime Hints");
78+
79+
hints.reflection()
80+
.registerTypes(Arrays.asList(TypeReference.of(RepositoryFactoryBeanSupport.class),
81+
TypeReference.of(RepositoryFragmentsFactoryBean.class), TypeReference.of(RepositoryFragment.class),
82+
TypeReference.of(TransactionalRepositoryFactoryBeanSupport.class),
83+
TypeReference.of(QueryByExampleExecutor.class), TypeReference.of(MappingContext.class),
84+
TypeReference.of(RepositoryMetadata.class)), builder -> {
85+
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
86+
});
87+
hints.reflection().registerTypes(
88+
Arrays.asList(TypeReference.of(Properties.class), TypeReference.of(BeanFactory.class),
89+
TypeReference.of(InputStreamSource[].class)),
90+
builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
91+
92+
hints.reflection().registerType(Throwable.class,
93+
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS));
94+
95+
hints.reflection().registerType(
96+
TypeReference.of("org.springframework.data.projection.SpelEvaluatingMethodInterceptor$TargetWrapper"),
97+
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
98+
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS));
99+
100+
hints.proxies().registerJdkProxy(TypeReference.of("org.springframework.data.annotation.QueryAnnotation"),
101+
TypeReference.of("org.springframework.core.annotation.SynthesizedAnnotation"));
102+
103+
hints.proxies().registerJdkProxy(TypeReference.of(AuditorAware.class), TypeReference.of(SpringProxy.class), TypeReference.of(Advised.class), TypeReference.of(DecoratingProxy.class));
104+
}
105+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.aot;
17+
18+
import org.springframework.aot.hint.MemberCategory;
19+
import org.springframework.aot.hint.ReflectionHints;
20+
import org.springframework.aot.hint.annotation.SimpleReflectiveProcessor;
21+
22+
/**
23+
* @author Christoph Strobl
24+
*/
25+
public class PublicMethodReflectiveProcessor extends SimpleReflectiveProcessor {
26+
27+
@Override
28+
protected void registerTypeHint(ReflectionHints hints, Class<?> type) {
29+
System.out.println("invoking reflective hint annotation for : " + type);
30+
hints.registerType(type, builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
31+
32+
}
33+
}

src/main/java/org/springframework/data/aot/RepositoryRegistrationAotContribution.java

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ private void contributeRepositoryInfo(@NonNull AotRepositoryContext repositoryCo
286286
.registerType(repositoryInformation.getRepositoryBaseClass(), hint ->
287287
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS))
288288
.registerType(repositoryInformation.getDomainType(), hint ->
289-
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
289+
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
290290

291291
// Repository Fragments
292292
for (RepositoryFragment<?> fragment : getRepositoryInformation().getFragments()) {
@@ -308,7 +308,7 @@ private void contributeRepositoryInfo(@NonNull AotRepositoryContext repositoryCo
308308
SpringProxy.class, Advised.class, DecoratingProxy.class);
309309

310310
// Transactional Repository Proxy
311-
repositoryContext.ifTransactionManagerPresent(transactionManagerBeanNames -> {
311+
//repositoryContext.ifTransactionManagerPresent(transactionManagerBeanNames -> {
312312

313313
// TODO: Is the following double JDK Proxy registration above necessary or would a single JDK Proxy
314314
// registration suffice?
@@ -327,7 +327,7 @@ private void contributeRepositoryInfo(@NonNull AotRepositoryContext repositoryCo
327327
contribution.getRuntimeHints().proxies()
328328
.registerJdkProxy(transactionalRepositoryProxyTypeReferences.toArray(new TypeReference[0]));
329329
}
330-
});
330+
//});
331331

332332
// Reactive Repositories
333333
if (repositoryInformation.isReactiveRepository()) {
@@ -396,39 +396,25 @@ private void contributeProjection(Class<?> type, GenerationContext generationCon
396396
.registerJdkProxy(type, TargetAware.class, SpringProxy.class, DecoratingProxy.class);
397397
}
398398

399-
protected void contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
400-
401-
repositoryContext.getResolvedTypes().stream()
402-
.filter(it -> !isJavaOrPrimitiveType(it))
403-
.forEach(it -> contributeType(it, generationContext));
404-
405-
repositoryContext.getResolvedAnnotations().stream()
406-
.filter(this::isSpringDataManagedAnnotation)
407-
.map(MergedAnnotation::getType)
408-
.forEach(it -> contributeType(it, generationContext));
409-
}
410-
411-
private boolean isJavaOrPrimitiveType(Class<?> type) {
399+
static boolean isJavaOrPrimitiveType(Class<?> type) {
412400

413401
return TypeUtils.type(type).isPartOf("java")
414402
|| type.isPrimitive()
415403
|| ClassUtils.isPrimitiveArray(type);
416404
}
417405

418-
private boolean isSpringDataManagedAnnotation(MergedAnnotation<?> annotation) {
406+
static boolean isSpringDataManagedAnnotation(MergedAnnotation<?> annotation) {
419407

420408
return annotation != null
421409
&& (isInSpringDataNamespace(annotation.getType()) || annotation.getMetaTypes().stream()
422-
.anyMatch(this::isInSpringDataNamespace));
410+
.anyMatch(RepositoryRegistrationAotContribution::isInSpringDataNamespace));
423411
}
424412

425-
private boolean isInSpringDataNamespace(Class<?> type) {
413+
static boolean isInSpringDataNamespace(Class<?> type) {
426414
return type != null && type.getPackage().getName().startsWith(TypeContributor.DATA_NAMESPACE);
427415
}
428416

429-
protected void contributeType(Class<?> type, GenerationContext generationContext) {
430-
431-
logTrace("Contributing type information for [%s]", type);
417+
static void contributeType(Class<?> type, GenerationContext generationContext) {
432418
TypeContributor.contribute(type, it -> true, generationContext);
433419
}
434420

src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3535
import org.springframework.beans.factory.support.RegisteredBean;
3636
import org.springframework.beans.factory.support.RootBeanDefinition;
37+
import org.springframework.core.annotation.MergedAnnotation;
3738
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
3839
import org.springframework.data.repository.config.RepositoryMetadata;
3940
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@@ -51,7 +52,7 @@
5152
* via the {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
5253
* </p>
5354
* <p>
54-
* With {@link RepositoryRegistrationAotContribution#contribute(AotRepositoryContext, GenerationContext)}, stores
55+
* With {@link RepositoryRegistrationAotProcessor#contribute(AotRepositoryContext, GenerationContext)}, stores
5556
* can provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection
5657
* configuration will be added for types reachable from the repository declaration and query methods as well as
5758
* all used {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
@@ -83,6 +84,19 @@ public BeanRegistrationAotContribution processAheadOfTime(@NonNull RegisteredBea
8384
: null;
8485
}
8586

87+
protected void contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) {
88+
89+
repositoryContext.getResolvedTypes().stream()
90+
.filter(it -> !RepositoryRegistrationAotContribution
91+
.isJavaOrPrimitiveType(it))
92+
.forEach(it -> RepositoryRegistrationAotContribution.contributeType(it, generationContext));
93+
94+
repositoryContext.getResolvedAnnotations().stream()
95+
.filter(RepositoryRegistrationAotContribution::isSpringDataManagedAnnotation)
96+
.map(MergedAnnotation::getType)
97+
.forEach(it -> RepositoryRegistrationAotContribution.contributeType(it, generationContext));
98+
}
99+
86100
private boolean isRepositoryBean(RegisteredBean bean) {
87101
return bean != null && getConfigMap().containsKey(bean.getBeanName());
88102
}
@@ -93,7 +107,7 @@ protected RepositoryRegistrationAotContribution newRepositoryRegistrationAotCont
93107
RepositoryRegistrationAotContribution contribution =
94108
RepositoryRegistrationAotContribution.fromProcessor(this).forBean(repositoryBean);
95109

96-
return contribution.withModuleContribution(contribution::contribute);
110+
return contribution.withModuleContribution(this::contribute);
97111
}
98112

99113
@Override

src/main/java/org/springframework/data/aot/TypeCollector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ private void process(Class<?> root, Consumer<ResolvableType> consumer) {
121121

122122
private void processType(ResolvableType type, InspectionCache cache, Consumer<ResolvableType> callback) {
123123

124-
if (ResolvableType.NONE.equals(type) || cache.contains(type)) {
124+
if (ResolvableType.NONE.equals(type) || cache.contains(type) || type.toClass().isSynthetic()) {
125125
return;
126126
}
127127

src/main/java/org/springframework/data/aot/TypeContributor.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* @author Christoph Strobl
3030
* @since 3.0
3131
*/
32-
class TypeContributor {
32+
public class TypeContributor {
3333

3434
public static final String DATA_NAMESPACE = "org.springframework.data";
3535

@@ -39,7 +39,7 @@ class TypeContributor {
3939
* @param type
4040
* @param contribution
4141
*/
42-
static void contribute(Class<?> type, GenerationContext contribution) {
42+
public static void contribute(Class<?> type, GenerationContext contribution) {
4343
contribute(type, Collections.emptySet(), contribution);
4444
}
4545

@@ -51,7 +51,7 @@ static void contribute(Class<?> type, GenerationContext contribution) {
5151
* @param contribution
5252
*/
5353
@SuppressWarnings("unchecked")
54-
static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter, GenerationContext contribution) {
54+
public static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> filter, GenerationContext contribution) {
5555

5656
if (type.isPrimitive()) {
5757
return;
@@ -76,7 +76,7 @@ static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> fil
7676
}
7777

7878
contribution.getRuntimeHints().reflection().registerType(type, hint ->
79-
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS));
79+
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.DECLARED_FIELDS));
8080
}
8181

8282
/**
@@ -87,15 +87,15 @@ static void contribute(Class<?> type, Predicate<Class<? extends Annotation>> fil
8787
* @param annotationNamespaces
8888
* @param contribution
8989
*/
90-
static void contribute(Class<?> type, Set<String> annotationNamespaces, GenerationContext contribution) {
90+
public static void contribute(Class<?> type, Set<String> annotationNamespaces, GenerationContext contribution) {
9191
contribute(type, it -> isPartOfOrMetaAnnotatedWith(it, annotationNamespaces), contribution);
9292
}
9393

94-
private static boolean isPartOf(Class<?> type, Set<String> namespaces) {
94+
public static boolean isPartOf(Class<?> type, Set<String> namespaces) {
9595
return namespaces.stream().anyMatch(namespace -> type.getPackageName().startsWith(namespace));
9696
}
9797

98-
protected static boolean isPartOfOrMetaAnnotatedWith(Class<? extends Annotation> annotation, Set<String> namespaces) {
98+
public static boolean isPartOfOrMetaAnnotatedWith(Class<? extends Annotation> annotation, Set<String> namespaces) {
9999

100100
if (isPartOf(annotation, namespaces)) {
101101
return true;

src/main/java/org/springframework/data/convert/ReadingConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.lang.annotation.RetentionPolicy;
2323
import java.lang.annotation.Target;
2424

25+
import org.springframework.aot.hint.annotation.Reflective;
2526
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.data.aot.PublicMethodReflectiveProcessor;
2628

2729
/**
2830
* Annotation to clarify intended usage of a {@link Converter} as reading converter in case the conversion types leave
@@ -32,6 +34,7 @@
3234
*/
3335
@Target(TYPE)
3436
@Documented
37+
@Reflective({PublicMethodReflectiveProcessor.class})
3538
@Retention(RetentionPolicy.RUNTIME)
3639
public @interface ReadingConverter {
3740

src/main/java/org/springframework/data/convert/WritingConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.lang.annotation.RetentionPolicy;
2323
import java.lang.annotation.Target;
2424

25+
import org.springframework.aot.hint.annotation.Reflective;
2526
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.data.aot.PublicMethodReflectiveProcessor;
2628

2729
/**
2830
* Annotation to clarify intended usage of a {@link Converter} as writing converter in case the conversion types leave
@@ -32,6 +34,7 @@
3234
*/
3335
@Target(TYPE)
3436
@Documented
37+
@Reflective({PublicMethodReflectiveProcessor.class})
3538
@Retention(RetentionPolicy.RUNTIME)
3639
public @interface WritingConverter {
3740

src/main/java/org/springframework/data/mapping/callback/EntityCallback.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.mapping.callback;
1717

18+
import org.springframework.aot.hint.annotation.Reflective;
19+
import org.springframework.data.aot.PublicMethodReflectiveProcessor;
20+
1821
/**
1922
* Marker interface for entity callbacks to be implemented in specific callback subtypes. Intended for internal usage
2023
* within store specific implementations.
@@ -59,6 +62,7 @@
5962
* @see org.springframework.core.Ordered
6063
* @see org.springframework.core.annotation.Order
6164
*/
65+
@Reflective({PublicMethodReflectiveProcessor.class})
6266
public interface EntityCallback<T> {
6367

6468
}

0 commit comments

Comments
 (0)