1. 程式人生 > >Spring源碼入門——AnnotationBeanNameGenerator解析

Spring源碼入門——AnnotationBeanNameGenerator解析

類型 substr 整理 org new auto exception med res

---恢復內容開始---

  接上篇,上篇解析了DefaultBeanGenerator生成bean name的過程(http://www.cnblogs.com/jason0529/p/5272265.html ), 本篇我們繼續解析另一類bean name生成方式。

  spring定義bean有兩種模式,配置文件(xml,properties)和註解。註:jpa的聲明接口生成bean應該可以算第三種模式,這裏不討論。

  對兩種bean定義方式,spring提供了兩種不同的bean name實現方式去實現不同的模式。AnnotationBeanNameGenerator能夠處理 Component,Respository,Service,Controller這四個常用的註解,解析為bean name註給他們對應的value屬性。另外jee的javax.annotation.ManagedBean和javax.inject.Named也可以支持。

  當Component,Respository,Service,Controller註解的value樹形沒有自定義時,會根據類的名稱生成一個短的bean name。例如: com.xyz.FooServiceImpl -> fooServiceImpl

  入口肯定是BeanNameGenerator接口聲明的generateBeanName(BeanDefinition, BeanDefinitionRegistry) 方法,該方法做了一個分類判斷,處理AnnotationBeanDefinition和default兩種方式的。

技術分享圖片
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //判斷是否是否是AnnotatedBeanDefinition的子類, AnnotatedBeanDefinition是BeanDefinition的一個子類
        //如果是AnnotatedBeanDefinition , 按照註解生成模式生成信息,否則生成默認的bean name
        if (definition instanceof AnnotatedBeanDefinition) {
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            //保證生成的bean name 非空
            if (StringUtils.hasText(beanName)) {
                // Explicit bean name found.
                return beanName;
            }
        }
        // Fallback: generate a unique default bean name.
        return buildDefaultBeanName(definition, registry);
    }
技術分享圖片

 先從相對簡單的default看起,這段代碼的疑點是生成的bean name並沒有和DefaultBeanNameGenerator一樣做唯一性校驗,可能導致不同包下面存在相同的類名時,會產生兩個name一樣的bean,引發spring 異常。

技術分享圖片
/**
     * Derive a default bean name from the given bean definition.
     * <p>The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}.
     * @param definition the bean definition to build a bean name for
     * @param registry the registry that the given bean definition is being registered with
     * @return the default bean name (never {@code null})
     */
    protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
     //沒有做 name 唯一性校驗。 return buildDefaultBeanName(definition); } /** * Derive a default bean name from the given bean definition. * <p>The default implementation simply builds a decapitalized version * of the short class name: e.g. "mypackage.MyJdbcDao" -> "myJdbcDao". * <p>Note that inner classes will thus have names of the form * "outerClassName.innerClassName", which because of the period in the * name may be an issue if you are autowiring by name. * @param definition the bean definition to build a bean name for * @return the default bean name (never {@code null}) */ protected String buildDefaultBeanName(BeanDefinition definition) {
     //具體類名獲取和格式化先不做具體討論 String shortClassName = ClassUtils.getShortName(definition.getBeanClassName()); return Introspector.decapitalize(shortClassName); }
技術分享圖片

---恢復內容結束---

  接上篇,上篇解析了DefaultBeanGenerator生成bean name的過程(http://www.cnblogs.com/jason0529/p/5272265.html ), 本篇我們繼續解析另一類bean name生成方式。

  spring定義bean有兩種模式,配置文件(xml,properties)和註解。註:jpa的聲明接口生成bean應該可以算第三種模式,這裏不討論。

  對兩種bean定義方式,spring提供了兩種不同的bean name實現方式去實現不同的模式。AnnotationBeanNameGenerator能夠處理 Component,Respository,Service,Controller這四個常用的註解,解析為bean name註給他們對應的value屬性。另外jee的javax.annotation.ManagedBean和javax.inject.Named也可以支持。

  當Component,Respository,Service,Controller註解的value樹形沒有自定義時,會根據類的名稱生成一個短的bean name。例如: com.xyz.FooServiceImpl -> fooServiceImpl

  入口肯定是BeanNameGenerator接口聲明的generateBeanName(BeanDefinition, BeanDefinitionRegistry) 方法,該方法做了一個分類判斷,處理AnnotationBeanDefinition和default兩種方式的。

技術分享圖片
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //判斷是否是否是AnnotatedBeanDefinition的子類, AnnotatedBeanDefinition是BeanDefinition的一個子類
        //如果是AnnotatedBeanDefinition , 按照註解生成模式生成信息,否則生成默認的bean name
        if (definition instanceof AnnotatedBeanDefinition) {
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            //保證生成的bean name 非空
            if (StringUtils.hasText(beanName)) {
                // Explicit bean name found.
                return beanName;
            }
        }
        // Fallback: generate a unique default bean name.
        return buildDefaultBeanName(definition, registry);
    }
技術分享圖片

 先從相對簡單的default看起,這段代碼的疑點是生成的bean name並沒有和DefaultBeanNameGenerator一樣做唯一性校驗,可能導致不同包下面存在相同的類名時,會產生兩個name一樣的bean,引發spring 異常。

