From 2ee1ce61c038c27fb163316961d3180de42f8297 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 20 Jul 2019 17:03:31 +0100 Subject: [PATCH] Add missing variants of getBeanNamesForType Update `ListableBeanFactory` and `BeanFactoryUtils` to add the missing `getBeanNamesForType` methods that accept a `ResolvableType` rather than a `Class`. This completes the work started in 778a01943b. Closes gh-23335 --- .../beans/factory/BeanFactoryUtils.java | 40 ++++++++++++++++++- .../beans/factory/ListableBeanFactory.java | 34 ++++++++++++++++ .../support/DefaultListableBeanFactory.java | 9 ++++- .../support/StaticListableBeanFactory.java | 34 +++++++++------- .../DefaultListableBeanFactoryTests.java | 2 + .../support/AbstractApplicationContext.java | 6 +++ .../setup/StubWebApplicationContext.java | 5 +++ 7 files changed, 113 insertions(+), 17 deletions(-) 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 1b5682d25d..dfc60e36c4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -172,6 +172,44 @@ public abstract class BeanFactoryUtils { return result; } + /** + * Get all bean names for the given type, including those defined in ancestor + * factories. Will return unique names in case of overridden bean definitions. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" + * flag is set, which means that FactoryBeans will get initialized. If the + * object created by the FactoryBean doesn't match, the raw FactoryBean itself + * will be matched against the type. If "allowEagerInit" is not set, + * only raw FactoryBeans will be checked (which doesn't require initialization + * of each FactoryBean). + * @param lbf the bean factory + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @param type the type that beans must match (as a {@code ResolvableType}) + * @return the array of matching bean names, or an empty array if none + * @since 5.2 + * @see ListableBeanFactory#getBeanNamesForType(ResolvableType, boolean, boolean) + */ + public static String[] beanNamesForTypeIncludingAncestors( + ListableBeanFactory lbf, ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { + + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + String[] parentResult = beanNamesForTypeIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit); + result = mergeNamesWithParent(result, parentResult, hbf); + } + } + return result; + } + /** * Get all bean names for the given type, including those defined in ancestor * factories. Will return unique names in case of overridden bean definitions. 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 0fc8d08686..09f0f5dade 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 @@ -116,6 +116,40 @@ public interface ListableBeanFactory extends BeanFactory { */ String[] getBeanNamesForType(ResolvableType type); + /** + * Return the names of beans matching the given type (including subclasses), + * judging from either bean definitions or the value of {@code getObjectType} + * in the case of FactoryBeans. + *

NOTE: This method introspects top-level beans only. It does not + * check nested beans which might match the specified type as well. + *

Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set, + * which means that FactoryBeans will get initialized. If the object created by the + * FactoryBean doesn't match, the raw FactoryBean itself will be matched against the + * type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked + * (which doesn't require initialization of each FactoryBean). + *

Does not consider any hierarchy this factory may participate in. + * Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors} + * to include beans in ancestor factories too. + *

Note: Does not ignore singleton beans that have been registered + * by other means than bean definitions. + *

Bean names returned by this method should always return bean names in the + * order of definition in the backend configuration, as far as possible. + * @param type the generically typed class or interface to match + * @param includeNonSingletons whether to include prototype or scoped beans too + * or just singletons (also applies to FactoryBeans) + * @param allowEagerInit whether to initialize lazy-init singletons and + * objects created by FactoryBeans (or by factory methods with a + * "factory-bean" reference) for the type check. Note that FactoryBeans need to be + * eagerly initialized to determine their type: So be aware that passing in "true" + * for this flag will initialize FactoryBeans and "factory-bean" references. + * @return the names of beans (or objects created by FactoryBeans) matching + * the given object type (including subclasses), or an empty array if none + * @since 5.2 + * @see FactoryBean#getObjectType + * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, ResolvableType, boolean, boolean) + */ + String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit); + /** * Return the names of beans matching the given type (including subclasses), * judging from either bean definitions or the value of {@code getObjectType} 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 8a6052bc76..c94ae2cfde 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 @@ -463,12 +463,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public String[] getBeanNamesForType(ResolvableType type) { + return getBeanNamesForType(type, true, true); + } + + @Override + public String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { Class resolved = type.resolve(); if (resolved != null && !type.hasGenerics()) { - return getBeanNamesForType(resolved, true, true); + return getBeanNamesForType(resolved, includeNonSingletons, includeNonSingletons); } else { - return doGetBeanNamesForType(type, true, true); + return doGetBeanNamesForType(type, includeNonSingletons, includeNonSingletons); } } 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 2d894a6c30..ee364317e3 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-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -329,26 +329,32 @@ public class StaticListableBeanFactory implements ListableBeanFactory { @Override public String[] getBeanNamesForType(@Nullable ResolvableType type) { - boolean isFactoryType = false; - if (type != null) { - Class resolved = type.resolve(); - if (resolved != null && FactoryBean.class.isAssignableFrom(resolved)) { - isFactoryType = true; - } - } + return getBeanNamesForType(type, true, true); + } + + + @Override + public String[] getBeanNamesForType(@Nullable ResolvableType type, + boolean includeNonSingletons, boolean allowEagerInit) { + + Class resolved = (type != null ? type.resolve() : null); + boolean isFactoryType = resolved != null && FactoryBean.class.isAssignableFrom(resolved); List matches = new ArrayList<>(); + for (Map.Entry entry : this.beans.entrySet()) { - String name = entry.getKey(); + String beanName = entry.getKey(); Object beanInstance = entry.getValue(); if (beanInstance instanceof FactoryBean && !isFactoryType) { - Class objectType = ((FactoryBean) beanInstance).getObjectType(); - if (objectType != null && (type == null || type.isAssignableFrom(objectType))) { - matches.add(name); + FactoryBean factoryBean = (FactoryBean) beanInstance; + Class objectType = factoryBean.getObjectType(); + if ((includeNonSingletons || factoryBean.isSingleton()) && + objectType != null && (type == null || type.isAssignableFrom(objectType))) { + matches.add(beanName); } } else { if (type == null || type.isInstance(beanInstance)) { - matches.add(name); + matches.add(beanName); } } } @@ -362,7 +368,7 @@ public class StaticListableBeanFactory implements ListableBeanFactory { @Override public String[] getBeanNamesForType(@Nullable Class type, boolean includeNonSingletons, boolean allowEagerInit) { - return getBeanNamesForType(ResolvableType.forClass(type)); + return getBeanNamesForType(ResolvableType.forClass(type), includeNonSingletons, allowEagerInit); } @Override diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index e245650110..e23dc6b243 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -1751,6 +1751,8 @@ public class DefaultListableBeanFactoryTests { assertThat(lbf.getBeanNamesForType(ConstructorDependencyFactoryBean.class).length).isEqualTo(1); assertThat(lbf.getBeanNamesForType(ResolvableType.forClassWithGenerics(FactoryBean.class, Object.class)).length).isEqualTo(1); assertThat(lbf.getBeanNamesForType(ResolvableType.forClassWithGenerics(FactoryBean.class, String.class)).length).isEqualTo(0); + assertThat(lbf.getBeanNamesForType(ResolvableType.forClassWithGenerics(FactoryBean.class, Object.class), true, true).length).isEqualTo(1); + assertThat(lbf.getBeanNamesForType(ResolvableType.forClassWithGenerics(FactoryBean.class, String.class), true, true).length).isEqualTo(0); } private RootBeanDefinition createConstructorDependencyBeanDefinition(int age) { 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 bcb11ef4b6..aa46bb8653 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 @@ -1208,6 +1208,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return getBeanFactory().getBeanNamesForType(type); } + @Override + public String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { + assertBeanFactoryActive(); + return getBeanFactory().getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + } + @Override public String[] getBeanNamesForType(@Nullable Class type) { assertBeanFactoryActive(); 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 53c0298dbf..e86bd75ed5 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 @@ -251,6 +251,11 @@ class StubWebApplicationContext implements WebApplicationContext { return this.beanFactory.getBeanNamesForType(type); } + @Override + public String[] getBeanNamesForType(@Nullable ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { + return this.beanFactory.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + } + @Override public String[] getBeanNamesForType(@Nullable Class type) { return this.beanFactory.getBeanNamesForType(type); -- GitLab