001/*
002 * Copyright 2002-2020 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.context.annotation;
018
019import java.beans.Introspector;
020import java.util.Collections;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
026import org.springframework.beans.factory.config.BeanDefinition;
027import org.springframework.beans.factory.support.BeanDefinitionRegistry;
028import org.springframework.beans.factory.support.BeanNameGenerator;
029import org.springframework.core.annotation.AnnotationAttributes;
030import org.springframework.core.type.AnnotationMetadata;
031import org.springframework.lang.Nullable;
032import org.springframework.util.Assert;
033import org.springframework.util.ClassUtils;
034import org.springframework.util.StringUtils;
035
036/**
037 * {@link BeanNameGenerator} implementation for bean classes annotated with the
038 * {@link org.springframework.stereotype.Component @Component} annotation or
039 * with another annotation that is itself annotated with {@code @Component} as a
040 * meta-annotation. For example, Spring's stereotype annotations (such as
041 * {@link org.springframework.stereotype.Repository @Repository}) are
042 * themselves annotated with {@code @Component}.
043 *
044 * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
045 * JSR-330's {@link javax.inject.Named} annotations, if available. Note that
046 * Spring component annotations always override such standard annotations.
047 *
048 * <p>If the annotation's value doesn't indicate a bean name, an appropriate
049 * name will be built based on the short name of the class (with the first
050 * letter lower-cased). For example:
051 *
052 * <pre class="code">com.xyz.FooServiceImpl -&gt; fooServiceImpl</pre>
053 *
054 * @author Juergen Hoeller
055 * @author Mark Fisher
056 * @since 2.5
057 * @see org.springframework.stereotype.Component#value()
058 * @see org.springframework.stereotype.Repository#value()
059 * @see org.springframework.stereotype.Service#value()
060 * @see org.springframework.stereotype.Controller#value()
061 * @see javax.inject.Named#value()
062 * @see FullyQualifiedAnnotationBeanNameGenerator
063 */
064public class AnnotationBeanNameGenerator implements BeanNameGenerator {
065
066        /**
067         * A convenient constant for a default {@code AnnotationBeanNameGenerator} instance,
068         * as used for component scanning purposes.
069         * @since 5.2
070         */
071        public static final AnnotationBeanNameGenerator INSTANCE = new AnnotationBeanNameGenerator();
072
073        private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
074
075        private final Map<String, Set<String>> metaAnnotationTypesCache = new ConcurrentHashMap<>();
076
077
078        @Override
079        public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
080                if (definition instanceof AnnotatedBeanDefinition) {
081                        String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
082                        if (StringUtils.hasText(beanName)) {
083                                // Explicit bean name found.
084                                return beanName;
085                        }
086                }
087                // Fallback: generate a unique default bean name.
088                return buildDefaultBeanName(definition, registry);
089        }
090
091        /**
092         * Derive a bean name from one of the annotations on the class.
093         * @param annotatedDef the annotation-aware bean definition
094         * @return the bean name, or {@code null} if none is found
095         */
096        @Nullable
097        protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
098                AnnotationMetadata amd = annotatedDef.getMetadata();
099                Set<String> types = amd.getAnnotationTypes();
100                String beanName = null;
101                for (String type : types) {
102                        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
103                        if (attributes != null) {
104                                Set<String> metaTypes = this.metaAnnotationTypesCache.computeIfAbsent(type, key -> {
105                                        Set<String> result = amd.getMetaAnnotationTypes(key);
106                                        return (result.isEmpty() ? Collections.emptySet() : result);
107                                });
108                                if (isStereotypeWithNameValue(type, metaTypes, attributes)) {
109                                        Object value = attributes.get("value");
110                                        if (value instanceof String) {
111                                                String strVal = (String) value;
112                                                if (StringUtils.hasLength(strVal)) {
113                                                        if (beanName != null && !strVal.equals(beanName)) {
114                                                                throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
115                                                                                "component names: '" + beanName + "' versus '" + strVal + "'");
116                                                        }
117                                                        beanName = strVal;
118                                                }
119                                        }
120                                }
121                        }
122                }
123                return beanName;
124        }
125
126        /**
127         * Check whether the given annotation is a stereotype that is allowed
128         * to suggest a component name through its annotation {@code value()}.
129         * @param annotationType the name of the annotation class to check
130         * @param metaAnnotationTypes the names of meta-annotations on the given annotation
131         * @param attributes the map of attributes for the given annotation
132         * @return whether the annotation qualifies as a stereotype with component name
133         */
134        protected boolean isStereotypeWithNameValue(String annotationType,
135                        Set<String> metaAnnotationTypes, @Nullable Map<String, Object> attributes) {
136
137                boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
138                                metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) ||
139                                annotationType.equals("javax.annotation.ManagedBean") ||
140                                annotationType.equals("javax.inject.Named");
141
142                return (isStereotype && attributes != null && attributes.containsKey("value"));
143        }
144
145        /**
146         * Derive a default bean name from the given bean definition.
147         * <p>The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}.
148         * @param definition the bean definition to build a bean name for
149         * @param registry the registry that the given bean definition is being registered with
150         * @return the default bean name (never {@code null})
151         */
152        protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
153                return buildDefaultBeanName(definition);
154        }
155
156        /**
157         * Derive a default bean name from the given bean definition.
158         * <p>The default implementation simply builds a decapitalized version
159         * of the short class name: e.g. "mypackage.MyJdbcDao" -> "myJdbcDao".
160         * <p>Note that inner classes will thus have names of the form
161         * "outerClassName.InnerClassName", which because of the period in the
162         * name may be an issue if you are autowiring by name.
163         * @param definition the bean definition to build a bean name for
164         * @return the default bean name (never {@code null})
165         */
166        protected String buildDefaultBeanName(BeanDefinition definition) {
167                String beanClassName = definition.getBeanClassName();
168                Assert.state(beanClassName != null, "No bean class name set");
169                String shortClassName = ClassUtils.getShortName(beanClassName);
170                return Introspector.decapitalize(shortClassName);
171        }
172
173}