Skip to content

Commit 267b914

Browse files
bclozelphilwebb
andcommitted
Add native hints for SpringFactoriesLoader file content
Add `SpringFactoriesLoaderRuntimeHintsRegistrar` which provides native hints for `spring.factories` content. Closes gh-27955 Co-authored-by: Phillip Webb <[email protected]>
1 parent e7e60f7 commit 267b914

File tree

4 files changed

+178
-3
lines changed

4 files changed

+178
-3
lines changed

spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ private List<String> loadFactoryNames(Class<?> factoryType) {
217217
}
218218

219219
@Nullable
220-
protected <T> T instantiateFactory(String implementationName, Class<T> type, @Nullable ArgumentResolver argumentResolver, FailureHandler failureHandler) {
220+
protected <T> T instantiateFactory(String implementationName, Class<T> type,
221+
@Nullable ArgumentResolver argumentResolver, FailureHandler failureHandler) {
222+
221223
try {
222224
Class<?> factoryImplementationClass = ClassUtils.forName(implementationName, this.classLoader);
223225
Assert.isTrue(type.isAssignableFrom(factoryImplementationClass),
@@ -333,7 +335,7 @@ public static SpringFactoriesLoader forResourceLocation(@Nullable ClassLoader cl
333335
return loader;
334336
}
335337

336-
private static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
338+
static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
337339
Map<String, List<String>> result = new LinkedHashMap<>();
338340
try {
339341
Enumeration<URL> urls = classLoader.getResources(resourceLocation);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2002-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+
17+
package org.springframework.core.io.support;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.function.Consumer;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
26+
import org.springframework.aot.hint.MemberCategory;
27+
import org.springframework.aot.hint.RuntimeHints;
28+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
29+
import org.springframework.aot.hint.TypeHint;
30+
import org.springframework.core.log.LogMessage;
31+
import org.springframework.lang.Nullable;
32+
import org.springframework.util.ClassUtils;
33+
34+
/**
35+
* {@link RuntimeHintsRegistrar} to register hints for {@code spring.factories}.
36+
*
37+
* @author Brian Clozel
38+
* @author Phillip Webb
39+
* @since 6.0
40+
* @see SpringFactoriesLoader
41+
*/
42+
class SpringFactoriesLoaderRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
43+
44+
private static List<String> RESOURCE_LOCATIONS = List
45+
.of(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION);
46+
47+
private static final Consumer<TypeHint.Builder> HINT = builder -> builder
48+
.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
49+
50+
private final Log logger = LogFactory.getLog(SpringFactoriesLoaderRuntimeHintsRegistrar.class);
51+
52+
53+
@Override
54+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
55+
for (String resourceLocation : RESOURCE_LOCATIONS) {
56+
registerHints(hints, classLoader, resourceLocation);
57+
}
58+
}
59+
60+
private void registerHints(RuntimeHints hints, ClassLoader classLoader,
61+
String resourceLocation) {
62+
hints.resources().registerPattern(resourceLocation);
63+
Map<String, List<String>> factories = SpringFactoriesLoader
64+
.loadFactoriesResource(classLoader, resourceLocation);
65+
factories.forEach((factoryClassName, implementationClassNames) ->
66+
registerHints(hints, classLoader, factoryClassName, implementationClassNames));
67+
}
68+
69+
private void registerHints(RuntimeHints hints, ClassLoader classLoader,
70+
String factoryClassName, List<String> implementationClassNames) {
71+
Class<?> factoryClass = resolveClassName(classLoader, factoryClassName);
72+
if(factoryClass == null) {
73+
logger.trace(LogMessage.format("Skipping factories for [%s]", factoryClassName));
74+
return;
75+
}
76+
logger.trace(LogMessage.format("Processing factories for [%s]", factoryClassName));
77+
hints.reflection().registerType(factoryClass, HINT);
78+
for (String implementationClassName : implementationClassNames) {
79+
Class<?> implementationType = resolveClassName(classLoader, implementationClassName);
80+
logger.trace(LogMessage.format("%s factory type [%s] and implementation [%s]",
81+
(implementationType != null) ? "Processing" : "Skipping", factoryClassName, implementationClassName));
82+
if (implementationType != null) {
83+
hints.reflection().registerType(implementationType, HINT);
84+
}
85+
}
86+
}
87+
88+
@Nullable
89+
private Class<?> resolveClassName(ClassLoader classLoader, String factoryClassName) {
90+
try {
91+
return ClassUtils.resolveClassName(factoryClassName, classLoader);
92+
}
93+
catch (Exception ex) {
94+
return null;
95+
}
96+
}
97+
98+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
org.springframework.aot.hint.RuntimeHintsRegistrar=\
2-
org.springframework.core.annotation.CoreAnnotationsRuntimeHintsRegistrar
2+
org.springframework.core.annotation.CoreAnnotationsRuntimeHintsRegistrar,\
3+
org.springframework.core.io.support.SpringFactoriesLoaderRuntimeHintsRegistrar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2002-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+
17+
package org.springframework.core.io.support;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.aot.hint.MemberCategory;
23+
import org.springframework.aot.hint.RuntimeHints;
24+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
25+
import org.springframework.aot.hint.TypeHint;
26+
import org.springframework.aot.hint.TypeReference;
27+
import org.springframework.util.ClassUtils;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
/**
32+
* Tests for {@link SpringFactoriesLoaderRuntimeHintsRegistrar}.
33+
*
34+
* @author Phillip Webb
35+
*/
36+
class SpringFactoriesLoaderRuntimeHintsRegistrarTests {
37+
38+
private RuntimeHints hints;
39+
40+
@BeforeEach
41+
void setup() {
42+
this.hints = new RuntimeHints();
43+
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories")
44+
.load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar
45+
.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
46+
}
47+
48+
@Test
49+
void resourceLocationHasHints() {
50+
assertThat(this.hints.resources().resourcePatterns())
51+
.anySatisfy(hint -> assertThat(hint.getIncludes())
52+
.contains(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION));
53+
}
54+
55+
@Test
56+
void factoryTypeHasHint() {
57+
TypeReference type = TypeReference.of(DummyFactory.class);
58+
assertThat(this.hints.reflection().getTypeHint(type))
59+
.satisfies(this::expectedHints);
60+
}
61+
62+
@Test
63+
void factoryImplementationHasHint() {
64+
TypeReference type = TypeReference.of(MyDummyFactory1.class);
65+
assertThat(this.hints.reflection().getTypeHint(type))
66+
.satisfies(this::expectedHints);
67+
}
68+
69+
private void expectedHints(TypeHint hint) {
70+
assertThat(hint.getMemberCategories())
71+
.containsExactly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
72+
}
73+
74+
}

0 commit comments

Comments
 (0)