Skip to content

Commit

Permalink
Expose registrar for @Reflective
Browse files Browse the repository at this point in the history
This commit exposes the logic of processing `@Reflective` on beans so
that it can be reused in custom arrangements.

Closes gh-28975
  • Loading branch information
snicoll committed Aug 17, 2022
1 parent ef7ab76 commit 4556895
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.util.Arrays;

import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;

/**
* AOT {@code BeanFactoryInitializationAotProcessor} that detects the presence
* of {@link Reflective @Reflective} on annotated elements of all registered
* beans and invokes the underlying {@link ReflectiveProcessor} implementations.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
*/
class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {

private static final ReflectiveRuntimeHintsRegistrar REGISTRAR = new ReflectiveRuntimeHintsRegistrar();

@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
Class<?>[] beanTypes = Arrays.stream(beanFactory.getBeanDefinitionNames())
.map(beanName -> RegisteredBean.of(beanFactory, beanName).getBeanClass())
.toArray(Class<?>[]::new);
return new ReflectiveProcessorBeanFactoryInitializationAotContribution(beanTypes);
}

private static class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {

private final Class<?>[] types;

public ReflectiveProcessorBeanFactoryInitializationAotContribution(Class<?>[] types) {
this.types = types;
}

@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
REGISTRAR.registerRuntimeHints(runtimeHints, this.types);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
org.springframework.context.aot.ReflectiveProcessorBeanFactoryInitializationAotProcessor

org.springframework.beans.factory.aot.BeanRegistrationAotProcessor= \
org.springframework.context.aot.ReflectiveProcessorBeanRegistrationAotProcessor,\
org.springframework.cache.annotation.CachingBeanRegistrationAotProcessor
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* 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.Constructor;

import org.junit.jupiter.api.Test;

import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.testfixture.aot.generate.TestGenerationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

/**
* Tests for {@link ReflectiveProcessorBeanFactoryInitializationAotProcessor}.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
*/
class ReflectiveProcessorBeanFactoryInitializationAotProcessorTests {

private final ReflectiveProcessorBeanFactoryInitializationAotProcessor processor = new ReflectiveProcessorBeanFactoryInitializationAotProcessor();

private final GenerationContext generationContext = new TestGenerationContext();

@Test
void processorIsRegistered() {
assertThat(AotServices.factories(getClass().getClassLoader()).load(BeanFactoryInitializationAotProcessor.class))
.anyMatch(ReflectiveProcessorBeanFactoryInitializationAotProcessor.class::isInstance);
}

@Test
void shouldProcessAnnotationOnType() {
process(SampleTypeAnnotatedBean.class);
assertThat(RuntimeHintsPredicates.reflection().onType(SampleTypeAnnotatedBean.class))
.accepts(this.generationContext.getRuntimeHints());
}

@Test
void shouldProcessAllBeans() throws NoSuchMethodException {
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
process(SampleTypeAnnotatedBean.class, SampleConstructorAnnotatedBean.class);
Constructor<?> constructor = SampleConstructorAnnotatedBean.class.getDeclaredConstructor(String.class);
assertThat(reflection.onType(SampleTypeAnnotatedBean.class).and(reflection.onConstructor(constructor)))
.accepts(this.generationContext.getRuntimeHints());
}

private void process(Class<?>... beanClasses) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
for (Class<?> beanClass : beanClasses) {
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
}
BeanFactoryInitializationAotContribution contribution = this.processor.processAheadOfTime(beanFactory);
assertThat(contribution).isNotNull();
contribution.applyTo(this.generationContext, mock(BeanFactoryInitializationCode.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) {

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* @author Sam Brannen
* @since 6.0
* @see SimpleReflectiveProcessor
* @see ReflectiveRuntimeHintsRegistrar
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR,
ElementType.FIELD, ElementType.METHOD })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,65 @@
* limitations under the License.
*/

package org.springframework.context.aot;
package org.springframework.aot.hint.annotation;

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.HashSet;
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.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
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.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
* AOT {@code BeanRegistrationAotProcessor} that detects the presence of
* {@link Reflective @Reflective} on annotated elements and invokes the
* underlying {@link ReflectiveProcessor} implementations.
* Process {@link Reflective} annotated elements.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
* since 6.0
*/
class ReflectiveProcessorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
public class ReflectiveRuntimeHintsRegistrar {

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<>();
processType(entries, beanClass);
for (Class<?> implementedInterface : ClassUtils.getAllInterfacesForClass(beanClass)) {
processType(entries, implementedInterface);
}
if (!entries.isEmpty()) {
return new ReflectiveProcessorBeanRegistrationAotContribution(entries);

/**
* Register the relevant runtime hints for elements that are annotated with
* {@link Reflective}.
* @param runtimeHints the runtime hints instance to use
* @param types the types to process
*/
public void registerRuntimeHints(RuntimeHints runtimeHints, Class<?>... types) {
Set<Entry> entries = new HashSet<>();
Arrays.stream(types).forEach(type -> {
processType(entries, type);
for (Class<?> implementedInterface : ClassUtils.getAllInterfacesForClass(type)) {
processType(entries, implementedInterface);
}
});
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);
MergedAnnotation<?> metaSource = reflectiveAnnotation.getMetaSource();
if (metaSource != null) {
RuntimeHintsUtils.registerAnnotationIfNecessary(hints, metaSource);
}
return null;
}

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

private ReflectiveProcessor instantiateClass(Class<? extends ReflectiveProcessor> type) {
try {
return type.getDeclaredConstructor().newInstance();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to instantiate " + type, ex);
}
}

static class DelegateReflectiveProcessor implements ReflectiveProcessor {

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

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

private static class ReflectiveProcessorBeanRegistrationAotContribution implements BeanRegistrationAotContribution {

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();
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);
MergedAnnotation<?> metaSource = reflectiveAnnotation.getMetaSource();
if (metaSource != null) {
RuntimeHintsUtils.registerAnnotationIfNecessary(hints, metaSource);
}
}

}

}
Loading

0 comments on commit 4556895

Please sign in to comment.