Spring中的預設beanName
在Spring中每一個註冊到容器中的Bean都有自己的名字(至少一個),可能不止一個(別名)。對於未明確指定name的Bean,Spring會自動為其生成一個名字。而對於在xml中配置的Bean和使用諸如Service、Component等註解標識的Bean,Spring為其生成名字的方式並不相同,下面我們一一分析。
核心介面
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的頂級介面,而它有兩個實現類,圖中左側的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,在少數情況下走該分支,例如使用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唯一,在其後加數字
int counter = -1;
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
}
return id;
}
註釋都寫在了上面,邏輯很簡單:類名+“#”+數字
註解配置
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);
}
繼續追蹤
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就是短類名
相關文章
- Spring Security 5中的預設密碼編碼器Spring密碼
- Spring預設的Bean的scopeSpringBean
- oracle中spfile的預設位置Oracle
- 【Spring面試題】Spring 為啥預設把bean設計成單例的?Spring面試題Bean單例
- Spring Boot 2 中的預設日誌管理與 Logback 配置詳解Spring Boot
- 【Spring進階指南】Spring 為啥預設把bean設計成單例的?SpringBean單例
- Python中的預設引數Python
- 【雜談】Spring Boot 預設支援的併發量Spring Boot
- input元素預設選中設定
- Spring框架中的設計模式(二)Spring框架設計模式
- Spring框架中的設計模式(一)Spring框架設計模式
- 如何更改Joomla中的預設語言OOM
- Jmeter中預設語言的顯示JMeter
- Python中的預設引數值Python
- oracle中的預設帳戶詳解Oracle
- Oracle中NLS_LANG的預設值Oracle
- 【Spring】AOP的代理預設是Jdk還是Cglib?SpringJDKCGLib
- 取消element中input type=number中的預設箭頭
- idea中設定maven預設位置IdeaMaven
- vue中select的使用以及select設定預設選中Vue
- 介面中的預設方法與靜態方法
- Recoil 中預設值的正確處理
- Spring AOP——Spring 中面向切面程式設計Spring程式設計
- 設定select下拉選單的預設選中項
- 在Docker中,如何更改Docker的預設儲存設定?Docker
- spring中的原始碼級的後設資料Spring原始碼
- MYSQL中怎樣設列的預設值為Now()的介紹MySql
- Spring中的9種設計模式彙總Spring設計模式
- MySQL5.7中的sql_mode預設值MySql
- 查詢oracle中具有預設口令的賬戶Oracle
- 如何修改meclipse中的預設瀏覽器Eclipse瀏覽器
- 如何檢視Vue CLI中webpack的預設配置VueWeb
- Spring原始碼解析—— IOC預設標籤解析(下)Spring原始碼
- iOS開發中利用runtime設定UITextView的預設文字iOSUITextView
- 在程式碼中設定IE9的預設文件模式IE9模式
- Spring中如何使用設計模式Spring設計模式
- Python中如何給字典設定預設值Python
- MYSQL中給時間列設定預設值MySql