美文网首页
Spring 容器的初始化

Spring 容器的初始化

作者: 咸鱼佬 | 来源:发表于2020-05-23 09:38 被阅读0次

    读完这篇文章你将会收获到

    • 了解到 Spring 容器初始化流程
    • ThreadLocal 在 Spring 中的最佳实践
    • 面试中回答 Spring 容器初始化流程

    引言

    我们先从一个简单常见的代码入手分析

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
    <beans>
        <bean class="com.demo.data.Person">
            <description>
                微信搜一搜:CoderLi(不妨关注➕一下?这次一定?)
            </description>
        </bean>
    </beans>
    
    public static void main(String[] args) {
            Resource resource = new ClassPathResource("coderLi.xml");
            DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
            xmlBeanDefinitionReader.loadBeanDefinitions(resource);
        }
    

    上面这段 Java 代码主要做了

    • 资源的获取(定位)
    • 创建一个 beanFactory
    • 根据 beanFactory (实现了 BeanDefinitionRegistry 接口) 创建一个 beanDefinitionReader
    • 装载资源并 registry 资源里面的 beanDefinition

    所以总体而言就是资源的加载、加载、注册三个步骤

    • 对于资源的加载可以看看我另一篇文章 Spring-资源加载(源码分析)

    • 加载的过程则是将 Resource 对象转为一系列的 BeanDefinition 对象

    • 注册则是将 BeanDefinition 注入到 BeanDefinitionRegistry 中

    组件介绍

    在分析源码流程之前我们一起先对一些重要的组件混个眼熟

    DefaultListableBeanFactory

    defaultListableBeanFactory 是整个 bean 加载的核心部分,是 bean 注册及加载 bean 的默认实现

    defaultListableBeanFactory 类图

    对于 AliasRegistry 可以参考我另一篇文章 Spring-AliasRegistry 。关于这个类我们只要记住两点,一个是它是一个 beanFactory、一个是它是一个 BeanDefinitionRegistry

    XmlBeanDefinitionReader

    从 XML 资源文件中读取并转换为 BeanDefinition 的各个功能

    XmlBeanDefinitionReader 类图

    DocumentLoader

    对 Resource 文件进行转换、将 Resource 文件转换为 Document 文件

    DocumentLoader 类图

    BeanDefinitionDocumentReader

    读取 Document 并向 BeanDefinitionRegistry 注册

    BeanDefinitionDocumentReader

    源码分析

    loadBeanDefinitions(Resource)

    XmlBeanDefinitionReader#loadBeanDefinitions(Resource) 我们先从这个入口方法开始进去

    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
       return loadBeanDefinitions(new EncodedResource(resource));
    }
    

    EncodedResource 是 Resource 的子类, Spring-资源加载(源码分析)

    public class EncodedResource implements InputStreamSource {
        private final Resource resource;
        @Nullable
        private final String encoding;
        @Nullable
        private final Charset charset;
    ..........
    ..........
      public Reader getReader() throws IOException {
            if (this.charset != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.charset);
            }
            else if (this.encoding != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.encoding);
            }
            else {
                return new InputStreamReader(this.resource.getInputStream());
            }
        }
    }
    

    只是一个简单的 Wrapper 类,针对不同的字符集和字符编码返回不一样的 Reader

    loadBeanDefinitions(EncodedResource)

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    
            // 从Thread Local 中获取正在加载的的资源
            Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    
            // 判断这个资源是否已经加载过了、主要是为了是否是 资源的循环依赖 import
            if (!currentResources.add(encodedResource)) {
                throw new BeanDefinitionStoreException("");
            }
    
            try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
                InputSource inputSource = new InputSource(inputStream);
                // 有encode 就设置进去
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 真正的加载
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException("");
            }
            finally {
                // ThreadLocal的最佳实践
                currentResources.remove(encodedResource);
                if (currentResources.isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }
            }
        }
    

    首先从 ThreadLocal 中获取正在加载的 Resource,这里主要是检查 import 标签带来的循环引用问题。

    从这里我们可以看到在 finally 中,对已经完成加载的资源进行移除,并且检查 Set 是否还有元素了,如果没有则直接调用 ThreadLocalremove 方法。这个就是 ThreadLocal 的最佳实践了,最后的 remove 方法的调用可以避免 ThreadLocal 在 ThreadLocalMap 中作为 WeakReference 而带来的内存泄露问题。

    这个方法里基本做啥事情、最主要的事情就是调用了 doLoadBeanDefinitions 这个方法,而这个方法才是真正干活的。(在 Spring 中,很有意思的是、真正干活的方法前缀都是带有 do 的,这个可以留意下)

    doLoadBeanDefinitions(InputSource Resource)

    // 获取 document 对象
    Document doc = doLoadDocument(inputSource, resource);
    // 注册 bean definition
    int count = registerBeanDefinitions(doc, resource);
    return count;
    

    doLoadDocument 这个方法就是将 Resource 转化为 Document,这里涉及到 xml 文件到验证,建立对应的 Document Node ,使用到的就是上面提及到的 DocumentLoader 。这个不展开来探讨。

    我们直接进入到 registerBeanDefinitions 方法中

    registerBeanDefinitions(Document,Resource)

    public int registerBeanDefinitions(Document doc, Resource resource)  {
            // 创建一个 bean definition 的 reader
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            // 注册之前已经有的 bean definition 的个数 return this.beanDefinitionMap.size();
            int countBefore = getRegistry().getBeanDefinitionCount();
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }
    

    上面代码中出现了一个我们提及到的 BeanDefinitionDocumentReader 组件,他的功能就是读取 Document 并向 BeanDefinitionRegistry 注册

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
       this.readerContext = readerContext;
       doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    

    这里又来了、do 才是真正干活的大哥

    protected void doRegisterBeanDefinitions(Element root) {
      
       BeanDefinitionParserDelegate parent = this.delegate;
       this.delegate = createDelegate(getReaderContext(), root, parent);
    
       if (this.delegate.isDefaultNamespace(root)) {
          // 处理 profiles
          String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
          if (StringUtils.hasText(profileSpec)) {
             String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                   profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
             if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
             }
          }
       }
       // 解释前的处理 这里默认为空实现、子类可以覆盖此方法在解释 Element 之前做些事情
       preProcessXml(root);
       // 解释
       parseBeanDefinitions(root, this.delegate);
       // 解释后处理 这里默认为空实现
       postProcessXml(root);
    
       this.delegate = parent;
    }
    

    这里主要的方法就是 parseBeanDefinitions

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    
       if (delegate.isDefaultNamespace(root)) {
          NodeList nl = root.getChildNodes();
          for (int i = 0; i < nl.getLength(); i++) {
             Node node = nl.item(i);
             if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                   // spring 默认标签解释
                   parseDefaultElement(ele, delegate);
                } else {
                   // 自定义 标签解释
                   delegate.parseCustomElement(ele);
                }
             }
          }
       } else {
          delegate.parseCustomElement(root);
       }
    }
    

    Spring 的默认标签有 import , beans , bean , alias

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
       // 解释 import 标签
       if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
          importBeanDefinitionResource(ele);
       } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
          processAliasRegistration(ele);
       } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
          processBeanDefinition(ele, delegate);
       } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
          doRegisterBeanDefinitions(ele);
       }
    }
    

    解释 import 标签调用 importBeanDefinitionResource 最终会调用到我们最开始处理 Resource 循环依赖的那个方法 loadBeanDefinitions

    我们直接进入到 processAliasRegistration 方法中

    protected void processAliasRegistration(Element ele) {
       String name = ele.getAttribute(NAME_ATTRIBUTE);
       String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
       boolean valid = true;
       if (!StringUtils.hasText(name)) {
          getReaderContext().error("Name must not be empty", ele);
          valid = false;
       }
       if (!StringUtils.hasText(alias)) {
          getReaderContext().error("Alias must not be empty", ele);
          valid = false;
       }
       if (valid) {
          try {
            // 最重要的一行代码
             getReaderContext().getRegistry().registerAlias(name, alias);
          } catch (Exception ex) {
             getReaderContext().error("Failed to register alias '" + alias +
                   "' for bean with name '" + name + "'", ele, ex);
          }
          getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
       }
    }
    

    最重要的一行代码就是将 name 和 alias 进行注册(这里注册的是 alias 标签中的 name 和 alias 之间的关系),可以参考这篇文章进行了解 Spring-AliasRegistry

    我们来到最主要的 processBeanDefinition

    protected void processBeanDefinition(Element ele,
    BeanDefinitionParserDelegate delegate) {
            
            // 这里获得了一个 BeanDefinitionHolder 
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    
            if (bdHolder != null) {
                bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
                try {
                    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
                } catch (BeanDefinitionStoreException ex) {
                    .....
                }
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }
        }
    

    我们先分析 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()) 这句代码

    public static void registerBeanDefinition(
                BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
                throws BeanDefinitionStoreException {
    
            // 注册 bean Name
            String beanName = definitionHolder.getBeanName();
            registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
            // 注册 alias .
            String[] aliases = definitionHolder.getAliases();
            if (aliases != null) {
                for (String alias : aliases) {
                    registry.registerAlias(beanName, alias);
                }
            }
        }
    

    这个方法的作用很简单、就是使用一开始我们传给 XmlBeanDefinitionReaderBeanDefinitionRegistry 对 bean 和 beanDefinition 的关系进行注册。并且也对 beanName 和 alias 的关系进行注册(这里是对 bean 标签中配置的 id 和 name 属性关系进行配置)

    delegate.parseBeanDefinitionElement(ele) 我们再把眼光返回到这个方法、这个方法就是创建 BeanDefinition 的地方了

    @Nullable
        public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
            return parseBeanDefinitionElement(ele, null);
        }
    
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    
       String id = ele.getAttribute(ID_ATTRIBUTE);
       String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    
       List<String> aliases = new ArrayList<>();
       // 判断是否配置了 name 属性、对name 进行分割 
      // 在 bean 标签中 name 就是 alias 了
       if (StringUtils.hasLength(nameAttr)) {
          String[] nameArr = StringUtils.tokenizeToStringArray(...);
          aliases.addAll(Arrays.asList(nameArr));
       }
    
       String beanName = id;
       // 没有配置id 并且 alias 列表不为空、则选取第一个 alias 为 bean Name
       if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
          beanName = aliases.remove(0);
       }
    
       if (containingBean == null) {
         // 检查 beanName 和alias 的唯一性
          checkNameUniqueness(beanName, aliases, ele);
       }
    
       // 怎么生成一个BeanDefinition 尼
       AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    
       if (beanDefinition != null) {
          // 如果 beanName 为 空
          if (!StringUtils.hasText(beanName)) {
             try {
                if (containingBean != null) {
                   beanName = BeanDefinitionReaderUtils.generateBeanName(
                         beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                   // 没有配置 beanName 和 alias的话、那么这个类的第一个实例、将拥有 全类名的alias
                   // org.springframework.beans.testfixture.beans.TestBean 这个是别名(TestBean#0 才拥有这个别名、其他的不配拥有)
                   // org.springframework.beans.testfixture.beans.TestBean#0 这个是 beanName
                   beanName = this.readerContext.generateBeanName(beanDefinition);
                  
                   String beanClassName = beanDefinition.getBeanClassName();
                   if (beanClassName != null &&
                         beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                         !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                      aliases.add(beanClassName);
                   }
                }
               
             } catch (Exception ex) {
               .........
             }
          }
          String[] aliasesArray = StringUtils.toStringArray(aliases);
          return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
       }
       // nothing
       return null;
    }
    

    在 bean 标签中 name 属性对应的就是 alias ,id 属性对应的就是 beanName 了

    当我们没有配置 id 属性但是配置了 name 属性、那么第一个 name 属性就会成为我们的 id

    当我们既没有配置 id 属性 也没有配置 name 属性,那么 Spring 就会帮我们生成具体可看看 Spring-AliasRegistry

    然后就创建了一个 BeanDefinitionHolder 返回了

    上面的代码我们看到有这个关键的方法 parseBeanDefinitionElement(ele, beanName, containingBean) 这个方法生成了我们期待的 BeanDefinition ,但是里面的内容都是比较枯燥的

    // 解释class 属性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    // 是否指定了 parent bean
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }
      // 创建 GenericBeanDefinition
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
      // 解释各种默认的属性
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      // 提取describe
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
      // 解释元数据
      parseMetaElements(ele, bd);
      // look up 方法
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      // replacer
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
      // 解析构造函数参数
      parseConstructorArgElements(ele, bd);
      // 解释property子元素
      parsePropertyElements(ele, bd);
      // 解释qualifier
      parseQualifierElements(ele, bd);
    
      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));
    

    都是去解析 bean 标签里面的各种属性

    那么我们整个 Spring 容器初始化流程就介绍完了

    总结

    public static void main(String[] args) {
            Resource resource = new ClassPathResource("coderLi.xml");
            DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
            xmlBeanDefinitionReader.loadBeanDefinitions(resource);
        }
    
    1. 调用 XmlBeanDefinitionReader 的方法 loadBeanDefinitions
    2. 将 Resource 包裹成 EncodeResource
    3. 通过 ThreadLocal 判断是否 Resource 循环依赖
    4. 使用 DocumentLoader 将 Resource 转换为 Document
    5. 使用 BeanDefinitionDocumentReader 解释 Document 的标签
    6. 解释 Spring 提供的默认标签/自定义的标签解释
      • 解释 import 标签的时候会回调到步骤2中
      • 解释 alias 标签会向 AliasRegistry 注册
      • 解释 bean 标签会向 BeanDefinitionRegistry 注册 beanName 和 BeanDefinition ,也会注册 bean 标签里面 id 和 name 的关系(其实就是 alias )

    大致的流程就是如此了,面试的时候大致说出 XmlBeanDefinitionReader,DocumentLoader,BeanDefinitionDocumentReader,BeanDefinitionRegistry,AliasRegistry 这几个组件,面试官大概率会认为你是真的看过 Spring 的这部分代码的

    往期相关文章

    Spring-资源加载(源码分析)

    Spring-AliasRegistry

    编译Spring5.2.0源码

    这次一定?

    相关文章

      网友评论

          本文标题:Spring 容器的初始化

          本文链接:https://www.haomeiwen.com/subject/zghkahtx.html