Add support of declarative use of reflection
This commit adds a `@Reflective` annotation that can be used to declare that the annotated element requires reflection at runtime. By default, the annotated element is exposed but this can be customized by specifying a dedicated `ReflectiveProcessor`. Closes gh-28469
This commit is contained in:
parent
2517c72f7d
commit
4cca190aad
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.context.aot;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.springframework.aot.generate.GenerationContext;
|
||||||
|
import org.springframework.aot.hint.MemberCategory;
|
||||||
|
import org.springframework.aot.hint.ReflectionHints;
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.aot.hint.TypeHint.Builder;
|
||||||
|
import org.springframework.aot.hint.annotation.Reflective;
|
||||||
|
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
|
||||||
|
import org.springframework.aot.hint.support.RuntimeHintsUtils;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||||
|
import org.springframework.beans.factory.support.RegisteredBean;
|
||||||
|
import org.springframework.core.annotation.MergedAnnotation;
|
||||||
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AOT {@code BeanRegistrationAotProcessor} that detects the presence of
|
||||||
|
* {@link Reflective @Reflective} on annotated elements and invoke the
|
||||||
|
* underlying {@link ReflectiveProcessor} implementations.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ReflectiveProcessorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||||
|
|
||||||
|
private final Map<Class<? extends ReflectiveProcessor>, ReflectiveProcessor> processors = new HashMap<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||||
|
Class<?> beanClass = registeredBean.getBeanClass();
|
||||||
|
Set<Entry> entries = new LinkedHashSet<>();
|
||||||
|
if (isReflective(beanClass)) {
|
||||||
|
entries.add(createEntry(beanClass));
|
||||||
|
}
|
||||||
|
doWithReflectiveConstructors(beanClass, constructor ->
|
||||||
|
entries.add(createEntry(constructor)));
|
||||||
|
ReflectionUtils.doWithFields(beanClass, field ->
|
||||||
|
entries.add(createEntry(field)), this::isReflective);
|
||||||
|
ReflectionUtils.doWithMethods(beanClass, method ->
|
||||||
|
entries.add(createEntry(method)), this::isReflective);
|
||||||
|
if (!entries.isEmpty()) {
|
||||||
|
return new ReflectiveProcessorBeanRegistrationAotContribution(entries);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doWithReflectiveConstructors(Class<?> beanClass, Consumer<Constructor<?>> consumer) {
|
||||||
|
for (Constructor<?> constructor : beanClass.getDeclaredConstructors()) {
|
||||||
|
if (isReflective(constructor)) {
|
||||||
|
consumer.accept(constructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isReflective(AnnotatedElement element) {
|
||||||
|
return MergedAnnotations.from(element).isPresent(Reflective.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Entry createEntry(AnnotatedElement element) {
|
||||||
|
Class<? extends ReflectiveProcessor>[] processorClasses = (Class<? extends ReflectiveProcessor>[])
|
||||||
|
MergedAnnotations.from(element).get(Reflective.class).getClassArray("value");
|
||||||
|
List<ReflectiveProcessor> processors = Arrays.stream(processorClasses).distinct()
|
||||||
|
.map(processorClass -> this.processors.computeIfAbsent(processorClass, BeanUtils::instantiateClass))
|
||||||
|
.toList();
|
||||||
|
ReflectiveProcessor processorToUse = (processors.size() == 1 ? processors.get(0)
|
||||||
|
: new DelegateReflectiveProcessor(processors));
|
||||||
|
return new Entry(element, processorToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DelegateReflectiveProcessor implements ReflectiveProcessor {
|
||||||
|
|
||||||
|
private final Iterable<ReflectiveProcessor> processors;
|
||||||
|
|
||||||
|
public DelegateReflectiveProcessor(Iterable<ReflectiveProcessor> processors) {
|
||||||
|
this.processors = processors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
|
||||||
|
this.processors.forEach(processor -> processor.registerReflectionHints(hints, element));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Entry(AnnotatedElement element, ReflectiveProcessor processor) {}
|
||||||
|
|
||||||
|
private static class ReflectiveProcessorBeanRegistrationAotContribution implements BeanRegistrationAotContribution {
|
||||||
|
|
||||||
|
private static final Consumer<Builder> ANNOTATION_CUSTOMIZATIONS = hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||||
|
|
||||||
|
private final Iterable<Entry> entries;
|
||||||
|
|
||||||
|
public ReflectiveProcessorBeanRegistrationAotContribution(Iterable<Entry> entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||||
|
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
|
||||||
|
runtimeHints.reflection().registerType(Reflective.class, ANNOTATION_CUSTOMIZATIONS);
|
||||||
|
this.entries.forEach(entry -> {
|
||||||
|
AnnotatedElement element = entry.element();
|
||||||
|
entry.processor().registerReflectionHints(runtimeHints.reflection(), element);
|
||||||
|
registerAnnotationIfNecessary(runtimeHints, element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerAnnotationIfNecessary(RuntimeHints hints, AnnotatedElement element) {
|
||||||
|
MergedAnnotation<Reflective> reflectiveAnnotation = MergedAnnotations.from(element).get(Reflective.class);
|
||||||
|
if (reflectiveAnnotation.getDistance() > 0) {
|
||||||
|
RuntimeHintsUtils.registerAnnotation(hints, reflectiveAnnotation.getRoot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor= \
|
||||||
|
org.springframework.context.aot.ReflectiveProcessorBeanRegistrationAotProcessor
|
|
@ -0,0 +1,220 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.context.aot;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.aot.generate.DefaultGenerationContext;
|
||||||
|
import org.springframework.aot.generate.GenerationContext;
|
||||||
|
import org.springframework.aot.generate.InMemoryGeneratedFiles;
|
||||||
|
import org.springframework.aot.hint.MemberCategory;
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.aot.hint.TypeReference;
|
||||||
|
import org.springframework.aot.hint.annotation.Reflective;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.RegisteredBean;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.core.annotation.SynthesizedAnnotation;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ReflectiveProcessorBeanRegistrationAotProcessor}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ReflectiveProcessorBeanRegistrationAotProcessorTests {
|
||||||
|
|
||||||
|
private final ReflectiveProcessorBeanRegistrationAotProcessor processor = new ReflectiveProcessorBeanRegistrationAotProcessor();
|
||||||
|
|
||||||
|
private final GenerationContext generationContext = new DefaultGenerationContext(
|
||||||
|
new InMemoryGeneratedFiles());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldIgnoreNonAnnotatedType() {
|
||||||
|
assertThat(createContribution(String.class)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProcessAnnotationOnType() {
|
||||||
|
process(SampleTypeAnnotatedBean.class);
|
||||||
|
assertThat(this.generationContext.getRuntimeHints().reflection().getTypeHint(SampleTypeAnnotatedBean.class))
|
||||||
|
.isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProcessAnnotationOnConstructor() {
|
||||||
|
process(SampleConstructorAnnotatedBean.class);
|
||||||
|
assertThat(this.generationContext.getRuntimeHints().reflection().getTypeHint(SampleConstructorAnnotatedBean.class))
|
||||||
|
.satisfies(typeHint -> assertThat(typeHint.constructors()).singleElement()
|
||||||
|
.satisfies(constructorHint -> assertThat(constructorHint.getParameterTypes())
|
||||||
|
.containsExactly(TypeReference.of(String.class))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProcessAnnotationOnField() {
|
||||||
|
process(SampleFieldAnnotatedBean.class);
|
||||||
|
assertThat(this.generationContext.getRuntimeHints().reflection().getTypeHint(SampleFieldAnnotatedBean.class))
|
||||||
|
.satisfies(typeHint -> assertThat(typeHint.fields()).singleElement()
|
||||||
|
.satisfies(fieldHint -> assertThat(fieldHint.getName()).isEqualTo("managed")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProcessAnnotationOnMethod() {
|
||||||
|
process(SampleMethodAnnotatedBean.class);
|
||||||
|
assertThat(this.generationContext.getRuntimeHints().reflection().getTypeHint(SampleMethodAnnotatedBean.class))
|
||||||
|
.satisfies(typeHint -> assertThat(typeHint.methods()).singleElement()
|
||||||
|
.satisfies(methodHint -> assertThat(methodHint.getName()).isEqualTo("managed")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRegisterAnnotation() {
|
||||||
|
process(SampleMethodMetaAnnotatedBean.class);
|
||||||
|
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
|
||||||
|
assertThat(runtimeHints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint ->
|
||||||
|
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||||
|
assertThat(runtimeHints.proxies().jdkProxies()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRegisterAnnotationAndProxyWithAliasFor() {
|
||||||
|
process(SampleMethodMetaAnnotatedBeanWithAlias.class);
|
||||||
|
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
|
||||||
|
assertThat(runtimeHints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint ->
|
||||||
|
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||||
|
assertThat(runtimeHints.proxies().jdkProxies()).anySatisfy(jdkProxyHint ->
|
||||||
|
assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(
|
||||||
|
TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
|
||||||
|
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||||
|
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
|
||||||
|
return this.processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process(Class<?> beanClass) {
|
||||||
|
BeanRegistrationAotContribution contribution = createContribution(beanClass);
|
||||||
|
assertThat(contribution).isNotNull();
|
||||||
|
contribution.applyTo(this.generationContext, mock(BeanRegistrationCode.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Reflective
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class SampleTypeAnnotatedBean {
|
||||||
|
|
||||||
|
private String notManaged;
|
||||||
|
|
||||||
|
public void notManaged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class SampleConstructorAnnotatedBean {
|
||||||
|
|
||||||
|
@Reflective
|
||||||
|
SampleConstructorAnnotatedBean(String name) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleConstructorAnnotatedBean(Integer nameAsNumber) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class SampleFieldAnnotatedBean {
|
||||||
|
|
||||||
|
@Reflective
|
||||||
|
String managed;
|
||||||
|
|
||||||
|
String notManaged;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class SampleMethodAnnotatedBean {
|
||||||
|
|
||||||
|
@Reflective
|
||||||
|
void managed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void notManaged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class SampleMethodMetaAnnotatedBean {
|
||||||
|
|
||||||
|
@SampleInvoker
|
||||||
|
void invoke() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void notManaged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class SampleMethodMetaAnnotatedBeanWithAlias {
|
||||||
|
|
||||||
|
@RetryInvoker
|
||||||
|
void invoke() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void notManaged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Reflective
|
||||||
|
@interface SampleInvoker {
|
||||||
|
|
||||||
|
int retries() default 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Target({ ElementType.METHOD })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@SampleInvoker
|
||||||
|
@interface RetryInvoker {
|
||||||
|
|
||||||
|
@AliasFor(attribute = "retries", annotation = SampleInvoker.class)
|
||||||
|
int value() default 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.aot.hint.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the annotated element requires reflection.
|
||||||
|
*
|
||||||
|
* <p>When present, either directly or as a meta-annotation, this annotation
|
||||||
|
* triggers the configured {@linkplain ReflectiveProcessor processors} against
|
||||||
|
* the annotated element. By default, a reflection hint is added on the
|
||||||
|
* annotated element so that it can be discovered and invoked if necessary.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 6.0
|
||||||
|
* @see SimpleReflectiveProcessor
|
||||||
|
*/
|
||||||
|
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR,
|
||||||
|
ElementType.FIELD, ElementType.METHOD })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface Reflective {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link #processors()}.
|
||||||
|
*/
|
||||||
|
Class<? extends ReflectiveProcessor>[] value() default SimpleReflectiveProcessor.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ReflectiveProcessor} implementations to invoke against the
|
||||||
|
* annotated element.
|
||||||
|
*/
|
||||||
|
@AliasFor("value")
|
||||||
|
Class<? extends ReflectiveProcessor>[] processors() default SimpleReflectiveProcessor.class;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.aot.hint.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.ReflectionHints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an {@link AnnotatedElement} and register the necessary reflection
|
||||||
|
* hints for it.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
public interface ReflectiveProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register {@link ReflectionHints} against the specified {@link AnnotatedElement}.
|
||||||
|
* @param hints the reflection hints instance to use
|
||||||
|
* @param element the element to process
|
||||||
|
*/
|
||||||
|
void registerReflectionHints(ReflectionHints hints, AnnotatedElement element);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.aot.hint.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.ExecutableHint.Builder;
|
||||||
|
import org.springframework.aot.hint.ExecutableMode;
|
||||||
|
import org.springframework.aot.hint.ReflectionHints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple {@link ReflectiveProcessor} implementation that registers only a
|
||||||
|
* reflection hint for the annotated type. Can be sub-classed to customize
|
||||||
|
* processing for a given {@link AnnotatedElement} type.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
public class SimpleReflectiveProcessor implements ReflectiveProcessor {
|
||||||
|
|
||||||
|
private static final Consumer<Builder> INVOKE_EXECUTABLE = hint -> hint.setModes(ExecutableMode.INVOKE);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
|
||||||
|
if (element instanceof Class<?> type) {
|
||||||
|
registerTypeHint(hints, type);
|
||||||
|
}
|
||||||
|
else if (element instanceof Constructor<?> constructor) {
|
||||||
|
registerConstructorHint(hints, constructor);
|
||||||
|
}
|
||||||
|
else if (element instanceof Field field) {
|
||||||
|
registerFieldHint(hints, field);
|
||||||
|
}
|
||||||
|
else if (element instanceof Method method) {
|
||||||
|
registerMethodHint(hints, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register {@link ReflectionHints} against the specified {@link Class}.
|
||||||
|
* @param hints the reflection hints instance to use
|
||||||
|
* @param type the class to process
|
||||||
|
*/
|
||||||
|
protected void registerTypeHint(ReflectionHints hints, Class<?> type) {
|
||||||
|
hints.registerType(type, hint -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register {@link ReflectionHints} against the specified {@link Constructor}.
|
||||||
|
* @param hints the reflection hints instance to use
|
||||||
|
* @param constructor the constructor to process
|
||||||
|
*/
|
||||||
|
protected void registerConstructorHint(ReflectionHints hints, Constructor<?> constructor) {
|
||||||
|
hints.registerConstructor(constructor, INVOKE_EXECUTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register {@link ReflectionHints} against the specified {@link Field}.
|
||||||
|
* @param hints the reflection hints instance to use
|
||||||
|
* @param field the field to process
|
||||||
|
*/
|
||||||
|
protected void registerFieldHint(ReflectionHints hints, Field field) {
|
||||||
|
hints.registerField(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register {@link ReflectionHints} against the specified {@link Method}.
|
||||||
|
* @param hints the reflection hints instance to use
|
||||||
|
* @param method the method to process
|
||||||
|
*/
|
||||||
|
protected void registerMethodHint(ReflectionHints hints, Method method) {
|
||||||
|
hints.registerMethod(method, INVOKE_EXECUTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Annotation support for runtime hints.
|
||||||
|
*/
|
||||||
|
@NonNullApi
|
||||||
|
@NonNullFields
|
||||||
|
package org.springframework.aot.hint.annotation;
|
||||||
|
|
||||||
|
import org.springframework.lang.NonNullApi;
|
||||||
|
import org.springframework.lang.NonNullFields;
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.aot.hint.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.ExecutableMode;
|
||||||
|
import org.springframework.aot.hint.ReflectionHints;
|
||||||
|
import org.springframework.aot.hint.TypeReference;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SimpleReflectiveProcessor}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class SimpleReflectiveProcessorTests {
|
||||||
|
|
||||||
|
private final SimpleReflectiveProcessor processor = new SimpleReflectiveProcessor();
|
||||||
|
|
||||||
|
private final ReflectionHints hints = new ReflectionHints();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerReflectiveHintsForClass() {
|
||||||
|
processor.registerReflectionHints(hints, SampleBean.class);
|
||||||
|
assertThat(hints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleBean.class));
|
||||||
|
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||||
|
assertThat(typeHint.constructors()).isEmpty();
|
||||||
|
assertThat(typeHint.fields()).isEmpty();
|
||||||
|
assertThat(typeHint.methods()).isEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerReflectiveHintsForConstructor() {
|
||||||
|
Constructor<?> constructor = SampleBean.class.getDeclaredConstructors()[0];
|
||||||
|
processor.registerReflectionHints(hints, constructor);
|
||||||
|
assertThat(hints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleBean.class));
|
||||||
|
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||||
|
assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint -> {
|
||||||
|
assertThat(constructorHint.getName()).isEqualTo("<init>");
|
||||||
|
assertThat(constructorHint.getModes()).containsExactly(ExecutableMode.INVOKE);
|
||||||
|
assertThat(constructorHint.getParameterTypes()).containsExactly(TypeReference.of(String.class));
|
||||||
|
});
|
||||||
|
assertThat(typeHint.fields()).isEmpty();
|
||||||
|
assertThat(typeHint.methods()).isEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerReflectiveHintsForField() throws NoSuchFieldException {
|
||||||
|
Field field = SampleBean.class.getDeclaredField("name");
|
||||||
|
processor.registerReflectionHints(hints, field);
|
||||||
|
assertThat(hints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleBean.class));
|
||||||
|
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||||
|
assertThat(typeHint.constructors()).isEmpty();
|
||||||
|
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
|
||||||
|
assertThat(fieldHint.getName()).isEqualTo("name"));
|
||||||
|
assertThat(typeHint.methods()).isEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerReflectiveHintsForMethod() throws NoSuchMethodException {
|
||||||
|
Method method = SampleBean.class.getDeclaredMethod("setName", String.class);
|
||||||
|
processor.registerReflectionHints(hints, method);
|
||||||
|
assertThat(hints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleBean.class));
|
||||||
|
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||||
|
assertThat(typeHint.constructors()).isEmpty();
|
||||||
|
assertThat(typeHint.fields()).isEmpty();
|
||||||
|
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
|
||||||
|
assertThat(methodHint.getName()).isEqualTo("setName");
|
||||||
|
assertThat(methodHint.getModes()).containsExactly(ExecutableMode.INVOKE);
|
||||||
|
assertThat(methodHint.getParameterTypes()).containsExactly(TypeReference.of(String.class));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SampleBean {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
SampleBean(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue