2022-08-23  阅读(50)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/series/article/1758153696

前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。

在前文提过,IOC 容器的初始化过程分为三步骤:Resource 定位、BeanDefinition 的载入和解析,BeanDefinition 注册。

202202131350410631.png

  • Resource 定位 。我们一般用外部资源来描述 Bean 对象,所以在初始化 IOC 容器的第一步就是需要定位这个外部资源。
  • BeanDefinition 的载入和解析 。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IOC 容器的内部数据结构:BeanDefinition。在 IOC 容器内部维护着一个 BeanDefinition Map 的数据结构,在配置文件中每一个 都对应着一个BeanDefinition对象。
  • BeanDefinition 注册 。向IOC容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistery 接口来实现的。在 IOC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IOC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。在这里需要注意的一点是这个过程并没有完成依赖注入,依赖注册是发生在应用第一次调用 getBean() 向容器索要 Bean 时。当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。

还记得在博客【死磕 Spring】----- IOC 之 加载 Bean 中提供的一段代码吗?这里我们同样也以这段代码作为我们研究 IOC 初始化过程的开端,如下:

            ClassPathResource resource = new ClassPathResource("bean.xml");
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
            reader.loadBeanDefinitions(resource);

刚刚开始的时候可能对上面这几行代码不知道什么意思,现在应该就一目了然了。

  • ClassPathResource resource = new ClassPathResource("bean.xml");: 根据 Xml 配置文件创建 Resource 资源对象。ClassPathResource 是 Resource 接口的子类,bean.xml 文件中的内容是我们定义的 Bean 信息。
  • DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 创建一个 BeanFactory。DefaultListableBeanFactory 是 BeanFactory 的一个子类,BeanFactory 作为一个接口,其实它本身是不具有独立使用的功能的,而 DefaultListableBeanFactory 则是真正可以独立使用的 IOC 容器,它是整个 Spring IOC 的始祖,在后续会有专门的文章来分析它。
  • XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);:创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition 。
  • reader.loadBeanDefinitions(resource);:开启 Bean 的载入和注册进程,完成后的 Bean 放置在 IOC 容器中。

Resource 定位

Spring 为了解决资源定位的问题,提供了两个接口:Resource、ResourceLoader,其中 Resource 接口是 Spring 统一资源的抽象接口,ResourceLoader 则是 Spring 资源加载的统一抽象。关于Resource、ResourceLoader 的更多知识请关注【死磕 Spring】----- IOC 之 Spring 统一资源加载策略

Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合,在上面那段代码中 new ClassPathResource("bean.xml") 为我们定义了资源,那么 ResourceLoader 则是在什么时候初始化的呢?看 XmlBeanDefinitionReader 构造方法:

                public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
                    super(registry);
                }

直接调用父类 AbstractBeanDefinitionReader :

                protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
                    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
                    this.registry = registry;
            
                    // Determine ResourceLoader to use.
                    if (this.registry instanceof ResourceLoader) {
                        this.resourceLoader = (ResourceLoader) this.registry;
                    }
                    else {
                        this.resourceLoader = new PathMatchingResourcePatternResolver();
                    }
            
                    // Inherit Environment if possible
                    if (this.registry instanceof EnvironmentCapable) {
                        this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
                    }
                    else {
                        this.environment = new StandardEnvironment();
                    }
                }

核心在于设置 resourceLoader 这段,如果设置了 ResourceLoader 则用设置的,否则使用 PathMatchingResourcePatternResolver ,该类是一个集大成者的 ResourceLoader。

BeanDefinition 的载入和解析

reader.loadBeanDefinitions(resource); 开启 BeanDefinition 的解析过程。如下:

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

在这个方法会将资源 resource 包装成一个 EncodedResource 实例对象,然后调用 loadBeanDefinitions() 方法,而将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码,保证内容读取的正确性。

               public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
                    // 省略一些代码
                    try {
                        // 将资源文件转为 InputStream 的 IO 流
                        InputStream inputStream = encodedResource.getResource().getInputStream();
                        try {
                            // 从 InputStream 中得到 XML 的解析源
                            InputSource inputSource = new InputSource(inputStream);
                            if (encodedResource.getEncoding() != null) {
                                inputSource.setEncoding(encodedResource.getEncoding());
                            }
            
                            // 具体的读取过程
                            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                        }
                        finally {
                            inputStream.close();
                        }
                    }
                    // 省略一些代码
                }

