Make sure RuntimeHintsRegistrar are invoked only once
Close gh-28594
This commit is contained in:
@@ -25,17 +25,43 @@ import java.lang.annotation.Target;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
|
||||
/**
|
||||
* Indicates that one or more {@link RuntimeHintsRegistrar} implementations should be processed.
|
||||
* <p>Unlike declaring {@link RuntimeHintsRegistrar} as {@code spring/aot.factories},
|
||||
* {@code @ImportRuntimeHints} allows for more flexible use cases where registrations are only
|
||||
* processed if the annotated configuration class or bean method is considered by the
|
||||
* application context.
|
||||
* Indicates that one or more {@link RuntimeHintsRegistrar} implementations
|
||||
* should be processed.
|
||||
*
|
||||
* <p>Unlike declaring {@link RuntimeHintsRegistrar} using
|
||||
* {@code spring/aot.factories}, this annotation allows for more flexible
|
||||
* registration where it is only processed if the annotated component or bean
|
||||
* method is actually registered in the bean factory. To illustrate this
|
||||
* behavior, consider the following example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Configuration
|
||||
* public class MyConfiguration {
|
||||
*
|
||||
* @Bean
|
||||
* @ImportRuntimeHints(MyHints.class)
|
||||
* @Conditional(MyCondition.class)
|
||||
* public MyService myService() {
|
||||
* return new MyService();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* If the configuration class above is processed, {@code MyHints} will be
|
||||
* contributed only if {@code MyCondition} matches. If it does not, and
|
||||
* therefore {@code MyService} is not defined as a bean, the hints will
|
||||
* not be processed either.
|
||||
*
|
||||
* <p>If several components refer to the same {@link RuntimeHintsRegistrar}
|
||||
* implementation, it is invoked only once for a given bean factory
|
||||
* processing.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see org.springframework.aot.hint.RuntimeHints
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ImportRuntimeHints {
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package org.springframework.context.aot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -55,29 +57,37 @@ class RuntimeHintsBeanFactoryInitializationAotProcessor
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
AotFactoriesLoader loader = new AotFactoriesLoader(beanFactory);
|
||||
List<RuntimeHintsRegistrar> registrars = new ArrayList<>(
|
||||
loader.load(RuntimeHintsRegistrar.class));
|
||||
Map<Class<? extends RuntimeHintsRegistrar>, RuntimeHintsRegistrar> registrars = loader
|
||||
.load(RuntimeHintsRegistrar.class).stream()
|
||||
.collect(LinkedHashMap::new, (map, item) -> map.put(item.getClass(), item), Map::putAll);
|
||||
extractFromBeanFactory(beanFactory).forEach(registrarClass ->
|
||||
registrars.computeIfAbsent(registrarClass, BeanUtils::instantiateClass));
|
||||
return new RuntimeHintsRegistrarContribution(registrars.values(),
|
||||
beanFactory.getBeanClassLoader());
|
||||
}
|
||||
|
||||
private Set<Class<? extends RuntimeHintsRegistrar>> extractFromBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
Set<Class<? extends RuntimeHintsRegistrar>> registrarClasses = new LinkedHashSet<>();
|
||||
for (String beanName : beanFactory
|
||||
.getBeanNamesForAnnotation(ImportRuntimeHints.class)) {
|
||||
ImportRuntimeHints annotation = beanFactory.findAnnotationOnBean(beanName,
|
||||
ImportRuntimeHints.class);
|
||||
if (annotation != null) {
|
||||
registrars.addAll(extracted(beanName, annotation));
|
||||
registrarClasses.addAll(extractFromBeanDefinition(beanName, annotation));
|
||||
}
|
||||
}
|
||||
return new RuntimeHintsRegistrarContribution(registrars,
|
||||
beanFactory.getBeanClassLoader());
|
||||
return registrarClasses;
|
||||
}
|
||||
|
||||
private List<RuntimeHintsRegistrar> extracted(String beanName,
|
||||
private Set<Class<? extends RuntimeHintsRegistrar>> extractFromBeanDefinition(String beanName,
|
||||
ImportRuntimeHints annotation) {
|
||||
Class<? extends RuntimeHintsRegistrar>[] registrarClasses = annotation.value();
|
||||
List<RuntimeHintsRegistrar> registrars = new ArrayList<>(registrarClasses.length);
|
||||
for (Class<? extends RuntimeHintsRegistrar> registrarClass : registrarClasses) {
|
||||
|
||||
Set<Class<? extends RuntimeHintsRegistrar>> registrars = new LinkedHashSet<>();
|
||||
for (Class<? extends RuntimeHintsRegistrar> registrarClass : annotation.value()) {
|
||||
logger.trace(
|
||||
LogMessage.format("Loaded [%s] registrar from annotated bean [%s]",
|
||||
registrarClass.getCanonicalName(), beanName));
|
||||
registrars.add(BeanUtils.instantiateClass(registrarClass));
|
||||
registrars.add(registrarClass);
|
||||
}
|
||||
return registrars;
|
||||
}
|
||||
@@ -87,13 +97,13 @@ class RuntimeHintsBeanFactoryInitializationAotProcessor
|
||||
implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
|
||||
private final List<RuntimeHintsRegistrar> registrars;
|
||||
private final Iterable<RuntimeHintsRegistrar> registrars;
|
||||
|
||||
@Nullable
|
||||
private final ClassLoader beanClassLoader;
|
||||
|
||||
|
||||
RuntimeHintsRegistrarContribution(List<RuntimeHintsRegistrar> registrars,
|
||||
RuntimeHintsRegistrarContribution(Iterable<RuntimeHintsRegistrar> registrars,
|
||||
@Nullable ClassLoader beanClassLoader) {
|
||||
this.registrars = registrars;
|
||||
this.beanClassLoader = beanClassLoader;
|
||||
|
||||
Reference in New Issue
Block a user