View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.context.annotation;
18  
19  import java.beans.Introspector;
20  import java.util.Map;
21  import java.util.Set;
22  
23  import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
24  import org.springframework.beans.factory.config.BeanDefinition;
25  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
26  import org.springframework.beans.factory.support.BeanNameGenerator;
27  import org.springframework.core.annotation.AnnotationAttributes;
28  import org.springframework.core.type.AnnotationMetadata;
29  import org.springframework.util.ClassUtils;
30  import org.springframework.util.StringUtils;
31  
32  /**
33   * {@link org.springframework.beans.factory.support.BeanNameGenerator}
34   * implementation for bean classes annotated with the
35   * {@link org.springframework.stereotype.Component @Component} annotation
36   * or with another annotation that is itself annotated with
37   * {@link org.springframework.stereotype.Component @Component} as a
38   * meta-annotation. For example, Spring's stereotype annotations (such as
39   * {@link org.springframework.stereotype.Repository @Repository}) are
40   * themselves annotated with
41   * {@link org.springframework.stereotype.Component @Component}.
42   *
43   * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
44   * JSR-330's {@link javax.inject.Named} annotations, if available. Note that
45   * Spring component annotations always override such standard annotations.
46   *
47   * <p>If the annotation's value doesn't indicate a bean name, an appropriate
48   * name will be built based on the short name of the class (with the first
49   * letter lower-cased). For example:
50   *
51   * <pre class="code">com.xyz.FooServiceImpl -&gt; fooServiceImpl</pre>
52   *
53   * @author Juergen Hoeller
54   * @author Mark Fisher
55   * @since 2.5
56   * @see org.springframework.stereotype.Component#value()
57   * @see org.springframework.stereotype.Repository#value()
58   * @see org.springframework.stereotype.Service#value()
59   * @see org.springframework.stereotype.Controller#value()
60   * @see javax.inject.Named#value()
61   */
62  public class AnnotationBeanNameGenerator implements BeanNameGenerator {
63  
64  	private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
65  
66  
67  	@Override
68  	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
69  		if (definition instanceof AnnotatedBeanDefinition) {
70  			String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
71  			if (StringUtils.hasText(beanName)) {
72  				// Explicit bean name found.
73  				return beanName;
74  			}
75  		}
76  		// Fallback: generate a unique default bean name.
77  		return buildDefaultBeanName(definition, registry);
78  	}
79  
80  	/**
81  	 * Derive a bean name from one of the annotations on the class.
82  	 * @param annotatedDef the annotation-aware bean definition
83  	 * @return the bean name, or {@code null} if none is found
84  	 */
85  	protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
86  		AnnotationMetadata amd = annotatedDef.getMetadata();
87  		Set<String> types = amd.getAnnotationTypes();
88  		String beanName = null;
89  		for (String type : types) {
90  			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
91  			if (isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) {
92  				Object value = attributes.get("value");
93  				if (value instanceof String) {
94  					String strVal = (String) value;
95  					if (StringUtils.hasLength(strVal)) {
96  						if (beanName != null && !strVal.equals(beanName)) {
97  							throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
98  									"component names: '" + beanName + "' versus '" + strVal + "'");
99  						}
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 }