Skip to content

Commit 4556895

Browse files
committed
Expose registrar for @Reflective
This commit exposes the logic of processing `@Reflective` on beans so that it can be reused in custom arrangements. Closes gh-28975
1 parent ef7ab76 commit 4556895

File tree

6 files changed

+242
-95
lines changed

6 files changed

+242
-95
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 java.util.Arrays;
20+
21+
import org.springframework.aot.generate.GenerationContext;
22+
import org.springframework.aot.hint.RuntimeHints;
23+
import org.springframework.aot.hint.annotation.Reflective;
24+
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
25+
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
26+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
27+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
28+
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
29+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
30+
import org.springframework.beans.factory.support.RegisteredBean;
31+
32+
/**
33+
* AOT {@code BeanFactoryInitializationAotProcessor} that detects the presence
34+
* of {@link Reflective @Reflective} on annotated elements of all registered
35+
* beans and invokes the underlying {@link ReflectiveProcessor} implementations.
36+
*
37+
* @author Stephane Nicoll
38+
* @author Sebastien Deleuze
39+
*/
40+
class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
41+
42+
private static final ReflectiveRuntimeHintsRegistrar REGISTRAR = new ReflectiveRuntimeHintsRegistrar();
43+
44+
@Override
45+
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
46+
Class<?>[] beanTypes = Arrays.stream(beanFactory.getBeanDefinitionNames())
47+
.map(beanName -> RegisteredBean.of(beanFactory, beanName).getBeanClass())
48+
.toArray(Class<?>[]::new);
49+
return new ReflectiveProcessorBeanFactoryInitializationAotContribution(beanTypes);
50+
}
51+
52+
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {
53+
54+
private final Class<?>[] types;
55+
56+
public ReflectiveProcessorBeanFactoryInitializationAotContribution(Class<?>[] types) {
57+
this.types = types;
58+
}
59+
60+
@Override
61+
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
62+
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
63+
REGISTRAR.registerRuntimeHints(runtimeHints, this.types);
64+
}
65+
66+
}
67+
68+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
2+
org.springframework.context.aot.ReflectiveProcessorBeanFactoryInitializationAotProcessor
3+
14
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor= \
2-
org.springframework.context.aot.ReflectiveProcessorBeanRegistrationAotProcessor,\
35
org.springframework.cache.annotation.CachingBeanRegistrationAotProcessor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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 java.lang.reflect.Constructor;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.aot.generate.GenerationContext;
24+
import org.springframework.aot.hint.annotation.Reflective;
25+
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
26+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
27+
import org.springframework.beans.factory.aot.AotServices;
28+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
29+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
30+
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
31+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
32+
import org.springframework.beans.factory.support.RootBeanDefinition;
33+
import org.springframework.core.testfixture.aot.generate.TestGenerationContext;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.mock;
37+
38+
/**
39+
* Tests for {@link ReflectiveProcessorBeanFactoryInitializationAotProcessor}.
40+
*
41+
* @author Stephane Nicoll
42+
* @author Sebastien Deleuze
43+
*/
44+
class ReflectiveProcessorBeanFactoryInitializationAotProcessorTests {
45+
46+
private final ReflectiveProcessorBeanFactoryInitializationAotProcessor processor = new ReflectiveProcessorBeanFactoryInitializationAotProcessor();
47+
48+
private final GenerationContext generationContext = new TestGenerationContext();
49+
50+
@Test
51+
void processorIsRegistered() {
52+
assertThat(AotServices.factories(getClass().getClassLoader()).load(BeanFactoryInitializationAotProcessor.class))
53+
.anyMatch(ReflectiveProcessorBeanFactoryInitializationAotProcessor.class::isInstance);
54+
}
55+
56+
@Test
57+
void shouldProcessAnnotationOnType() {
58+
process(SampleTypeAnnotatedBean.class);
59+
assertThat(RuntimeHintsPredicates.reflection().onType(SampleTypeAnnotatedBean.class))
60+
.accepts(this.generationContext.getRuntimeHints());
61+
}
62+
63+
@Test
64+
void shouldProcessAllBeans() throws NoSuchMethodException {
65+
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
66+
process(SampleTypeAnnotatedBean.class, SampleConstructorAnnotatedBean.class);
67+
Constructor<?> constructor = SampleConstructorAnnotatedBean.class.getDeclaredConstructor(String.class);
68+
assertThat(reflection.onType(SampleTypeAnnotatedBean.class).and(reflection.onConstructor(constructor)))
69+
.accepts(this.generationContext.getRuntimeHints());
70+
}
71+
72+
private void process(Class<?>... beanClasses) {
73+
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
74+
for (Class<?> beanClass : beanClasses) {
75+
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
76+
}
77+
BeanFactoryInitializationAotContribution contribution = this.processor.processAheadOfTime(beanFactory);
78+
assertThat(contribution).isNotNull();
79+
contribution.applyTo(this.generationContext, mock(BeanFactoryInitializationCode.class));
80+
}
81+
82+
@Reflective
83+
@SuppressWarnings("unused")
84+
static class SampleTypeAnnotatedBean {
85+
86+
private String notManaged;
87+
88+
public void notManaged() {
89+
90+
}
91+
}
92+
93+
@SuppressWarnings("unused")
94+
static class SampleConstructorAnnotatedBean {
95+
96+
@Reflective
97+
SampleConstructorAnnotatedBean(String name) {
98+
99+
}
100+
101+
SampleConstructorAnnotatedBean(Integer nameAsNumber) {
102+
103+
}
104+
105+
}
106+
107+
}

spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @author Sam Brannen
4040
* @since 6.0
4141
* @see SimpleReflectiveProcessor
42+
* @see ReflectiveRuntimeHintsRegistrar
4243
*/
4344
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR,
4445
ElementType.FIELD, ElementType.METHOD })
Lines changed: 43 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,60 +14,65 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.context.aot;
17+
package org.springframework.aot.hint.annotation;
1818

1919
import java.lang.reflect.AnnotatedElement;
2020
import java.lang.reflect.Constructor;
2121
import java.util.Arrays;
2222
import java.util.HashMap;
23-
import java.util.LinkedHashSet;
23+
import java.util.HashSet;
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
2727
import java.util.function.Consumer;
2828

29-
import org.springframework.aot.generate.GenerationContext;
3029
import org.springframework.aot.hint.ReflectionHints;
3130
import org.springframework.aot.hint.RuntimeHints;
32-
import org.springframework.aot.hint.annotation.Reflective;
33-
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
3431
import org.springframework.aot.hint.support.RuntimeHintsUtils;
35-
import org.springframework.beans.BeanUtils;
36-
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
37-
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
38-
import org.springframework.beans.factory.aot.BeanRegistrationCode;
39-
import org.springframework.beans.factory.support.RegisteredBean;
4032
import org.springframework.core.annotation.MergedAnnotation;
4133
import org.springframework.core.annotation.MergedAnnotations;
42-
import org.springframework.lang.Nullable;
4334
import org.springframework.util.ClassUtils;
4435
import org.springframework.util.ReflectionUtils;
4536

