2021-05-27 23:42  阅读(300)
文章分类:Spring 源码解析 文章标签:SpringSpring 源码
©  原文作者:Java学习部落 原文地址:https://www.zhihu.com/people/tobetopjavaer/posts

IOC 容器的初始化包括 BeanDefinitionResource 定位加载注册这三个基本的过程。

我们以ApplicationContext 为例讲解,ApplicationContext 系列容器也许是我们最熟悉的,因为 Web 项目中使用的 XmlWebApplicationContext 就属于这个继承体系,还有 ClasspathXmlApplicationContext 等,其继承体系如下图所示:

202105272342141421.png

ApplicationContext 允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。

对于 Bean 的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的 Spring应用提供了一个共享的 Bean 定义环境。

1、寻找入口

还有一个我们用的比较多的 ClassPathXmlApplicationContext,通过 main()方法启动:

    ApplicationContext app = new ClassPathXmlApplicationContext("application.xml"); 

先看其构造函数的调用:

    public ClassPathXmlApplicationContext(String configLocation) throws BeansException { 
        this(new String[]{configLocation}, true, (ApplicationContext)null); 
    }

其实际调用的构造函数为:

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable 
    ApplicationContext parent) throws BeansException { 
        super(parent); 
        this.setConfigLocations(configLocations); 
        if(refresh) { 
            this.refresh(); 
        } 
    }

还 有 像 AnnotationConfigApplicationContext 、 FileSystemXmlApplicationContext 、 XmlWebApplicationContext 等都继承自父容器 AbstractApplicationContext主要用到了装饰器模式 和策略模式,最终都是调用 refresh()方法。

2、获得配置路径

通 过 分 析 ClassPathXmlApplicationContext 的 源 代 码 可 以 知 道 , 在 创 建 ClassPathXmlApplicationContext 容器时,构造方法做以下两项重要工作:

首先,调用父类容器的构造方法(super(parent)方法)为容器设置好 Bean 资源加载器。

然后,再调用父类AbstractRefreshableConfigApplicationContext 的 setConfigLocations(configLocations)方法设置 Bean 配置信息的定位路径。

通 过 追 踪 ClassPathXmlApplicationContext 的 继 承 体 系 , 发 现 其 父 类 的 父 类 AbstractApplicationContext 中初始化 IOC 容器所做的主要源码如下:

    public abstract class AbstractApplicationContext extends DefaultResourceLoader 
    implements ConfigurableApplicationContext { 
        //静态初始化块,在整个容器创建过程中只执行一次 
        static { 
            //为了避免应用程序在 Weblogic8.1 关闭时出现类加载异常加载问题,加载 IOC 容 
            //器关闭事件(ContextClosedEvent)类 
            ContextClosedEvent.class.getName(); 
        }
        public AbstractApplicationContext() { 
            this.resourcePatternResolver = getResourcePatternResolver(); 
        }
        public AbstractApplicationContext(@Nullable ApplicationContext parent) { 
            this(); 
            setParent(parent); 
        }
        //获取一个 Spring Source 的加载器用于读入 Spring Bean 配置信息 
        protected ResourcePatternResolver getResourcePatternResolver() { 
            //AbstractApplicationContext 继承 DefaultResourceLoader,因此也是一个资源加载器 
            //Spring 资源加载器,其 getResource(String location)方法用于载入资源 
            return new PathMatchingResourcePatternResolver(this); 
        }
        ... 
    }

AbstractApplicationContext 的默认构造方法中有调用 PathMatchingResourcePatternResolver 的 构造方法创建 Spring 资源加载器:

    public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { 
        Assert.notNull(resourceLoader, "ResourceLoader must not be null"); 
        //设置 Spring 的资源加载器 
        this.resourceLoader = resourceLoader; 
    }

