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 -> 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}