diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java index 8a9b4b6414..d5ec4f2b97 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -17,16 +17,18 @@ package org.springframework.beans.factory.annotation; import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.function.Predicate; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -35,8 +37,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Convenience methods performing bean lookups related to annotations, for example - * Spring's {@link Qualifier @Qualifier} annotation. + * Convenience methods performing bean lookups related to Spring-specific annotations, + * for example Spring's {@link Qualifier @Qualifier} annotation. * * @author Juergen Hoeller * @author Chris Beams @@ -45,27 +47,52 @@ import org.springframework.util.Assert; */ public abstract class BeanFactoryAnnotationUtils { + /** + * Retrieve all bean of type {@code T} from the given {@code BeanFactory} declaring a + * qualifier (e.g. via {@code } or {@code @Qualifier}) matching the given + * qualifier, or having a bean name matching the given qualifier. + * @param beanFactory the factory to get the target beans from (also searching ancestors) + * @param beanType the type of beans to retrieve + * @param qualifier the qualifier for selecting among all type matches + * @return the matching beans of type {@code T} + * @throws BeansException if any of the matching beans could not be created + * @since 5.1.1 + * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class) + */ + public static Map qualifiedBeansOfType( + ListableBeanFactory beanFactory, Class beanType, String qualifier) throws BeansException { + + String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType); + Map result = new LinkedHashMap<>(4); + for (String beanName : candidateBeans) { + if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) { + result.put(beanName, beanFactory.getBean(beanName, beanType)); + } + } + return result; + } + /** * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a * qualifier (e.g. via {@code } or {@code @Qualifier}) matching the given * qualifier, or having a bean name matching the given qualifier. - * @param beanFactory the BeanFactory to get the target bean from + * @param beanFactory the factory to get the target bean from (also searching ancestors) * @param beanType the type of bean to retrieve * @param qualifier the qualifier for selecting between multiple bean matches * @return the matching bean of type {@code T} (never {@code null}) * @throws NoUniqueBeanDefinitionException if multiple matching beans of type {@code T} found * @throws NoSuchBeanDefinitionException if no matching bean of type {@code T} found * @throws BeansException if the bean could not be created - * @see BeanFactory#getBean(Class) + * @see BeanFactoryUtils#beanOfTypeIncludingAncestors(ListableBeanFactory, Class) */ public static T qualifiedBeanOfType(BeanFactory beanFactory, Class beanType, String qualifier) throws BeansException { Assert.notNull(beanFactory, "BeanFactory must not be null"); - if (beanFactory instanceof ConfigurableListableBeanFactory) { + if (beanFactory instanceof ListableBeanFactory) { // Full qualifier matching supported. - return qualifiedBeanOfType((ConfigurableListableBeanFactory) beanFactory, beanType, qualifier); + return qualifiedBeanOfType((ListableBeanFactory) beanFactory, beanType, qualifier); } else if (beanFactory.containsBean(qualifier)) { // Fallback: target bean at least found by bean name. @@ -82,12 +109,12 @@ public abstract class BeanFactoryAnnotationUtils { /** * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier * (e.g. {@code } or {@code @Qualifier}) matching the given qualifier). - * @param bf the BeanFactory to get the target bean from + * @param bf the factory to get the target bean from * @param beanType the type of bean to retrieve * @param qualifier the qualifier for selecting between multiple bean matches * @return the matching bean of type {@code T} (never {@code null}) */ - private static T qualifiedBeanOfType(ConfigurableListableBeanFactory bf, Class beanType, String qualifier) { + private static T qualifiedBeanOfType(ListableBeanFactory bf, Class beanType, String qualifier) { String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType); String matchingBean = null; for (String beanName : candidateBeans) { @@ -115,14 +142,14 @@ public abstract class BeanFactoryAnnotationUtils { * Check whether the named bean declares a qualifier of the given name. * @param qualifier the qualifier to match * @param beanName the name of the candidate bean - * @param beanFactory the {@code BeanFactory} from which to retrieve the named bean + * @param beanFactory the factory from which to retrieve the named bean * @return {@code true} if either the bean definition (in the XML case) * or the bean's factory method (in the {@code @Bean} case) defines a matching * qualifier value (through {@code } or {@code @Qualifier}) * @since 5.0 */ - public static boolean isQualifierMatch(Predicate qualifier, String beanName, - @Nullable BeanFactory beanFactory) { + public static boolean isQualifierMatch( + Predicate qualifier, String beanName, @Nullable BeanFactory beanFactory) { // Try quick bean name or alias match first... if (qualifier.test(beanName)) { @@ -135,6 +162,7 @@ public abstract class BeanFactoryAnnotationUtils { } } try { + Class beanType = beanFactory.getType(beanName); if (beanFactory instanceof ConfigurableBeanFactory) { BeanDefinition bd = ((ConfigurableBeanFactory) beanFactory).getMergedBeanDefinition(beanName); // Explicit qualifier metadata on bean definition? (typically in XML definition) @@ -160,7 +188,6 @@ public abstract class BeanFactoryAnnotationUtils { } } // Corresponding qualifier on bean implementation class? (for custom user types) - Class beanType = beanFactory.getType(beanName); if (beanType != null) { Qualifier targetAnnotation = AnnotationUtils.getAnnotation(beanType, Qualifier.class); if (targetAnnotation != null) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index f827fbf7fd..4ab5d5bf00 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -775,6 +775,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } } + if (uniqueCandidate != null) { + synchronized (mbd.constructorArgumentLock) { + mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate; + } + } if (commonType == null) { return null; } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java index 94d0d98863..86461a204f 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 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. @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -77,10 +78,27 @@ public class BeanMethodQualificationTests { } @Test - public void testCustom() { + public void testCustomWithLazyResolution() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(CustomConfig.class, CustomPojo.class); assertFalse(ctx.getBeanFactory().containsSingleton("testBean1")); + assertFalse(ctx.getBeanFactory().containsSingleton("testBean2")); + assertTrue(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"), + "testBean2", ctx.getDefaultListableBeanFactory())); + CustomPojo pojo = ctx.getBean(CustomPojo.class); + assertThat(pojo.testBean.getName(), equalTo("interesting")); + } + + @Test + public void testCustomWithEarlyResolution() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(CustomConfig.class, CustomPojo.class); + ctx.refresh(); + assertFalse(ctx.getBeanFactory().containsSingleton("testBean1")); + assertFalse(ctx.getBeanFactory().containsSingleton("testBean2")); + ctx.getBean("testBean2"); + assertTrue(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"), + "testBean2", ctx.getDefaultListableBeanFactory())); CustomPojo pojo = ctx.getBean(CustomPojo.class); assertThat(pojo.testBean.getName(), equalTo("interesting")); } @@ -94,6 +112,7 @@ public class BeanMethodQualificationTests { ctx.registerBeanDefinition("customPojo", customPojo); ctx.refresh(); assertFalse(ctx.getBeanFactory().containsSingleton("testBean1")); + assertFalse(ctx.getBeanFactory().containsSingleton("testBean2")); CustomPojo pojo = ctx.getBean(CustomPojo.class); assertThat(pojo.testBean.getName(), equalTo("interesting")); } @@ -171,7 +190,7 @@ public class BeanMethodQualificationTests { return new TestBean("interesting"); } - @Bean @Qualifier("boring") + @Bean @Qualifier("boring") @Lazy public TestBean testBean2() { return new TestBean("boring"); }