• 框架源码系列七:Spring源码学习之BeanDefinition源码学习(BeanDefinition、Annotation 方式配置的BeanDefinition的解析)


    一、BeanDefinition

    1. bean定义都定义了什么?

    2、BeanDefinition的继承体系

     父类:

    AttributeAccessor

    可以在xmlbean定义里面加上DTD文件里面没有的属性,如

        <bean id="cbean" class="com.study.spring.samples.CBean" name="leeSamll" age="18">
            <constructor-arg type="String" value="cbean01"></constructor-arg>
        </bean>

    BeanMetadataElement :

    定义bean定义来源于哪里,在BeanDefinition 里面的getResourceDescrption里面获取

    子类:

     

    类图:

     

     请思考:为什么要增加AnnotatedBeanDefinition?用GenericBeanDefinition不可以吗?是不是注解方式的Bean定义信息的存放及使用方式与通用的Bean定义方式不一样了?

    GenericBeanDefinition要定义的东西太多了,xml方式的处理和注解方式的处理可能不太一样了,所以做了扩展加入AnnotatedBeanDefinition,是为了只定义自己需要的东西,后面直接从AnnotatedBeanDefinition获取就行了

    二、Annotation 方式配置的BeanDefinition的解析

    1. 扫描的过程,如何扫描

     

        protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
            Assert.notEmpty(basePackages, "At least one base package must be specified");
            Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
            for (String basePackage : basePackages) {
                Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
                for (BeanDefinition candidate : candidates) {
                    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                    candidate.setScope(scopeMetadata.getScopeName());
                    String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                    if (candidate instanceof AbstractBeanDefinition) {
                        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                    }
                    if (candidate instanceof AnnotatedBeanDefinition) {
                        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                    }
                    if (checkCandidate(beanName, candidate)) {
                        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                        definitionHolder =
                                AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                        beanDefinitions.add(definitionHolder);
                        registerBeanDefinition(definitionHolder, this.registry);
                    }
                }
            }
            return beanDefinitions;
        }

    findCandidateComponents方法说明:

    组件索引方式获取bean定义:在pom.xml里面加入spring-context-indexer这个依赖,在编译的时候就会生成META-INF/spring.components文件(加了注解的类),然后bean定义就可以从spring.components里面获取,而不用在启动的时候去包下面扫描获取bean定义,变得很快

    scanCandidateComponents方法说明:

     

     MetadataReader说明:

    说明:

    获取类的信息,不只只有反射,ASM也可以获取,ASM通过获取类的字节码,参观Class(ClassVisistor可以获取到类上的注解和类对应的信息(类的属性、类的方法等),用到的ASM组件有ClassReaderClassVisistor

    2. 注解的解析

    2.1 如何从扫到的 .class 文件中获得注解信息?

    我们自己实现是如何做的:

    1)Class.forname("className")加载类获得Class对象

    2)反射获取注解

    3)判断是否存在组件,存在为其创建BeanDefinition

    4)看指定了名字没,如果没有,应用名字生成策略生成一个名字

    5)注册BeanDefinition

    2.2 Spring的解析过程

    spring解析注解利用的是ASM,ASM是一个低层次的字节码操作库

    2.3 提问:spring 中通过 ASM 字节码操作库来读取的类信息、注解信息。它没有加载类,为什么不用加载类的方式?

       因为加载类都要放到内存里面,用不到的话内存就会浪费了,用ASM加载类不会进内存里面。在spring的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(String)的方法里面的这段代码读取类的信息、注解信息的

    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
     * Copyright 2002-2009 the original author or authors.
    
    package org.springframework.core.type.classreading;
    
    import org.springframework.core.io.Resource;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.core.type.ClassMetadata;
    
    /**
     * Simple facade for accessing class metadata,
     * as read by an ASM {@link org.springframework.asm.ClassReader}.
     *
     * @author Juergen Hoeller
     * @since 2.5
     */
    public interface MetadataReader {
    
        /**
         * Return the resource reference for the class file.
         */
        Resource getResource();
    
        /**
         * Read basic class metadata for the underlying class.
         */
        ClassMetadata getClassMetadata();
    
        /**
         * Read full annotation metadata for the underlying class,
         * including metadata for annotated methods.
         */
        AnnotationMetadata getAnnotationMetadata();
    
    }

    2.4 MetadataReader读取到类信息、注解信息后,如何进行判断及创建BeanDefinition的,往BeanDefintion中给入了哪些信息。

     在spring的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(String)的方法里面的这段代码判断和创建BeanDefinition的

                            if (isCandidateComponent(metadataReader)) {
                                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                                sbd.setResource(resource);
                                sbd.setSource(resource);
                                if (isCandidateComponent(sbd)) {
                                    if (debugEnabled) {
                                        logger.debug("Identified candidate component class: " + resource);
                                    }
                                    candidates.add(sbd);
                                }
                                else {
                                    if (debugEnabled) {
                                        logger.debug("Ignored because not a concrete top-level class: " + resource);
                                    }
                                }
                            }

    判断是否是候选组件的方法:

        /**
         * Determine whether the given class does not match any exclude filter
         * and does match at least one include filter.
         * @param metadataReader the ASM ClassReader for the class
         * @return whether the class qualifies as a candidate component
         */
        protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
            for (TypeFilter tf : this.excludeFilters) {
                if (tf.match(metadataReader, getMetadataReaderFactory())) {
                    return false;
                }
            }
            for (TypeFilter tf : this.includeFilters) {
                if (tf.match(metadataReader, getMetadataReaderFactory())) {
                    return isConditionMatch(metadataReader);
                }
            }
            return false;
        }

    2.5 请思考,我们可以自己定义标注组件的注解吗?【扩展点】

    可以,示例如下:

    package com.study.leesamll.spring.ext;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.stereotype.Component;
    
    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface MyComponetAnno {
    
        String value() default "";
    }

    使用:

    package com.study.leesamll.spring.service;
    
    import java.util.Locale;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    
    import com.study.leesmall.spring.ext.MyComponetAnno;
    
    //@Componet
    //@Service
    @MyComponetAnno
    public class Abean {
    
        @Autowired
        private ApplicationContext applicationContext;
    
        public Abean() {
            System.out.println("-----------------Abean 被实例化了。。。。。。。。。");
        }
    
        public void doSomething() {
            System.out.println(this + " do something .....mike.love="
                    + this.applicationContext.getEnvironment().getProperty("mike.love"));
            System.out
                    .println("-----------mike.name=" + this.applicationContext.getMessage("mike.name", null, Locale.CHINA));
        }
    }

    2.6 扫描的过滤这块你在实际项目中是否用过?如何使用的?【扩展点】

    示例:

    package com.study.leesmall.spring.ext;
    
    import java.io.IOException;
    
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.core.type.filter.TypeFilter;
    
    import com.study.leesmall.spring.service.Abean;
    
    public class MyTypeFilter implements TypeFilter {
    
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                throws IOException {
            // 使用metadataReader中的类信息、注解信息来进行你的过滤判断逻辑
            return metadataReader.getClassMetadata().getClassName().equals(Abean.class.getName());
        }
    
    }

    TypeFilter的子类:

    请思考:像@Controller 注解,它和@Service、@Component 注解有不同的意图,这种不同的意图将会在哪里实现?如果我们自己也有类似的需要自定义组件注解,是不就可以模仿@Controller。猜想 spring是如何做到灵活扩展这个的?

    3、BeanDefinition注册 

    在前面我们已经拿到BeanDefinition了,下面就是注册BeanDefinition了

    1.目标

    1.1 搞清楚BeanFactory中BeanDefinition是如何存储的?
    1.2 搞清楚注册的过程是怎样的。

    2. 思路

    2.1 思考:我们原来是如何来存储beanName,BeanDefinition的?
      用并发的Map来存,beanName作为键,BeanDefinition作为值
    2.2 思考:我们注册BeanDefinition的处理逻辑是怎样的?
      首先判断beanName是不是合法的,如果是合法的再放到Map里面

    3. 看spring的注册过程

    入口:DefaultListableBeanFactory.registerBeanDefinition(String beanName,BeanDefinitionbeanDefinition)

    还是先拿到调用栈来分析:

    处理BeanDefinition的过程:

    String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

    说明:Spring里面默认的bean的名字的生成策略是拿类的名称来当bean的名称

    看一下AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);方法里面是怎么处理注解BeanDefinition的:

    下面就来看具体的注册bean定义逻辑:

     

    PS:

    1.Maven怎么加依赖时怎么同时下载源码?
    Eclipse-window-preference-maven-勾选download artifact sources
    下载慢的话把maven的setting.xml配置文件的镜像地址换成阿里的更快

    完整代码获取地址:https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-source-study

  • 相关阅读:
    Bug测试报告--在线考试系统--金州勇士
    Bug测试报告--食物链教学工具--奋斗吧兄弟
    Jquery对象和dom对象获取html的方法
    mysql中常见的存储引擎和索引类型
    转:spring MVC HTTP406 Not Acceptable
    Mybatis动态建表
    ssm框架插入mysql数据库中文乱码问题解决
    Maven环境下Poi的使用
    【转】Mybatis传多个参数(三种解决方案)
    【译文】用Spring Cloud和Docker搭建微服务平台
  • 原文地址:https://www.cnblogs.com/leeSmall/p/10134307.html
Copyright © 2020-2023  润新知