Skip to content

Commit 4396801

Browse files
committed
Add reflection hints for Kotlin reflection on functions
Kotlin reflection API invocation on a specific function may require iterating on all Java methods to find the right Kotlin function. As a consequence, this commit adds introspection hints on the class declared methods for all Kotlin beans since the impact on the footprint is low. Closes gh-29663
1 parent a516ed8 commit 4396801

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2002-2023 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.context.aot;
18+
19+
import org.springframework.aot.generate.GenerationContext;
20+
import org.springframework.aot.hint.MemberCategory;
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
23+
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
24+
import org.springframework.beans.factory.aot.BeanRegistrationCode;
25+
import org.springframework.beans.factory.support.RegisteredBean;
26+
import org.springframework.core.KotlinDetector;
27+
import org.springframework.lang.Nullable;
28+
29+
/**
30+
* AOT {@code BeanRegistrationAotProcessor} that adds additional hints
31+
* required by Kotlin reflection.
32+
*
33+
* @author Sebastien Deleuze
34+
* @since 6.0.4
35+
*/
36+
class KotlinReflectionBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
37+
38+
@Nullable
39+
@Override
40+
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
41+
Class<?> beanClass = registeredBean.getBeanClass();
42+
if (KotlinDetector.isKotlinType(beanClass)) {
43+
return new KotlinReflectionBeanRegistrationAotContribution(beanClass);
44+
}
45+
return null;
46+
}
47+
48+
private static class KotlinReflectionBeanRegistrationAotContribution implements BeanRegistrationAotContribution {
49+
50+
private final Class<?> beanClass;
51+
52+
public KotlinReflectionBeanRegistrationAotContribution(Class<?> beanClass) {
53+
this.beanClass = beanClass;
54+
}
55+
56+
@Override
57+
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
58+
registerHints(this.beanClass, generationContext.getRuntimeHints());
59+
}
60+
61+
private void registerHints(Class<?> type, RuntimeHints runtimeHints) {
62+
if (KotlinDetector.isKotlinType(type)) {
63+
runtimeHints.reflection().registerType(type, MemberCategory.INTROSPECT_DECLARED_METHODS);
64+
}
65+
Class<?> superClass = type.getSuperclass();
66+
if (superClass != null) {
67+
registerHints(superClass, runtimeHints);
68+
}
69+
}
70+
}
71+
72+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
22
org.springframework.context.aot.ReflectiveProcessorBeanFactoryInitializationAotProcessor
33

4+
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
5+
org.springframework.context.aot.KotlinReflectionBeanRegistrationAotProcessor
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2002-2023 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.context.aot;
18+
19+
class SampleJavaBean {
20+
21+
void sample() {
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.context.aot
18+
19+
import org.assertj.core.api.Assertions
20+
import org.junit.jupiter.api.Test
21+
import org.mockito.Mockito
22+
import org.springframework.aot.generate.GenerationContext
23+
import org.springframework.aot.hint.MemberCategory
24+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates
25+
import org.springframework.aot.test.generate.TestGenerationContext
26+
import org.springframework.beans.factory.aot.*
27+
import org.springframework.beans.factory.support.DefaultListableBeanFactory
28+
import org.springframework.beans.factory.support.RegisteredBean
29+
import org.springframework.beans.factory.support.RootBeanDefinition
30+
31+
/**
32+
* Tests for [KotlinReflectionBeanRegistrationAotProcessor].
33+
*
34+
* @author Sebastien Deleuze
35+
*/
36+
class KotlinReflectionBeanRegistrationAotProcessorTests {
37+
38+
private val processor = KotlinReflectionBeanRegistrationAotProcessor()
39+
40+
private val generationContext = TestGenerationContext()
41+
42+
@Test
43+
fun processorIsRegistered() {
44+
Assertions.assertThat(
45+
AotServices.factories(javaClass.classLoader).load(BeanRegistrationAotProcessor::class.java))
46+
.anyMatch(KotlinReflectionBeanRegistrationAotProcessor::class.java::isInstance)
47+
}
48+
49+
@Test
50+
fun shouldProcessKotlinBean() {
51+
process(SampleKotlinBean::class.java)
52+
Assertions.assertThat(
53+
RuntimeHintsPredicates.reflection()
54+
.onType(SampleKotlinBean::class.java)
55+
.withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS)
56+
).accepts(generationContext.runtimeHints)
57+
Assertions.assertThat(
58+
RuntimeHintsPredicates.reflection()
59+
.onType(BaseKotlinBean::class.java)
60+
.withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS)
61+
).accepts(generationContext.runtimeHints)
62+
}
63+
64+
@Test
65+
fun shouldNotProcessJavaBean() {
66+
process(SampleJavaBean::class.java)
67+
Assertions.assertThat(generationContext.runtimeHints.reflection().typeHints()).isEmpty()
68+
}
69+
70+
private fun process(beanClass: Class<*>) {
71+
createContribution(beanClass)?.applyTo(generationContext, Mockito.mock(BeanRegistrationCode::class.java))
72+
}
73+
74+
private fun createContribution(beanClass: Class<*>): BeanRegistrationAotContribution? {
75+
val beanFactory = DefaultListableBeanFactory()
76+
beanFactory.registerBeanDefinition(beanClass.name, RootBeanDefinition(beanClass))
77+
return processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.name))
78+
}
79+
80+
81+
class SampleKotlinBean : BaseKotlinBean() {
82+
fun sample() {
83+
}
84+
}
85+
86+
open class BaseKotlinBean {
87+
fun base() {
88+
}
89+
}
90+
91+
}

0 commit comments

Comments
 (0)