在设置容器的资源加载器之后,接下来 ClassPathXmlApplicationContext 执行setConfigLocations()方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean配置信息的定位,该方法的源码如下:

    //处理单个资源文件路径为一个字符串的情况 
    public void setConfigLocation(String location) { 
        //String CONFIG_LOCATION_DELIMITERS = ",; /t/n"; 
        //即多个资源文件路径之间用” ,; \t\n”分隔,解析成数组形式 
        setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)); 
    }
    /**
    * Set the config locations for this application context. 
    * <p>If not set, the implementation may use a default as appropriate. 
    */ 
    //解析 Bean 定义资源文件的路径,处理多个资源文件字符串数组 
    public void setConfigLocations(@Nullable String... locations) { 
        if (locations != null) { 
            Assert.noNullElements(locations, "Config locations must not be null"); 
            this.configLocations = new String[locations.length]; 
            for (int i = 0; i < locations.length; i++) { 
            // resolvePath 为同一个类中将字符串解析为路径的方法 
            this.configLocations[i] = resolvePath(locations[i]).trim(); 
            } 
    }else { 
        this.configLocations = null; 
        } 
    }

通过这两个方法的源码我们可以看出,我们既可以使用一个字符串来配置多个 Spring Bean 配置信息,也可以使用字符串数组,即下面两种方式都是可以的:

    ClassPathResource res = new ClassPathResource("a.xml,b.xml"); 

多个资源文件路径之间可以是用” , ; \t\n”等分隔。

    ClassPathResource res =new ClassPathResource(new String[]{"a.xml","b.xml"}); 

至此,SpringIOC 容器在初始化时将配置的 Bean 配置信息定位为 Spring 封装的Resource。

3、开始启动

SpringIOC 容器对 Bean 配置资源的载入是从 refresh()函数开始的,refresh()是一个模板方法,规定了IOC 容 器 的 启 动 流 程 , 有 些 逻 辑 要 交 给 其 子 类 去 实 现 。

它 对 Bean 配 置 资 源 进 行 载 入 ClassPathXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh()函数启动整个 IOC 容器对 Bean 定义的载入过程,现在我们来详细看看 refresh()中的逻辑处理:

    @Override 
    public void refresh() throws BeansException, IllegalStateException { 
        synchronized (this.startupShutdownMonitor) { 
        // Prepare this context for refreshing. 
        //1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 
        prepareRefresh(); 
        // Tell the subclass to refresh the internal bean factory. 
        //2、告诉子类启动 refreshBeanFactory()方法,Bean 定义资源文件的载入从 
        //子类的 refreshBeanFactory()方法启动 
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 
        // Prepare the bean factory for use in this context. 
        //3、为 BeanFactory 配置容器特性,例如类加载器、事件处理器等 
        prepareBeanFactory(beanFactory); 
        try { 
            // Allows post-processing of the bean factory in context subclasses. 
            //4、为容器的某些子类指定特殊的 BeanPost 事件处理器 
            postProcessBeanFactory(beanFactory); 
            // Invoke factory processors registered as beans in the context. 
            //5、调用所有注册的 BeanFactoryPostProcessor 的 Bean 
            invokeBeanFactoryPostProcessors(beanFactory); 
            // Register bean processors that intercept bean creation. 
            //6、为 BeanFactory 注册 BeanPost 事件处理器. 
            //BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件 
            registerBeanPostProcessors(beanFactory); 
            // Initialize message source for this context. 
            //7、初始化信息源,和国际化相关. 
            initMessageSource(); 
            // Initialize event multicaster for this context. 
            //8、初始化容器事件传播器. 
            initApplicationEventMulticaster(); 
            // Initialize other special beans in specific context subclasses. 
            //9、调用子类的某些特殊 Bean 初始化方法 
            onRefresh(); 
            // Check for listener beans and register them. 
            //10、为事件传播器注册事件监听器.
            registerListeners(); 
            // Instantiate all remaining (non-lazy-init) singletons. 
            //11、初始化所有剩余的单例 Bean 
            finishBeanFactoryInitialization(beanFactory); 
            // Last step: publish corresponding event. 
            //12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件 
            finishRefresh(); 
        }catch (BeansException ex) { 
            if (logger.isWarnEnabled()) { 
            logger.warn("Exception encountered during context initialization - " + 
            "cancelling refresh attempt: " + ex); 
            }
        // Destroy already created singletons to avoid dangling resources. 
        //13、销毁已创建的 Bean 
        destroyBeans(); 
        // Reset 'active' flag. 
        //14、取消 refresh 操作,重置容器的同步标识. 
        cancelRefresh(ex); 
        // Propagate exception to caller. 
        throw ex; 
        }finally { 
            // Reset common introspection caches in Spring's core, since we 
            // might not ever need metadata for singleton beans anymore... 
            //15、重设公共缓存 
            resetCommonCaches(); 
            } 
        } 
    }

