Spring中的預設beanName

禾禾元元發表於2018-06-07

在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的頂級介面,而它有兩個實現類,圖中左側的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);
    }

通過上面兩段程式碼可以看出邏輯如下

  1. 取短類名,即不包含包路徑的類名,例如com.test.Student的短類名為Student,這點跟XML配置中取全類名不一樣
  2. 如果短類名長度大於1,且第一個和第二個字元為大寫,則直接返回短類名,也就是說假設類為com.test.STudent,則beanName為STudent
  3. 其他情況下將短類名首字元小寫後返回,假設類為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就是短類名


相關文章