From 86d45578d92fdb6a105021c21371faadf57a9011 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 8 Nov 2022 13:29:33 +0100 Subject: [PATCH] Introduce findAllAnnotationsOnBean variant on ListableBeanFactory Closes gh-29446 --- .../beans/factory/ListableBeanFactory.java | 25 ++++++- .../support/DefaultListableBeanFactory.java | 52 +++++++++++--- .../support/StaticListableBeanFactory.java | 13 +++- .../annotation/ImportRuntimeHints.java | 2 +- ...BeanFactoryInitializationAotProcessor.java | 72 +++---------------- .../support/AbstractApplicationContext.java | 9 +++ .../annotation/AnnotatedElementUtils.java | 6 +- .../setup/StubWebApplicationContext.java | 10 ++- 8 files changed, 109 insertions(+), 80 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java index bf88c509cd71..9eeb5403b54f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.util.Map; +import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.core.ResolvableType; @@ -338,6 +339,8 @@ Map getBeansOfType(@Nullable Class type, boolean includeNonSin * @throws BeansException if a bean could not be created * @since 3.0 * @see #findAnnotationOnBean + * @see #findAllAnnotationsOnBean(String, Class, boolean) + * @see #findAllAnnotationsOnBean(String, Class, boolean) */ Map getBeansWithAnnotation(Class annotationType) throws BeansException; @@ -380,4 +383,24 @@ A findAnnotationOnBean( String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; + /** + * Find all {@link Annotation} instances of {@code annotationType} on the specified + * bean, traversing its interfaces and superclasses if no annotation can be found on + * the given class itself, as well as checking the bean's factory method (if any). + * @param beanName the name of the bean to look for annotations on + * @param annotationType the type of annotation to look for + * (at class, interface or factory method level of the specified bean) + * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized + * just for the purpose of determining its object type + * @return the set of annotations of the given type found (potentially empty) + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 6.0 + * @see #getBeanNamesForAnnotation + * @see #findAnnotationOnBean(String, Class, boolean) + * @see #getType(String, boolean) + */ + Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException; + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 9e7dacfde841..bf7a54ae06e4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -729,19 +729,12 @@ public A findAnnotationOnBean( String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { - return findMergedAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit) - .synthesize(MergedAnnotation::isPresent).orElse(null); - } - - private MergedAnnotation findMergedAnnotationOnBean( - String beanName, Class annotationType, boolean allowFactoryBeanInit) { - Class beanType = getType(beanName, allowFactoryBeanInit); if (beanType != null) { MergedAnnotation annotation = MergedAnnotations.from(beanType, SearchStrategy.TYPE_HIERARCHY).get(annotationType); if (annotation.isPresent()) { - return annotation; + return annotation.synthesize(); } } if (containsBeanDefinition(beanName)) { @@ -753,7 +746,7 @@ private MergedAnnotation findMergedAnnotationOnBean( MergedAnnotation annotation = MergedAnnotations.from(beanClass, SearchStrategy.TYPE_HIERARCHY).get(annotationType); if (annotation.isPresent()) { - return annotation; + return annotation.synthesize(); } } } @@ -763,11 +756,48 @@ private MergedAnnotation findMergedAnnotationOnBean( MergedAnnotation annotation = MergedAnnotations.from(factoryMethod, SearchStrategy.TYPE_HIERARCHY).get(annotationType); if (annotation.isPresent()) { - return annotation; + return annotation.synthesize(); + } + } + } + return null; + } + + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + Set annotations = new LinkedHashSet<>(); + Class beanType = getType(beanName, allowFactoryBeanInit); + if (beanType != null) { + MergedAnnotations.from(beanType, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(annotationType) + .filter(MergedAnnotation::isPresent) + .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); + } + if (containsBeanDefinition(beanName)) { + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + // Check raw bean class, e.g. in case of a proxy. + if (bd.hasBeanClass() && bd.getFactoryMethodName() == null) { + Class beanClass = bd.getBeanClass(); + if (beanClass != beanType) { + MergedAnnotations.from(beanClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(annotationType) + .filter(MergedAnnotation::isPresent) + .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); } } + // Check annotations declared on factory method, if any. + Method factoryMethod = bd.getResolvedFactoryMethod(); + if (factoryMethod != null) { + MergedAnnotations.from(factoryMethod, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(annotationType) + .filter(MergedAnnotation::isPresent) + .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); + } } - return MergedAnnotation.missing(); + return annotations; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index e6c0eeee032f..f3d43d107ed6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -19,9 +19,11 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import org.springframework.beans.BeansException; @@ -472,4 +474,13 @@ public A findAnnotationOnBean( return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null); } + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { + + Class beanType = getType(beanName, allowFactoryBeanInit); + return (beanType != null ? + AnnotatedElementUtils.findAllMergedAnnotations(beanType, annotationType) : Collections.emptySet()); + } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java index ea38ed7f3d09..d33550db62cd 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java @@ -61,7 +61,7 @@ * @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 { diff --git a/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java index 4d80cf146f84..ffb4c40d6fc0 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java @@ -16,12 +16,8 @@ package org.springframework.context.aot; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -36,12 +32,8 @@ 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.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.log.LogMessage; import org.springframework.lang.Nullable; @@ -54,6 +46,8 @@ * * @author Brian Clozel * @author Sebastien Deleuze + * @author Juergen Hoeller + * @since 6.0 */ class RuntimeHintsBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { @@ -67,67 +61,26 @@ public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableL .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()); + return new RuntimeHintsRegistrarContribution(registrars.values(), beanFactory.getBeanClassLoader()); } private Set> extractFromBeanFactory(ConfigurableListableBeanFactory beanFactory) { Set> registrarClasses = new LinkedHashSet<>(); - for (String beanName : beanFactory - .getBeanNamesForAnnotation(ImportRuntimeHints.class)) { - findAnnotationsOnBean(beanFactory, beanName, - ImportRuntimeHints.class).forEach(annotation -> - registrarClasses.addAll(extractFromBeanDefinition(beanName, annotation))); + for (String beanName : beanFactory.getBeanDefinitionNames()) { + beanFactory.findAllAnnotationsOnBean(beanName, ImportRuntimeHints.class, true) + .forEach(annotation -> registrarClasses.addAll(extractFromBeanDefinition(beanName, annotation))); } return registrarClasses; } - private List findAnnotationsOnBean(ConfigurableListableBeanFactory beanFactory, - String beanName, Class annotationType) { - - List annotations = new ArrayList<>(); - Class beanType = beanFactory.getType(beanName, true); - if (beanType != null) { - MergedAnnotations.from(beanType, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .stream(annotationType) - .filter(MergedAnnotation::isPresent) - .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); - } - if (beanFactory.containsBeanDefinition(beanName)) { - BeanDefinition bd = beanFactory.getBeanDefinition(beanName); - if (bd instanceof RootBeanDefinition rbd) { - // Check raw bean class, e.g. in case of a proxy. - if (rbd.hasBeanClass() && rbd.getFactoryMethodName() == null) { - Class beanClass = rbd.getBeanClass(); - if (beanClass != beanType) { - MergedAnnotations.from(beanClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .stream(annotationType) - .filter(MergedAnnotation::isPresent) - .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); - } - } - // Check annotations declared on factory method, if any. - Method factoryMethod = rbd.getResolvedFactoryMethod(); - if (factoryMethod != null) { - MergedAnnotations.from(factoryMethod, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .stream(annotationType) - .filter(MergedAnnotation::isPresent) - .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); - } - } - } - return annotations; - } - private Set> extractFromBeanDefinition(String beanName, ImportRuntimeHints annotation) { Set> registrars = new LinkedHashSet<>(); for (Class registrarClass : annotation.value()) { if (logger.isTraceEnabled()) { - logger.trace( - LogMessage.format("Loaded [%s] registrar from annotated bean [%s]", - registrarClass.getCanonicalName(), beanName)); + logger.trace(LogMessage.format("Loaded [%s] registrar from annotated bean [%s]", + registrarClass.getCanonicalName(), beanName)); } registrars.add(registrarClass); } @@ -135,26 +88,24 @@ private Set> extractFromBeanDefinition(St } - static class RuntimeHintsRegistrarContribution - implements BeanFactoryInitializationAotContribution { - + static class RuntimeHintsRegistrarContribution implements BeanFactoryInitializationAotContribution { private final Iterable registrars; @Nullable private final ClassLoader beanClassLoader; - RuntimeHintsRegistrarContribution(Iterable registrars, @Nullable ClassLoader beanClassLoader) { + this.registrars = registrars; this.beanClassLoader = beanClassLoader; } - @Override public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + RuntimeHints hints = generationContext.getRuntimeHints(); this.registrars.forEach(registrar -> { if (logger.isTraceEnabled()) { @@ -165,7 +116,6 @@ public void applyTo(GenerationContext generationContext, registrar.registerHints(hints, this.beanClassLoader); }); } - } } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index be2a0c34150d..60978c2aa5e0 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -1318,6 +1318,15 @@ public A findAnnotationOnBean( return getBeanFactory().findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); } + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + assertBeanFactoryActive(); + return getBeanFactory().findAllAnnotationsOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 500feec26ebe..1ac4f84a5e80 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -829,8 +829,7 @@ private static MultiValueMap nullIfEmpty(MultiValueMap Comparator> highAggregateIndexesFirst() { - return Comparator.> comparingInt( - MergedAnnotation::getAggregateIndex).reversed(); + return Comparator.> comparingInt(MergedAnnotation::getAggregateIndex).reversed(); } @Nullable @@ -840,8 +839,7 @@ private static AnnotationAttributes getAnnotationAttributes(MergedAnnotation if (!annotation.isPresent()) { return null; } - return annotation.asAnnotationAttributes( - Adapt.values(classValuesAsString, nestedAnnotationsAsMap)); + return annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, nestedAnnotationsAsMap)); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java index 02dbfa93badc..1b5492bd8470 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -323,6 +323,14 @@ public A findAnnotationOnBean( return this.beanFactory.findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); } + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return this.beanFactory.findAllAnnotationsOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface