001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.factory.annotation;
018
019import java.lang.reflect.Method;
020import java.util.LinkedHashMap;
021import java.util.Map;
022import java.util.function.Predicate;
023
024import org.springframework.beans.BeansException;
025import org.springframework.beans.factory.BeanFactory;
026import org.springframework.beans.factory.BeanFactoryUtils;
027import org.springframework.beans.factory.ListableBeanFactory;
028import org.springframework.beans.factory.NoSuchBeanDefinitionException;
029import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
030import org.springframework.beans.factory.config.BeanDefinition;
031import org.springframework.beans.factory.config.ConfigurableBeanFactory;
032import org.springframework.beans.factory.support.AbstractBeanDefinition;
033import org.springframework.beans.factory.support.AutowireCandidateQualifier;
034import org.springframework.beans.factory.support.RootBeanDefinition;
035import org.springframework.core.annotation.AnnotationUtils;
036import org.springframework.lang.Nullable;
037import org.springframework.util.Assert;
038
039/**
040 * Convenience methods performing bean lookups related to Spring-specific annotations,
041 * for example Spring's {@link Qualifier @Qualifier} annotation.
042 *
043 * @author Juergen Hoeller
044 * @author Chris Beams
045 * @since 3.1.2
046 * @see BeanFactoryUtils
047 */
048public abstract class BeanFactoryAnnotationUtils {
049
050        /**
051         * Retrieve all bean of type {@code T} from the given {@code BeanFactory} declaring a
052         * qualifier (e.g. via {@code <qualifier>} or {@code @Qualifier}) matching the given
053         * qualifier, or having a bean name matching the given qualifier.
054         * @param beanFactory the factory to get the target beans from (also searching ancestors)
055         * @param beanType the type of beans to retrieve
056         * @param qualifier the qualifier for selecting among all type matches
057         * @return the matching beans of type {@code T}
058         * @throws BeansException if any of the matching beans could not be created
059         * @since 5.1.1
060         * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
061         */
062        public static <T> Map<String, T> qualifiedBeansOfType(
063                        ListableBeanFactory beanFactory, Class<T> beanType, String qualifier) throws BeansException {
064
065                String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType);
066                Map<String, T> result = new LinkedHashMap<>(4);
067                for (String beanName : candidateBeans) {
068                        if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) {
069                                result.put(beanName, beanFactory.getBean(beanName, beanType));
070                        }
071                }
072                return result;
073        }
074
075        /**
076         * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a
077         * qualifier (e.g. via {@code <qualifier>} or {@code @Qualifier}) matching the given
078         * qualifier, or having a bean name matching the given qualifier.
079         * @param beanFactory the factory to get the target bean from (also searching ancestors)
080         * @param beanType the type of bean to retrieve
081         * @param qualifier the qualifier for selecting between multiple bean matches
082         * @return the matching bean of type {@code T} (never {@code null})
083         * @throws NoUniqueBeanDefinitionException if multiple matching beans of type {@code T} found
084         * @throws NoSuchBeanDefinitionException if no matching bean of type {@code T} found
085         * @throws BeansException if the bean could not be created
086         * @see BeanFactoryUtils#beanOfTypeIncludingAncestors(ListableBeanFactory, Class)
087         */
088        public static <T> T qualifiedBeanOfType(BeanFactory beanFactory, Class<T> beanType, String qualifier)
089                        throws BeansException {
090
091                Assert.notNull(beanFactory, "BeanFactory must not be null");
092
093                if (beanFactory instanceof ListableBeanFactory) {
094                        // Full qualifier matching supported.
095                        return qualifiedBeanOfType((ListableBeanFactory) beanFactory, beanType, qualifier);
096                }
097                else if (beanFactory.containsBean(qualifier)) {
098                        // Fallback: target bean at least found by bean name.
099                        return beanFactory.getBean(qualifier, beanType);
100                }
101                else {
102                        throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
103                                        " bean found for bean name '" + qualifier +
104                                        "'! (Note: Qualifier matching not supported because given " +
105                                        "BeanFactory does not implement ConfigurableListableBeanFactory.)");
106                }
107        }
108
109        /**
110         * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
111         * (e.g. {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
112         * @param bf the factory to get the target bean from
113         * @param beanType the type of bean to retrieve
114         * @param qualifier the qualifier for selecting between multiple bean matches
115         * @return the matching bean of type {@code T} (never {@code null})
116         */
117        private static <T> T qualifiedBeanOfType(ListableBeanFactory bf, Class<T> beanType, String qualifier) {
118                String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
119                String matchingBean = null;
120                for (String beanName : candidateBeans) {
121                        if (isQualifierMatch(qualifier::equals, beanName, bf)) {
122                                if (matchingBean != null) {
123                                        throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
124                                }
125                                matchingBean = beanName;
126                        }
127                }
128                if (matchingBean != null) {
129                        return bf.getBean(matchingBean, beanType);
130                }
131                else if (bf.containsBean(qualifier)) {
132                        // Fallback: target bean at least found by bean name - probably a manually registered singleton.
133                        return bf.getBean(qualifier, beanType);
134                }
135                else {
136                        throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
137                                        " bean found for qualifier '" + qualifier + "' - neither qualifier match nor bean name match!");
138                }
139        }
140
141        /**
142         * Check whether the named bean declares a qualifier of the given name.
143         * @param qualifier the qualifier to match
144         * @param beanName the name of the candidate bean
145         * @param beanFactory the factory from which to retrieve the named bean
146         * @return {@code true} if either the bean definition (in the XML case)
147         * or the bean's factory method (in the {@code @Bean} case) defines a matching
148         * qualifier value (through {@code <qualifier>} or {@code @Qualifier})
149         * @since 5.0
150         */
151        public static boolean isQualifierMatch(
152                        Predicate<String> qualifier, String beanName, @Nullable BeanFactory beanFactory) {
153
154                // Try quick bean name or alias match first...
155                if (qualifier.test(beanName)) {
156                        return true;
157                }
158                if (beanFactory != null) {
159                        for (String alias : beanFactory.getAliases(beanName)) {
160                                if (qualifier.test(alias)) {
161                                        return true;
162                                }
163                        }
164                        try {
165                                Class<?> beanType = beanFactory.getType(beanName);
166                                if (beanFactory instanceof ConfigurableBeanFactory) {
167                                        BeanDefinition bd = ((ConfigurableBeanFactory) beanFactory).getMergedBeanDefinition(beanName);
168                                        // Explicit qualifier metadata on bean definition? (typically in XML definition)
169                                        if (bd instanceof AbstractBeanDefinition) {
170                                                AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
171                                                AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName());
172                                                if (candidate != null) {
173                                                        Object value = candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY);
174                                                        if (value != null && qualifier.test(value.toString())) {
175                                                                return true;
176                                                        }
177                                                }
178                                        }
179                                        // Corresponding qualifier on factory method? (typically in configuration class)
180                                        if (bd instanceof RootBeanDefinition) {
181                                                Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod();
182                                                if (factoryMethod != null) {
183                                                        Qualifier targetAnnotation = AnnotationUtils.getAnnotation(factoryMethod, Qualifier.class);
184                                                        if (targetAnnotation != null) {
185                                                                return qualifier.test(targetAnnotation.value());
186                                                        }
187                                                }
188                                        }
189                                }
190                                // Corresponding qualifier on bean implementation class? (for custom user types)
191                                if (beanType != null) {
192                                        Qualifier targetAnnotation = AnnotationUtils.getAnnotation(beanType, Qualifier.class);
193                                        if (targetAnnotation != null) {
194                                                return qualifier.test(targetAnnotation.value());
195                                        }
196                                }
197                        }
198                        catch (NoSuchBeanDefinitionException ex) {
199                                // Ignore - can't compare qualifiers for a manually registered singleton object
200                        }
201                }
202                return false;
203        }
204
205}