• Spring源码入门——AnnotationBeanNameGenerator解析


    ---恢复内容开始---

      接上篇,上篇解析了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无法明确具体哪个生效)

  • 相关阅读:
    netty系列之:基于流的数据传输
    netty系列之:中国加油
    netty系列之:Event、Handler和Pipeline
    netty系列之:netty中的Channel详解
    netty系列之:netty架构概述
    Python 列表解析式竟然支持异步?
    Python 数值中的下划线是怎么回事?
    Nginx+keepalived 双机主从模式下修改主Nginx自动同步nginx.conf配置文件至备Nginx
    Oracle nvarchar2存储特殊字符乱码问题
    Oracle 11.2.0.4.0版本下大表添加默认值字段影响因素
  • 原文地址:https://www.cnblogs.com/Jeely/p/10794176.html
Copyright © 2020-2023  润新知