技術分享圖片
/**
     * Derive a default bean name from the given bean definition.
     * <p>The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}.
     * @param definition the bean definition to build a bean name for
     * @param registry the registry that the given bean definition is being registered with
     * @return the default bean name (never {@code null})
     */
    protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
     //沒有做 name 唯一性校驗。 return buildDefaultBeanName(definition); } /** * Derive a default bean name from the given bean definition. * <p>The default implementation simply builds a decapitalized version * of the short class name: e.g. "mypackage.MyJdbcDao" -> "myJdbcDao". * <p>Note that inner classes will thus have names of the form * "outerClassName.innerClassName", which because of the period in the * name may be an issue if you are autowiring by name. * @param definition the bean definition to build a bean name for * @return the default bean name (never {@code null}) */ protected String buildDefaultBeanName(BeanDefinition definition) {
     //具體類名獲取和格式化先不做具體討論 String shortClassName = ClassUtils.getShortName(definition.getBeanClassName()); return Introspector.decapitalize(shortClassName); }
技術分享圖片

  其實ClassUtils.getShortName也很簡單,根據傳入字符串獲取一個具體類名稱,不含包路徑,考慮cglib代理的類,做了一個特殊處理。

技術分享圖片
/**
     * Get the class name without the qualified package name.
     * @param className the className to get the short name for
     * @return the class name of the class without the package name
     * @throws IllegalArgumentException if the className is empty
     */
    public static String getShortName(String className) {
        Assert.hasLength(className, "Class name must not be empty");
        int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
        int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
        if (nameEndIndex == -1) {
            nameEndIndex = className.length();
        }
        String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
        shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
        return shortName;
    }
技術分享圖片

  接下來看第二條線,AnnotationBeanDefinition的beanName如何生成,具體處理委托給了determineBeanNameFromAnnotation(AnnotatedBeanDefinition)方法完成,該方法對該類的所有註解進行了遍歷,滿足三個條件:註解名稱在待解析列表,存在value屬性且非空,當且只有一個註解正確配置value屬性。  

技術分享圖片
    /**
     * Derive a bean name from one of the annotations on the class.
     * 從類的註解中包含value屬性的註解生成一個bean name
     * @param annotatedDef the annotation-aware bean definition
     * @return the bean name, or {@code null} if none is found
     */
    protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
        //獲取註解類元信息
        AnnotationMetadata amd = annotatedDef.getMetadata();
        //一個類存在多個註解,故類型為集合
        Set<String> types = amd.getAnnotationTypes();
        String beanName = null;
        for (String type : types) {
            //獲取該類型對應的屬性
            AnnotationAttributes attributes = MetadataUtils.attributesFor(amd, type);
            //判斷註解類型是否包含value屬性
            if (isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) {
                String value = (String) attributes.get("value");
                if (StringUtils.hasLength(value)) {
                    //不多於1個註解配置了value屬性且非空,比如無法在一個類上面同時使用Component和Sevice註解同時配置beanName值
                    if (beanName != null && !value.equals(beanName)) {
                        throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
                                "component names: ‘" + beanName + "‘ versus ‘" + value + "‘");
                    }
                    beanName = value;
                }
            }
        }
        return beanName;
    }
技術分享圖片

  這個方法裏面有兩個關鍵的處理流程,第一步,讀取對應annotationType對應的所有屬性。

public static AnnotationAttributes attributesFor(AnnotationMetadata metadata, String annoClassName) {
        return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false));
    }
技術分享圖片
   //AnnotationMetadata.java
   /**
     * Retrieve the attributes of the annotation of the given type,
     * if any (i.e. if defined on the underlying class, as direct
     * annotation or as meta-annotation).
     * @param annotationType the annotation type to look for
     * @param classValuesAsString whether to convert class references to String
     * class names for exposure as values in the returned Map, instead of Class
     * references which might potentially have to be loaded first
     * @return a Map of attributes, with the attribute name as key (e.g. "value")
     * and the defined attribute value as Map value. This return value will be
     * {@code null} if no matching annotation is defined.
     */
    Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
技術分享圖片

  第二步,判斷annotationType是否具有value屬性,但是metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)這部分並不是很懂?註解無法繼承,為啥要多判斷一次?

技術分享圖片
    private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
    /**
     * Check whether the given annotation is a stereotype that is allowed
     * to suggest a component name through its annotation {@code value()}.
     * @param annotationType the name of the annotation class to check
     * @param metaAnnotationTypes the names of meta-annotations on the given annotation
     * @param attributes the map of attributes for the given annotation
     * @return whether the annotation qualifies as a stereotype with component name
     */
    protected boolean isStereotypeWithNameValue(String annotationType,Set<String> metaAnnotationTypes, Map<String, Object> attributes) {

        boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
                (metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) ||
                annotationType.equals("javax.annotation.ManagedBean") ||
                annotationType.equals("javax.inject.Named");
        return (isStereotype && attributes != null && attributes.containsKey("value"));
    }
技術分享圖片

  整理下思路:

    生成bean name有兩條處理線,使用AnnotationBeanDefinition註解和不使用的。

    不使用AnnotationBeanDefinition註解的,直接將類名(不含包名)改為駝峰形式作為bean name。

    使用AnnotationBeanDefinition註解的:

      1,讀取所有註解類型

      2,便利所有註解類型,找到所有為Component、Service,Respository,Controller含有非空value屬性的註解

      3,不多於一個個有效配置時生效,大於一個會拋出異常。(spring無法明確具體哪個生效)

Spring源碼入門——AnnotationBeanNameGenerator解析