refresh()方法主要为 IOC 容器 Bean 的生命周期管理提供条件,Spring IOC 容器载入 Bean 配置信息从 其 子 类 容 器 的 refreshBeanFactory() 方 法 启 动 。

所 以 整 个 refresh() 中 “ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();”

这句以后代码的 都是注册容器的信息源和生命周期事件,我们前面说的载入就是从这句代码开始启动。

refresh()方法的主要作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IOC 容器。

它类似于对 IOC 容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 配置资源进行载入。

4、创建容器

obtainFreshBeanFactory()方法调用子类容器的 refreshBeanFactory()方法,启动容器载入 Bean 配置

信息的过程,代码如下:

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { 
        //这里使用了委派设计模式,父类定义了抽象的 refreshBeanFactory()方法,具体实现调用子类容器的 refreshBeanFactory()方 
        法 
        refreshBeanFactory(); 
        ConfigurableListableBeanFactory beanFactory = getBeanFactory(); 
        if (logger.isDebugEnabled()) { 
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); 
        }
       return beanFactory; 
    }

AbstractApplicationContext 类中只抽象定义了 refreshBeanFactory()方法,容器真正调用的是其子类 AbstractRefreshableApplicationContext 实现的 refreshBeanFactory()方法,方法的源码如下:

    protected final void refreshBeanFactory() throws BeansException { 
        //如果已经有容器,销毁容器中的 bean,关闭容器 
        if (hasBeanFactory()) { 
            destroyBeans(); 
            closeBeanFactory(); 
        }
        try { 
            //创建 IOC 容器 
            DefaultListableBeanFactory beanFactory = createBeanFactory(); 
            beanFactory.setSerializationId(getId()); 
            //对 IOC 容器进行定制化,如设置启动参数,开启注解的自动装配等 
            customizeBeanFactory(beanFactory); 
            //调用载入 Bean 定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的 loadBeanDefinitions 方法,具体的实现调用子类容器 
            loadBeanDefinitions(beanFactory); 
            synchronized (this.beanFactoryMonitor) { 
            this.beanFactory = beanFactory;
            } 
        }catch (IOException ex) { 
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        } 
    }

在这个方法中,先判断 BeanFactory 是否存在,如果存在则先销毁 beans 并关闭beanFactory,接着 创建 DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载 bean 定义。

5、载入配置路径

AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法,容器真正调用的是其子类 AbstractXmlApplicationContext 对该方法的实现,AbstractXmlApplicationContext 的主要源码如下:

loadBeanDefinitions() 方 法 同 样 是 抽 象 方 法 , 是 由 其 子 类 实 现 的 , 也 即 在 AbstractXmlApplicationContext 中。

    public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { 
        ... 
        //实现父类抽象的载入 Bean 定义方法 
        @Override 
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException 
        { 
            //创建 XmlBeanDefinitionReader,即创建 Bean 读取器,并通过回调设置到容器中去,容器使用该读取器读取 Bean 配置资源 
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); 
            //为 Bean 读取器设置 Spring 资源加载器,AbstractXmlApplicationContext 的 
            //祖先父类 AbstractApplicationContext 继承 DefaultResourceLoader,因此,容器本身也是一个资源加载器 
            beanDefinitionReader.setEnvironment(this.getEnvironment()); 
            beanDefinitionReader.setResourceLoader(this); 
            //为 Bean 读取器设置 SAX xml 解析器 
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); 
            //当 Bean 读取器读取 Bean 定义的 Xml 资源文件时,启用 Xml 的校验机制 
            initBeanDefinitionReader(beanDefinitionReader); 
            //Bean 读取器真正实现加载的方法 
            loadBeanDefinitions(beanDefinitionReader); 
        }
        protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) { 
            reader.setValidating(this.validating); 
        }
        //Xml Bean 读取器加载 Bean 配置资源 
        protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { 
            //获取 Bean 配置资源的定位 
            Resource[] configResources = getConfigResources(); 
            if (configResources != null) { 
            //Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位的 Bean 配置资源 
            reader.loadBeanDefinitions(configResources); 
            }
            // 如果子类中获取的 Bean 配置资源定位为空,则获取 ClassPathXmlApplicationContext 
            // 构造方法中 setConfigLocations 方法设置的资源 
            String[] configLocations = getConfigLocations(); 
            if (configLocations != null) { 
            //Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位 
            //的 Bean 配置资源 
            reader.loadBeanDefinitions(configLocations); 
            } 
        }
        //这里又使用了一个委托模式,调用子类的获取 Bean 配置资源定位的方法 
        //该方法在 ClassPathXmlApplicationContext 中进行实现,对于我们 
        //举例分析源码的 ClassPathXmlApplicationContext 没有使用该方法 
        @Nullable 
        protected Resource[] getConfigResources() { 
            return null; 
        } 
    }

以 XmlBean 读取器的其中一种策略 XmlBeanDefinitionReader 为例。

XmlBeanDefinitionReader 调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions()方法读取Bean配置资源。

由于我们使用 ClassPathXmlApplicationContext 作为例子分析,因此 getConfigResources 的返回值为 null,因此程序执行 reader.loadBeanDefinitions(configLocations)分支。

6、分配路径处理策略

在 XmlBeanDefinitionReader 的抽象父类 AbstractBeanDefinitionReader 中定义了载入过程。

AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源码如下:

    //重载方法,调用下面的 loadBeanDefinitions(String, Set<Resource>);方法 
    @Override 
    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { 
        return loadBeanDefinitions(location, null); 
    }
    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws 
    BeanDefinitionStoreException { 
        //获取在 IOC 容器初始化过程中设置的资源加载器 
        ResourceLoader resourceLoader = getResourceLoader(); 
        if (resourceLoader == null) { 
            throw new BeanDefinitionStoreException( 
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); 
        }
        if (resourceLoader instanceof ResourcePatternResolver) { 
            // Resource pattern matching available. 
            try { 
                //将指定位置的 Bean 配置信息解析为 Spring IOC 容器封装的资源 
                //加载多个指定位置的 Bean 配置信息 
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 
                //委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能 
                int loadCount = loadBeanDefinitions(resources); 
                if (actualResources != null) { 
                    for (Resource resource : resources) { 
                        actualResources.add(resource); 
                    } 
                }
                if (logger.isDebugEnabled()) { 
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); 
                }
                return loadCount; 
            }catch (IOException ex) { 
                throw new BeanDefinitionStoreException( 
                "Could not resolve bean definition resource pattern [" + location + "]", ex); 
            } 
        }else { 
            // Can only load single resources by absolute URL. 
            //将指定位置的 Bean 配置信息解析为 Spring IOC 容器封装的资源 
            //加载单个指定位置的 Bean 配置信息 
            Resource resource = resourceLoader.getResource(location); 
            //委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能 
            int loadCount = loadBeanDefinitions(resource); 
            if (actualResources != null) { 
                actualResources.add(resource); 
            }
            if (logger.isDebugEnabled()) { 
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); 
            }
            return loadCount; 
            } 
        }
        //重载方法,调用 loadBeanDefinitions(String); 
        @Override 
        public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { 
            Assert.notNull(locations, "Location array must not be null"); 
            int counter = 0; 
            for (String location : locations) { 
                counter += loadBeanDefinitions(location); 
            }
            return counter; 
    }

AbstractRefreshableConfigApplicationContext 的 loadBeanDefinitions(Resource...resources) 方法实际上是调用 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法。

从对 AbstractBeanDefinitionReader 的 loadBeanDefinitions()方法源码分析可以看出该方法就做了两件事:

首先,调用资源加载器的获取资源方法 resourceLoader.getResource(location),获取到要加载的资源。

其次,真正执行加载功能是其子类 XmlBeanDefinitionReader 的 loadBeanDefinitions()方法。

在loadBeanDefinitions()方法中调用了 AbstractApplicationContext 的 getResources()方法,跟进去之后发现 getResources()方法其实定义在 ResourcePatternResolver 中,此时,我们有必要来看一下ResourcePatternResolver 的全类图:

