zoukankan      html  css  js  c++  java
  • 框架源码系列七: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

  • 相关阅读:
    494. Target Sum 添加标点符号求和
    636. Exclusive Time of Functions 进程的执行时间
    714. Best Time to Buy and Sell Stock with Transaction Fee有交易费的买卖股票
    377. Combination Sum IV 返回符合目标和的组数
    325. Maximum Size Subarray Sum Equals k 和等于k的最长子数组
    275. H-Index II 递增排序后的论文引用量
    274. H-Index论文引用量
    RabbitMQ学习之HelloWorld(1)
    java之struts2的数据处理
    java之struts2的action的创建方式
  • 原文地址:https://www.cnblogs.com/leeSmall/p/10134307.html
Copyright © 2011-2022 走看看