From 71182ab54b62924ad2e475995872435211b33807 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sun, 3 Sep 2017 08:19:34 +0200 Subject: [PATCH] Provide hierarchy traversal support for getBeanNamesForAnnotation Issue: SPR-15923 --- .../beans/factory/BeanFactoryUtils.java | 32 +++++++++++++++++ .../beans/factory/BeanFactoryUtilsTests.java | 36 +++++++++++++++++-- .../tests/sample/beans/AnnotatedBean.java | 24 +++++++++++++ .../tests/sample/beans/TestAnnotation.java | 27 ++++++++++++++ .../factory/BeanFactoryUtilsTests-root.xml | 2 ++ 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 spring-beans/src/test/java/org/springframework/tests/sample/beans/AnnotatedBean.java create mode 100644 spring-beans/src/test/java/org/springframework/tests/sample/beans/TestAnnotation.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java index 079139fe41..2fad1042ff 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -16,6 +16,7 @@ package org.springframework.beans.factory; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; @@ -353,6 +354,37 @@ public abstract class BeanFactoryUtils { return uniqueBean(type, beansOfType); } + /** + * Get all bean names whose {@code Class} has the supplied {@link Annotation} + * type, including those defined in ancestor factories, without creating any bean + * instances yet. Will return unique names in case of overridden bean definitions. + * @param lbf the bean factory + * @param annotationType the type of annotation to look for + * @return the array of matching bean names, or an empty array if none + * @since 5.0 + */ + public static String[] beanNamesForAnnotationIncludingAncestors( + ListableBeanFactory lbf, Class annotationType) { + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + String[] result = lbf.getBeanNamesForAnnotation(annotationType); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + String[] parentResult = beanNamesForAnnotationIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), annotationType); + List resultList = new ArrayList<>(); + resultList.addAll(Arrays.asList(result)); + for (String beanName : parentResult) { + if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { + resultList.add(beanName); + } + } + result = StringUtils.toStringArray(resultList); + } + } + return result; + } + /** * Return a single bean of the given type or subtypes, also picking up beans * defined in ancestor bean factories if the current bean factory is a diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java index a5eced11ce..587f30261e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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. @@ -28,8 +28,10 @@ import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.cglib.proxy.NoOp; import org.springframework.core.io.Resource; +import org.springframework.tests.sample.beans.AnnotatedBean; import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.IndexedTestBean; +import org.springframework.tests.sample.beans.TestAnnotation; import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.sample.beans.factory.DummyFactory; import org.springframework.util.ObjectUtils; @@ -91,8 +93,8 @@ public class BeanFactoryUtilsTests { // Leaf count assertTrue(this.listableBeanFactory.getBeanDefinitionCount() == 1); // Count minus duplicate - assertTrue("Should count 7 beans, not " + BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory), - BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory) == 7); + assertTrue("Should count 8 beans, not " + BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory), + BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory) == 8); } @Test @@ -265,6 +267,34 @@ public class BeanFactoryUtilsTests { assertEquals(this.listableBeanFactory.getBean("&testFactory2"), beans.get("&testFactory2")); } + @Test + public void testHierarchicalNamesForAnnotationWithNoMatch() throws Exception { + List names = Arrays.asList( + BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class)); + assertEquals(0, names.size()); + } + + @Test + public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Exception { + List names = Arrays.asList( + BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class)); + assertEquals(1, names.size()); + assertTrue(names.contains("annotatedBean")); + // Distinguish from default ListableBeanFactory behavior + assertTrue(listableBeanFactory.getBeanNamesForAnnotation(TestAnnotation.class).length == 0); + } + + @Test + public void testGetBeanNamesForAnnotationWithOverride() throws Exception { + AnnotatedBean annotatedBean = new AnnotatedBean(); + this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean); + List names = Arrays.asList( + BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class)); + assertEquals(2, names.size()); + assertTrue(names.contains("annotatedBean")); + assertTrue(names.contains("anotherAnnotatedBean")); + } + @Test public void testADependencies() { String[] deps = this.dependentBeansFactory.getDependentBeans("a"); diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/AnnotatedBean.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/AnnotatedBean.java new file mode 100644 index 0000000000..d08c5476d7 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/AnnotatedBean.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.tests.sample.beans; + +/** + * @author Stephane Nicoll + */ +@TestAnnotation +public class AnnotatedBean { +} diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestAnnotation.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestAnnotation.java new file mode 100644 index 0000000000..5e64245ca0 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestAnnotation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.tests.sample.beans; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author Stephane Nicoll + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { +} diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/BeanFactoryUtilsTests-root.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/BeanFactoryUtilsTests-root.xml index 28f8d65277..7cc47b9a01 100644 --- a/spring-beans/src/test/resources/org/springframework/beans/factory/BeanFactoryUtilsTests-root.xml +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/BeanFactoryUtilsTests-root.xml @@ -10,6 +10,8 @@ + + custom