001/*
002 * Copyright 2002-2016 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;
020
021import org.springframework.beans.BeansException;
022import org.springframework.beans.factory.BeanFactory;
023import org.springframework.beans.factory.BeanFactoryUtils;
024import org.springframework.beans.factory.NoSuchBeanDefinitionException;
025import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
026import org.springframework.beans.factory.config.BeanDefinition;
027import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
028import org.springframework.beans.factory.support.AbstractBeanDefinition;
029import org.springframework.beans.factory.support.AutowireCandidateQualifier;
030import org.springframework.beans.factory.support.RootBeanDefinition;
031import org.springframework.core.annotation.AnnotationUtils;
032import org.springframework.util.Assert;
033import org.springframework.util.ObjectUtils;
034
035/**
036 * Convenience methods performing bean lookups related to annotations, for example
037 * Spring's {@link Qualifier @Qualifier} annotation.
038 *
039 * @author Juergen Hoeller
040 * @author Chris Beams
041 * @since 3.1.2
042 * @see BeanFactoryUtils
043 */
044public abstract class BeanFactoryAnnotationUtils {
045
046        /**
047         * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a
048         * qualifier (e.g. via {@code <qualifier>} or {@code @Qualifier}) matching the given
049         * qualifier, or having a bean name matching the given qualifier.
050         * @param beanFactory the BeanFactory to get the target bean from
051         * @param beanType the type of bean to retrieve
052         * @param qualifier the qualifier for selecting between multiple bean matches
053         * @return the matching bean of type {@code T} (never {@code null})
054         * @throws NoUniqueBeanDefinitionException if multiple matching beans of type {@code T} found
055         * @throws NoSuchBeanDefinitionException if no matching bean of type {@code T} found
056         * @throws BeansException if the bean could not be created
057         * @see BeanFactory#getBean(Class)
058         */
059        public static <T> T qualifiedBeanOfType(BeanFactory beanFactory, Class<T> beanType, String qualifier)
060                        throws BeansException {
061
062                Assert.notNull(beanFactory, "BeanFactory must not be null");
063
064                if (beanFactory instanceof ConfigurableListableBeanFactory) {
065                        // Full qualifier matching supported.
066                        return qualifiedBeanOfType((ConfigurableListableBeanFactory) beanFactory, beanType, qualifier);
067                }
068                else if (beanFactory.containsBean(qualifier)) {
069                        // Fallback: target bean at least found by bean name.
070                        return beanFactory.getBean(qualifier, beanType);
071                }
072                else {
073                        throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
074                                        " bean found for bean name '" + qualifier +
075                                        "'! (Note: Qualifier matching not supported because given " +
076                                        "BeanFactory does not implement ConfigurableListableBeanFactory.)");
077                }
078        }
079
080        /**
081         * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
082         * (e.g. {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
083         * @param bf the BeanFactory to get the target bean from
084         * @param beanType the type of bean to retrieve
085         * @param qualifier the qualifier for selecting between multiple bean matches
086         * @return the matching bean of type {@code T} (never {@code null})
087         */
088        private static <T> T qualifiedBeanOfType(ConfigurableListableBeanFactory bf, Class<T> beanType, String qualifier) {
089                String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
090                String matchingBean = null;
091                for (String beanName : candidateBeans) {
092                        if (isQualifierMatch(qualifier, beanName, bf)) {
093                                if (matchingBean != null) {
094                                        throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
095                                }
096                                matchingBean = beanName;
097                        }
098                }
099                if (matchingBean != null) {
100                        return bf.getBean(matchingBean, beanType);
101                }
102                else if (bf.containsBean(qualifier)) {
103                        // Fallback: target bean at least found by bean name - probably a manually registered singleton.
104                        return bf.getBean(qualifier, beanType);
105                }
106                else {
107                        throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
108                                        " bean found for qualifier '" + qualifier + "' - neither qualifier match nor bean name match!");
109                }
110        }
111
112        /**
113         * Check whether the named bean declares a qualifier of the given name.
114         * @param qualifier the qualifier to match
115         * @param beanName the name of the candidate bean
116         * @param bf the {@code BeanFactory} from which to retrieve the named bean
117         * @return {@code true} if either the bean definition (in the XML case)
118         * or the bean's factory method (in the {@code @Bean} case) defines a matching
119         * qualifier value (through {@code <qualifier>} or {@code @Qualifier})
120         */
121        private static boolean isQualifierMatch(String qualifier, String beanName, ConfigurableListableBeanFactory bf) {
122                if (bf.containsBean(beanName)) {
123                        try {
124                                BeanDefinition bd = bf.getMergedBeanDefinition(beanName);
125                                // Explicit qualifier metadata on bean definition? (typically in XML definition)
126                                if (bd instanceof AbstractBeanDefinition) {
127                                        AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
128                                        AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName());
129                                        if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) ||
130                                                        qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) {
131                                                return true;
132                                        }
133                                }
134                                // Corresponding qualifier on factory method? (typically in configuration class)
135                                if (bd instanceof RootBeanDefinition) {
136                                        Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod();
137                                        if (factoryMethod != null) {
138                                                Qualifier targetAnnotation = AnnotationUtils.getAnnotation(factoryMethod, Qualifier.class);
139                                                if (targetAnnotation != null) {
140                                                        return qualifier.equals(targetAnnotation.value());
141                                                }
142                                        }
143                                }
144                                // Corresponding qualifier on bean implementation class? (for custom user types)
145                                Class<?> beanType = bf.getType(beanName);
146                                if (beanType != null) {
147                                        Qualifier targetAnnotation = AnnotationUtils.getAnnotation(beanType, Qualifier.class);
148                                        if (targetAnnotation != null) {
149                                                return qualifier.equals(targetAnnotation.value());
150                                        }
151                                }
152                        }
153                        catch (NoSuchBeanDefinitionException ex) {
154                                // Ignore - can't compare qualifiers for a manually registered singleton object
155                        }
156                }
157                return false;
158        }
159
160}