202105272342146693.png

从上面可以看到 ResourceLoader 与 ApplicationContext 的继承关系,可以看出其实际调用的是DefaultResourceLoader中的getSource() 方法 定位Resource。

因 为 ClassPathXmlApplicationContext 本身就是 DefaultResourceLoader 的实现类,所以此时又回到了 ClassPathXmlApplicationContext 中来。

7、解析配置文件路径

XmlBeanDefinitionReader通 过 调 用ClassPathXmlApplicationContext的父类 DefaultResourceLoader 的 getResource()方法获取要加载的资源,其源码如下 :

    //获取 Resource 的具体实现方法 
    @Override 
    public Resource getResource(String location) { 
        Assert.notNull(location, "Location must not be null"); 
        for (ProtocolResolver protocolResolver : this.protocolResolvers) { 
            Resource resource = protocolResolver.resolve(location, this); 
            if (resource != null) { 
            return resource; 
            } 
        }
        //如果是类路径的方式,那需要使用 ClassPathResource 来得到 bean 文件的资源对象 
        if (location.startsWith("/")) { 
            return getResourceByPath(location); 
        }else if (location.startsWith(CLASSPATH_URL_PREFIX)) { 
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); 
        }else { 
            try { 
                // 如果是 URL 方式,使用 UrlResource 作为 bean 文件的资源对象 
                URL url = new URL(location); 
                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); 
            }catch (MalformedURLException ex) { 
                //如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用 
                //容器本身的 getResourceByPath 方法获取 Resource 
                return getResourceByPath(location); 
            } 
        } 
    }

DefaultResourceLoader 提供了 getResourceByPath()方法的实现,就是为了处理既不是 classpath标识,又不是 URL 标识的 Resource 定位这种情况。

    protected Resource getResourceByPath(String path) { 
        return new ClassPathContextResource(path, getClassLoader()); 
    }

在 ClassPathResource 中完成了对整个路径的解析。这样,就可以从类路径上对 IOC 配置文件进行加载,当然我们可以按照这个逻辑从任何地方加载,在 Spring 中我们看到它提供的各种资源抽象,比如 ClassPathResource、URLResource、FileSystemResource 等来供我们使用。

上面我们看到的是定位Resource 的一个过程,而这只是加载过程的一部分。例如 FileSystemXmlApplication 容器就重写了getResourceByPath()方法:

    @Override 
    protected Resource getResourceByPath(String path) { 
        if (path.startsWith("/")) { 
        path = path.substring(1); 
        }
        //这里使用文件系统资源对象来定义 bean 文件 
        return new FileSystemResource(path); 
    }

通过子类的覆盖,巧妙地完成了将类路径变为文件路径的转换。

8、开始读取配置内容

继续回到 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …)方法看到代表 bean 文件的资源定义以后的载入过程。

    //XmlBeanDefinitionReader 加载资源的入口方法 
    @Override 
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 
        //将读入的 XML 资源进行特殊编码处理 
        return loadBeanDefinitions(new EncodedResource(resource)); 
    }
    //这里是载入 XML 形式 Bean 配置信息方法 
    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 { 
                //关闭从 Resource 中得到的 IO 流 
                inputStream.close(); 
            } 
        }
        ... 
    }
    //从特定 XML 文件中实际载入 Bean 配置资源的方法 
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 
    throws BeanDefinitionStoreException { 
        try { 
            //将 XML 文件转换为 DOM 对象,解析过程由 documentLoader 实现 
            Document doc = doLoadDocument(inputSource, resource); 
            //这里是启动对 Bean 定义解析的详细过程,该解析过程会用到 Spring 的 Bean 配置规则 
            return registerBeanDefinitions(doc, resource); 
        }
        ... 
    } 

通过源码分析,载入 Bean 配置信息的最后一步是将 Bean 配置信息转换为 Document 对象,该过程由documentLoader()方法实现。

9、准备文档对象

DocumentLoader 将 Bean 配置资源转换成 Document 对象的源码如下:

    //使用标准的 JAXP 将载入的 Bean 配置资源转换成 document 对象 
    @Override 
    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); 
    }
    
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) 
    throws ParserConfigurationException { 
        //创建文档解析工厂 
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
        factory.setNamespaceAware(namespaceAware); 
        //设置解析 XML 的校验 
        if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { 
            factory.setValidating(true); 
            if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { 
                // Enforce namespace aware for XSD... 
                factory.setNamespaceAware(true); 
                try { 
                    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); 
                }catch (IllegalArgumentException ex) { 
                    ParserConfigurationException pcex = new ParserConfigurationException( 
                    "Unable to validate using XSD: Your JAXP provider [" + factory + 
                    "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + 
                    "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); 
                    pcex.initCause(ex); 
                    throw pcex; 
                } 
            } 
        }
        return factory; 
    }

上面的解析过程是调用 JavaEE 标准的 JAXP 标准进行处理。

至此 Spring IOC 容器根据定位的 Bean 配置信息,将其加载读入并转换成为 Document 对象过程完成。

接下来我们要继续分析 Spring IOC 容器将载入的 Bean 配置信息转换为 Document 对象之后,是如何将其解析为 Spring IOC 管理的 Bean 对象并将其注册到容器中的。

10、分配解析策略

XmlBeanDefinitionReader 类中的 doLoadBeanDefinition()方法是从特定 XML 文件中实际载入Bean 配置资源的方法,该方法在载入 Bean 配置资源之后将其转换为 Document 对象。

接下来调用registerBeanDefinitions()启动Spring IOC容器对Bean定 的解析过程 , registerBeanDefinitions()方法源码如下:

    //按照 Spring 的 Bean 语义要求将 Bean 配置资源解析并转换为容器内部数据结构 
    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)); 
    
        //统计解析的 Bean 数量 
        return getRegistry().getBeanDefinitionCount() - countBefore; 
    }

Bean 配置资源的载入解析分为以下两个过程:

首先,通过调用 XML 解析器将 Bean 配置信息转换得到 Document 对象,但是这些 Document 对象并没有按照 Spring 的 Bean 规则进行解析。

这一步是载入的过程 。

其次,在完成通用的 XML 解析之后,按照 Spring Bean 的定义规则对 Document 对象进行解析,其解析过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader 中实现。

11、将配置载入内存

BeanDefinitionDocumentReader 接口通过 registerBeanDefinitions()方法调用其实现类DefaultBeanDefinitionDocumentReader 对 Document 对象进行解析,解析的代码如下:

202105272342151345.png202105272342154157.png202105272342160309.png2021052723421633211.png2021052723421667213.png2021052723421706515.png

通过上述 Spring IOC 容器对载入的 Bean 定义 Document 解析可以看出,我们使用 Spring 时,在Spring 配置文件中可以使用元素来导入 IOC 容器所需要的其他资源,Spring IOC 容器在解析时会首先将指定导入的资源加载进容器中。

使用别名时,Spring IOC 容器首先将别名元素所定义的别名注册到容器中。

对于既不是元素,又不是元素的元素,即 Spring 配置文件中普通的元素的解析由 BeanDefinitionParserDelegate 类的parseBeanDefinitionElement()方法来实现。

这个解析的过程非常复杂。

12、载入元素

Bean 配置信息中的元素解析在DefaultBeanDefinitionDocumentReader 中已经完成,对 Bean 配置信息中使用最多的元素交由 BeanDefinitionParserDelegate来解析, 其解析实现的源码如下:

2021052723421741017.png2021052723421779019.png2021052723421832321.png2021052723421865923.png2021052723421900125.png

只要使用过 Spring,对 Spring 配置文件比较熟悉的人,通过对上述源码的分析,就会明白我们在 Spring配置文件中元素的中配置的属性就是通过该方法解析和设置到 Bean 中去的。

注意:在解析元素过程中没有创建和实例化 Bean 对象,只是创建了 Bean 对象的定义类BeanDefinition,将元素中的配置信息设置到 BeanDefinition 中作为记录,当依赖注入时才使用这些记录信息创建和实例化具体的 Bean 对象。

上面方法中一些对一些配置如元信息(meta)、qualifier 等的解析,我们在 Spring 中配置时使用的也不多,我们在使用 Spring 的元素时,配置最多的是属性,因此我们下面继续分析源码,了解 Bean 的属性在解析时是如何设置的。