从 encodedResource 源中获取 xml 的解析源,调用 doLoadBeanDefinitions() 执行具体的解析过程。

                protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                        throws BeanDefinitionStoreException {
                    try {
                        Document doc = doLoadDocument(inputSource, resource);
                        return registerBeanDefinitions(doc, resource);
                    }
                    // 省略很多catch代码

在该方法中主要做两件事:1、根据 xml 解析源获取相应的 Document 对象,2、调用 registerBeanDefinitions() 开启 BeanDefinition 的解析注册过程。

转换为 Document 对象

调用 doLoadDocument() 会将 Bean 定义的资源转换为 Document 对象。

                protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
                    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                            getValidationModeForResource(resource), isNamespaceAware());
                }

loadDocument() 方法接受五个参数:

  • inputSource :加载 Document 的 Resource 源
  • entityResolver :解析文件的解析器
  • errorHandler :处理加载 Document 对象的过程的错误
  • validationMode :验证模式
  • namespaceAware :命名空间支持。如果要提供对 XML 名称空间的支持,则为true

对于这五个参数,有两个参数需要重点关注下:entityResolver、validationMode。这两个参数分别在 【死磕Spring】----- IOC 之 获取 Document 对象【死磕Spring】----- IOC 之 获取验证模型 中有详细的讲述。

loadDocument() 在类 DefaultDocumentLoader 中提供了实现,如下:

                public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
                  // 创建文件解析工厂
                    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
                    }
                    // 创建文档解析器
                    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
                    // 解析 Spring 的 Bean 定义资源
                    return builder.parse(inputSource);
                }

这到这里,就已经将定义的 Bean 资源文件,载入并转换为 Document 对象了,那么下一步就是如何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中。这个过程有方法 registerBeanDefinitions() 实现。如下:

                public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
                   // 创建 BeanDefinitionDocumentReader 来对 xml 格式的BeanDefinition 解析
                    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
                    // 获得容器中注册的Bean数量
                    int countBefore = getRegistry().getBeanDefinitionCount();
            
                    // 解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,
                    // 具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成
                    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
                    return getRegistry().getBeanDefinitionCount() - countBefore;
                }

首先创建 BeanDefinition 的解析器 BeanDefinitionDocumentReader,然后调用 documentReader.registerBeanDefinitions() 开启解析过程,这里使用的是委派模式,具体的实现由子类 DefaultBeanDefinitionDocumentReader 完成。

                public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
                   // 获得XML描述符
                    this.readerContext = readerContext;
                    logger.debug("Loading bean definitions");
            
                    // 获得Document的根元素
                    Element root = doc.getDocumentElement();
            
                    // 解析根元素
                    doRegisterBeanDefinitions(root);
                }

对 Document 对象的解析

从 Document 对象中获取根元素 root,然后调用 doRegisterBeanDefinitions() 开启真正的解析过程。

                protected void doRegisterBeanDefinitions(Element root) {
                    BeanDefinitionParserDelegate parent = this.delegate;
                    this.delegate = createDelegate(getReaderContext(), root, parent);
            
                   // 省略部分代码
            
                    preProcessXml(root);
                    parseBeanDefinitions(root, this.delegate);
                    postProcessXml(root);
            
                    this.delegate = parent;
                }

preProcessXml()postProcessXml() 为前置、后置增强处理,目前 Spring 中都是空实现, parseBeanDefinitions() 是对根元素 root 的解析注册过程。

                protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
                   // Bean定义的Document对象使用了Spring默认的XML命名空间
                    if (delegate.isDefaultNamespace(root)) {
                      // 获取Bean定义的Document对象根元素的所有子节点
                        NodeList nl = root.getChildNodes();
                        for (int i = 0; i < nl.getLength(); i++) {
                            Node node = nl.item(i);
                            // 获得Document节点是XML元素节点
                            if (node instanceof Element) {
                                Element ele = (Element) node;
                                // Bean定义的Document的元素节点使用的是Spring默认的XML命名空间
                                if (delegate.isDefaultNamespace(ele)) {
                                   // 使用Spring的Bean规则解析元素节点(默认解析规则)
                                    parseDefaultElement(ele, delegate);
                                }
                                else {
                                   // 没有使用Spring默认的XML命名空间,则使用用户自定义的解析规则解析元素节点
                                    delegate.parseCustomElement(ele);
                                }
                            }
                        }
                    }
                    else {
                      // Document 的根节点没有使用Spring默认的命名空间,则使用用户自定义的解析规则解析
                        delegate.parseCustomElement(root);
                    }
                }

迭代 root 元素的所有子节点,对其进行判断,若节点为默认命名空间,则ID调用 parseDefaultElement() 开启默认标签的解析注册过程,否则调用 parseCustomElement() 开启自定义标签的解析注册过程。

标签解析

若定义的元素节点使用的是 Spring 默认命名空间,则调用 parseDefaultElement() 进行默认标签解析,如下:

                private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
                   // 如果元素节点是<Import>导入元素,进行导入解析
                    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
                        importBeanDefinitionResource(ele);
                    }
                    // 如果元素节点是<Alias>别名元素,进行别名解析
                    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
                        processAliasRegistration(ele);
                    }
                    // 如果元素节点<Bean>元素,则进行Bean解析注册
                    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
                        processBeanDefinition(ele, delegate);
                    }
            
                    // // 如果元素节点<Beans>元素,则进行Beans解析
                    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
                        // recurse
                        doRegisterBeanDefinitions(ele);
                    }
                }

对四大标签:import、alias、bean、beans 进行解析,其中 bean 标签的解析为核心工作。关于各个标签的解析过程见如下文章:

对于默认标签则由 parseCustomElement() 负责解析。

                public BeanDefinition parseCustomElement(Element ele) {
                    return parseCustomElement(ele, null);
                }
            
                public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
                    String namespaceUri = getNamespaceURI(ele);
                    if (namespaceUri == null) {
                        return null;
                    }
                    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
                    if (handler == null) {
                        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                        return null;
                    }
                    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
                }

获取节点的 namespaceUri,然后根据该 namespaceuri 获取相对应的 Handler,调用 Handler 的 parse() 方法即完成自定义标签的解析和注入。想了解更多参考:【死磕Spring】----- IOC 之解析自定义标签

注册 BeanDefinition

经过上面的解析,则将 Document 对象里面的 Bean 标签解析成了一个个的 BeanDefinition ,下一步则是将这些 BeanDefinition 注册到 IOC 容器中。动作的触发是在解析 Bean 标签完成后,如下:

                protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
                    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
                    if (bdHolder != null) {
                        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
                        try {
                            // Register the final decorated instance.
                            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
                        }
                        catch (BeanDefinitionStoreException ex) {
                            getReaderContext().error("Failed to register bean definition with name '" +
                                    bdHolder.getBeanName() + "'", ele, ex);
                        }
                        // Send registration event.
                        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
                    }
                }

调用 BeanDefinitionReaderUtils.registerBeanDefinition() 注册,其实这里面也是调用 BeanDefinitionRegistry 的 registerBeanDefinition()来注册 BeanDefinition ,不过最终的实现是在 DefaultListableBeanFactory 中实现,如下:

                @Override
                public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                        throws BeanDefinitionStoreException {
            
                  // 省略一堆校验
            
                    BeanDefinition oldBeanDefinition;
            
                    oldBeanDefinition = this.beanDefinitionMap.get(beanName);
                      // 省略一堆 if
                        this.beanDefinitionMap.put(beanName, beanDefinition);
                    }
                    else {
                        if (hasBeanCreationStarted()) {
                            // Cannot modify startup-time collection elements anymore (for stable iteration)
                            synchronized (this.beanDefinitionMap) {
                                this.beanDefinitionMap.put(beanName, beanDefinition);
                                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                                updatedDefinitions.addAll(this.beanDefinitionNames);
                                updatedDefinitions.add(beanName);
                                this.beanDefinitionNames = updatedDefinitions;
                                if (this.manualSingletonNames.contains(beanName)) {
                                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                                    updatedSingletons.remove(beanName);
                                    this.manualSingletonNames = updatedSingletons;
                                }
                            }
                        }
                        else {
                            // Still in startup registration phase
                            this.beanDefinitionMap.put(beanName, beanDefinition);
                            this.beanDefinitionNames.add(beanName);
                            this.manualSingletonNames.remove(beanName);
                        }
                        this.frozenBeanDefinitionNames = null;
                    }
            
                    if (oldBeanDefinition != null || containsSingleton(beanName)) {
                        resetBeanDefinition(beanName);
                    }
                }

这段代码最核心的部分是这句 this.beanDefinitionMap.put(beanName, beanDefinition) ,所以注册过程也不是那么的高大上,就是利用一个 Map 的集合对象来存放,key 是 beanName,value 是 BeanDefinition。

至此,整个 IOC 的初始化过程就已经完成了,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IOC 容器中,都已经完美地完成了。现在 IOC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。最后用一张流程图来结束这篇总结之文。

202202131350414872.png

更多阅读:


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文