聊聊spring bean名稱命名的那些事兒

linyb極客之路發表於2021-06-24

前言

用了多年spring,一直想當然把spring預設的beanName當成是類名的首字母小寫,比如HelloService其beanName為helloService。直到有天對接了供方廠商的介面,他有個類形如ABService,於是用

getBean(“aBService”) 

的方式獲取bean,結果取到是null,一開始以為是ABservice沒注入,後面採用

getBean(ABService.class) 

能成功獲取到bean,說明ABService是有注入到IOC容器中,但是為啥用aBService獲取不到bean?於是就用如下程式碼段,列印出相應ABService對應的beanName

 applicationContext.getBeansOfType(ABService.class).forEach((beanName,bean)->{
            System.out.println(beanName + ":" + bean);
        });

列印出來的結果,如下

ABService:com.github.lybgeek.ABService@245b6b85

beanName竟然是ABService,這就和之前的想當然有出入。於是只好檢視原始碼

原始碼檢視

原始碼檢視有2種方式,本文的示例是springboot專案

方法一:從main方法直接除錯斷點

beanName.jpg
從圖可以看出如果是以掃描註解注入形式,其beanName的生成規則是由

org.springframework.context.annotation.AnnotationBeanNameGenerator#generateBeanName

決定。

ps: 這種直接從main啟動類除錯起,比較適用於時間比較多,或者排查毫無頭緒

方法二:帶著問題檢視,靠猜加驗證的方式

利用idea的find Usage查詢引用,比如ABService的註解@service,我們可以直接檢視哪個引用到@Service,再猜測下beanName的生成規則
在這裡插入圖片描述
透過猜,我們基本上就可以定位出比較符合我們需求的方法

原始碼驗證

從上面的分析,我們可以知道如果是掃描bean註解注入的方式,其生成beanName規則,是在

org.springframework.context.annotation.AnnotationBeanNameGenerator

其生成規則程式碼如下

@Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        if (definition instanceof AnnotatedBeanDefinition) {
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            if (StringUtils.hasText(beanName)) {
                // Explicit bean name found.
                return beanName;
            }
        }
        // Fallback: generate a unique default bean name.
        return buildDefaultBeanName(definition, registry);
    }

從程式碼段,我們可以看出,註解上有取名,比如@Service(“abService”),則beanName為abService,如果沒有取名,則看

protected String buildDefaultBeanName(BeanDefinition definition) {
        String beanClassName = definition.getBeanClassName();
        Assert.state(beanClassName != null, "No bean class name set");
        String shortClassName = ClassUtils.getShortName(beanClassName);
        return Introspector.decapitalize(shortClassName);
    }
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);
    }

其實從程式碼我們就很容易看出答案了,如果類名前兩個或以上個字母都是大寫,則beanName和類名就一樣了,不會進行首字母小寫轉換。

decapitalize這個方法的註釋也寫得很清楚,註釋如下

/**
     * Utility method to take a string and convert it to normal Java variable
     * name capitalization.  This normally means converting the first
     * character from upper case to lower case, but in the (unusual) special
     * case when there is more than one character and both the first and
     * second characters are upper case, we leave it alone.
     * <p>
     * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
     * as "URL".
     *
     * @param  name The string to be decapitalized.
     * @return  The decapitalized version of the string.
     */

總結

透過掃描bean註解注入IOC時,如果不指定bean名稱的預設規則是類名的首字母小寫,如果類名前兩個或以上個字母都是大寫,那麼bean名稱與類名一樣。

其實這個細節可能懂的都懂,本文的彩蛋主要是分享一下平時檢視原始碼的一點心得吧,哈哈

相關文章