4637
/**
47-
* AOT {@code BeanRegistrationAotProcessor} that detects the presence of
48-
* {@link Reflective @Reflective} on annotated elements and invokes the
49-
* underlying {@link ReflectiveProcessor} implementations.
38+
* Process {@link Reflective} annotated elements.
5039
*
5140
* @author Stephane Nicoll
52-
* @author Sebastien Deleuze
41+
* since 6.0
5342
*/
54-
class ReflectiveProcessorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
43+
public class ReflectiveRuntimeHintsRegistrar {
5544

5645
private final Map<Class<? extends ReflectiveProcessor>, ReflectiveProcessor> processors = new HashMap<>();
5746

58-
@Nullable
59-
@Override
60-
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
61-
Class<?> beanClass = registeredBean.getBeanClass();
62-
Set<Entry> entries = new LinkedHashSet<>();
63-
processType(entries, beanClass);
64-
for (Class<?> implementedInterface : ClassUtils.getAllInterfacesForClass(beanClass)) {
65-
processType(entries, implementedInterface);
66-
}
67-
if (!entries.isEmpty()) {
68-
return new ReflectiveProcessorBeanRegistrationAotContribution(entries);
47+
48+
/**
49+
* Register the relevant runtime hints for elements that are annotated with
50+
* {@link Reflective}.
51+
* @param runtimeHints the runtime hints instance to use
52+
* @param types the types to process
53+
*/
54+
public void registerRuntimeHints(RuntimeHints runtimeHints, Class<?>... types) {
55+
Set<Entry> entries = new HashSet<>();
56+
Arrays.stream(types).forEach(type -> {
57+
processType(entries, type);
58+
for (Class<?> implementedInterface : ClassUtils.getAllInterfacesForClass(type)) {
59+
processType(entries, implementedInterface);
60+
}
61+
});
62+
entries.forEach(entry -> {
63+
AnnotatedElement element = entry.element();
64+
entry.processor().registerReflectionHints(runtimeHints.reflection(), element);
65+
registerAnnotationIfNecessary(runtimeHints, element);
66+
});
67+
}
68+
69+
private void registerAnnotationIfNecessary(RuntimeHints hints, AnnotatedElement element) {
70+
MergedAnnotation<Reflective> reflectiveAnnotation = MergedAnnotations.from(element)
71+
.get(Reflective.class);
72+
MergedAnnotation<?> metaSource = reflectiveAnnotation.getMetaSource();
73+
if (metaSource != null) {
74+
RuntimeHintsUtils.registerAnnotationIfNecessary(hints, metaSource);
6975
}
70-
return null;
7176
}
7277

7378
private void processType(Set<Entry> entries, Class<?> typeToProcess) {
@@ -99,13 +104,22 @@ private Entry createEntry(AnnotatedElement element) {
99104
Class<? extends ReflectiveProcessor>[] processorClasses = (Class<? extends ReflectiveProcessor>[])
100105
MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(Reflective.class).getClassArray("value");
101106
List<ReflectiveProcessor> processors = Arrays.stream(processorClasses).distinct()
102-
.map(processorClass -> this.processors.computeIfAbsent(processorClass, BeanUtils::instantiateClass))
107+
.map(processorClass -> this.processors.computeIfAbsent(processorClass, this::instantiateClass))
103108
.toList();
104109
ReflectiveProcessor processorToUse = (processors.size() == 1 ? processors.get(0)
105110
: new DelegateReflectiveProcessor(processors));
106111
return new Entry(element, processorToUse);
107112
}
108113

114+
private ReflectiveProcessor instantiateClass(Class<? extends ReflectiveProcessor> type) {
115+
try {
116+
return type.getDeclaredConstructor().newInstance();
117+
}
118+
catch (Exception ex) {
119+
throw new IllegalStateException("Failed to instantiate " + type, ex);
120+
}
121+
}
122+
109123
static class DelegateReflectiveProcessor implements ReflectiveProcessor {
110124

111125
private final Iterable<ReflectiveProcessor> processors;
@@ -123,33 +137,4 @@ public void registerReflectionHints(ReflectionHints hints, AnnotatedElement elem
123137

124138
private record Entry(AnnotatedElement element, ReflectiveProcessor processor) {}
125139

126-
private static class ReflectiveProcessorBeanRegistrationAotContribution implements BeanRegistrationAotContribution {
127-
128-
private final Iterable<Entry> entries;
129-
130-
public ReflectiveProcessorBeanRegistrationAotContribution(Iterable<Entry> entries) {
131-
this.entries = entries;
132-
}
133-
134-
@Override
135-
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
136-
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
137-
this.entries.forEach(entry -> {
138-
AnnotatedElement element = entry.element();
139-
entry.processor().registerReflectionHints(runtimeHints.reflection(), element);
140-
registerAnnotationIfNecessary(runtimeHints, element);
141-
});
142-
}
143-
144-
private void registerAnnotationIfNecessary(RuntimeHints hints, AnnotatedElement element) {
145-
MergedAnnotation<Reflective> reflectiveAnnotation = MergedAnnotations.from(element)
146-
.get(Reflective.class);
147-
MergedAnnotation<?> metaSource = reflectiveAnnotation.getMetaSource();
148-
if (metaSource != null) {
149-
RuntimeHintsUtils.registerAnnotationIfNecessary(hints, metaSource);
150-
}
151-
}
152-
153-
}
154-
155140
}

0 commit comments

Comments
 (0)