Browse Source
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-28469pull/27579/merge
Stephane Nicoll
3 years ago
8 changed files with 684 additions and 0 deletions
@ -0,0 +1,149 @@
@@ -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 @@
@@ -0,0 +1,2 @@
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor= \ |
||||
org.springframework.context.aot.ReflectiveProcessorBeanRegistrationAotProcessor |
@ -0,0 +1,220 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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