13、载入元素

BeanDefinitionParserDelegate 在解析调用 parsePropertyElements()方法解析元素中的属性子元素,解析源码如下:

2021052723421926127.png2021052723421962929.png2021052723422018431.png2021052723422075733.png

通过对上述源码的分析,我们可以了解在 Spring 配置文件中,元素中元素的相关配置是如何处理的:

1、ref 被封装为指向依赖对象一个引用。

2、value 配置都会封装成一个字符串类型的对象。

3、ref 和 value 都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用与所引用的属性关联起来。

在方法的最后对于元素的子元素通过 parsePropertySubElement ()方法解析,我们继续分析该方法的源码,了解其解析过程。

14、载入的子元素

在 BeanDefinitionParserDelegate 类中的 parsePropertySubElement()方法对中的子元素解析,源码如下:

2021052723422101335.png2021052723422122937.png2021052723422159839.png

通过上述源码分析,我们明白了在 Spring 配置文件中,对元素中配置的 array、list、set、map、prop 等各种集合子元素的都通过上述方法解析,生成对应的数据对象,比如 ManagedList、 ManagedArray、ManagedSet 等,这些 Managed 类是 Spring 对象 BeanDefiniton 的数据封装,对集合数据类型的具体解析有各自的解析方法实现,解析方法的命名非常规范,一目了然,我们对集合元素的解析方法进行源码分析,了解其实现过程。

15、载入的子元素

在 BeanDefinitionParserDelegate 类中的 parseListElement()方法就是具体实现解析元素中的集合子元素,源码如下:

2021052723422190641.png2021052723422234543.png

经过对 Spring Bean 配置信息转换的 Document 对象中的元素层层解析,Spring IOC 现在已经将 XML形式定义的 Bean 配置信息转换为 Spring IOC 所识别的数据结构——BeanDefinition,它是 Bean 配置信息中配置的 POJO 对象在 Spring IOC 容器中的映射,我们可以通过 AbstractBeanDefinition 为入口,看到了 IOC 容器进行索引、查询和操作。

通过 Spring IOC 容器对 Bean 配置资源的解析后,IOC 容器大致完成了管理 Bean 对象的准备工作,即初始化过程,但是最为重要的依赖注入还没有发生,现在在 IOC 容器中 BeanDefinition 存储的只是一些静态信息,接下来需要向容器注册 Bean 定义信息才能全部完成 IOC 容器的初始化过程

16、分配注册策略

让我们继续跟踪程序的执行顺序,接下来我们来分析DefaultBeanDefinitionDocumentReader 对Bean 定 义转 换的 Document 对 象解 析的 流程 中, 在其 parseDefaultElement() 方法中完成对Document 对 象 的 解 析 后 得 到 封 装 BeanDefinition的BeanDefinitionHold 对象 , 然后调用BeanDefinitionReaderUtils 的 registerBeanDefinition()方向IOC容器注册解析的Bean , BeanDefinitionReaderUtils 的注册的源码如下:

2021052723422276345.png

当调用 BeanDefinitionReaderUtils 向 IOC 容器注册解析的 BeanDefinition 时,真正完成注册功能的是DefaultListableBeanFactory。

17、向容器注册

DefaultListableBeanFactory 中 使 用 一 个 HashMap 的 集 合 对 象 存 放 IOC 容 器 中 注 册解析的 BeanDefinition,向 IOC 容器注册的主要源码如下:

2021052723422312847.png2021052723422361149.png2021052723422396151.png2021052723422435653.png

至此,Bean 配置信息中配置的 Bean 被解析过后,已经注册到 IOC 容器中,被容器管理起来,真正完成了 IOC 容器初始化所做的全部工作。现在 IOC 容器中已经建立了整个 Bean 的配置信息,这些BeanDefinition 信息已经可以使用,并且可以被检索,IOC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护。这些的注册的 Bean 定义信息是 IOC 容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 《Spring源码解析(四)》从源码深处体验Spring核心技术--基于Xml的IOC容器的初始化
上一篇
《Spring源码解析(三)》从源码深处体验Spring核心技术--IOC容器初体验
下一篇
《Spring源码解析(五)》从源码深处体验Spring核心技术--基于注解的IOC初始化