Spring中的beanName
在Spring中每一個注冊到容器中的Bean都有自己的名字(至少一個)挑社,可能不止一個(別名)。對于未明確指定name的Bean,Spring會自動為其生成一個名字。而對于在xml中配置的Bean和使用諸如Service、Component等注解標識的Bean与殃,Spring為其生成名字的方式并不相同单山,下面我們一一分析。
核心接口
核心接口.png
BeanNameGenerator接口定義如下
public interface BeanNameGenerator {
/**
* Generate a bean name for the given bean definition.
* @param definition the bean definition to generate a name for
* @param registry the bean definition registry that the given definition
* is supposed to be registered with
* @return the generated bean name
*/
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}
BeanNameGenerator是生成beanName的頂級接口幅疼,而它有兩個實現(xiàn)類米奸,圖中左側的DefaultBeanNameGenerator是給XML配置中Bean使用的,圖中右側的AnnotationBeanNameGenerator則是給通過注解定義的Bean使用的爽篷。
XML配置
在此不贅述XML文件中Bean的解析過程悴晰,直接來看DefaultBeanNameGenerator,其調用鏈路為
DefaultBeanNameGenerator#generateBeanName—>BeanDefinitionReaderUtils#generateBeanName
最后這個方法的定義如下
public static String generateBeanName(
BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
throws BeanDefinitionStoreException {
// 先拿類名賦值
String generatedBeanName = definition.getBeanClassName();
if (generatedBeanName == null) {
if (definition.getParentName() != null) {
generatedBeanName = definition.getParentName() + "$child";
}
else if (definition.getFactoryBeanName() != null) {
generatedBeanName = definition.getFactoryBeanName() + "$created";
}
}
if (!StringUtils.hasText(generatedBeanName)) {
throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
}
String id = generatedBeanName;
if (isInnerBean) {
// 內部bean逐工,在少數(shù)情況下走該分支铡溪,例如使用key-ref等標簽時
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
else {
// Top-level bean: use plain class name.
// Increase counter until the id is unique.
// 為了保證id唯一,在其后加數(shù)字
int counter = -1;
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
}
return id;
}
注釋都寫在了上面泪喊,邏輯很簡單:類名+“#”+數(shù)字
注解配置
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// 如果注解的value指定了beanName棕硫,則使用該值
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
// 如果沒有指定value,則為其生成beanName
return buildDefaultBeanName(definition, registry);
}
繼續(xù)追蹤
protected String buildDefaultBeanName(BeanDefinition definition) {
String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
return Introspector.decapitalize(shortClassName);
}
Introspector.decapitalize的代碼如下
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
通過上面兩段代碼可以看出邏輯如下
- 取短類名袒啼,即不包含包路徑的類名哈扮,例如com.test.Student的短類名為Student,這點跟XML配置中取全類名不一樣
- 如果短類名長度大于1蚓再,且第一個和第二個字符為大寫滑肉,則直接返回短類名,也就是說假設類為com.test.STudent摘仅,則beanName為STudent
- 其他情況下將短類名首字符小寫后返回靶庙,假設類為com.test.Student,則beanName為student
驗證
由于只為了驗證beanName娃属,簡單起見惶洲,Bean類中都為空
People類
@Component
public class Pepole {
}
TNtt類
@Service
public class TNttt {
}
TestPepole類
public class TestPepole {
}
TNTt類
public class TNTt {
}
其中TestPepole和TNTt通過XML配置
<bean class="com.hust.TestPepole"></bean>
<bean class="com.hust.TNTt"></bean>
測試主類
public class App {
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Pepole.class)));
System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
TNttt.class)));
System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
TestPepole.class)));
System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
TNTt.class)));
}
}
輸出結果
["pepole"]
["TNttt"]
["com.hust.TestPepole#0"]
["com.hust.TNTt#0"]
總結
- 在不指定beanName的情況下,Spring會自動為注冊的Bean生成一個唯一的beanName
- 通過注解注冊的Bean和XML注冊的Bean膳犹,Spring為其生成默認beanName的機制不一樣
- 不要盲目覺得通過注解注冊的Bean,Spring為其生成beanName就是將短類名的首字母小寫签则,當短類名的首字符和第二個字符均大寫時须床,beanName就是短類名