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