zoukankan      html  css  js  c++  java
  • Spring IOC容器的初始化—(一)Resource定位

    前言

        上一篇博文“ Spring IOC是怎样启动的 ”中提到了refresh()方法,这个就是容器初始化的入口。容器初始化共有三个阶段:

    第一阶段:Resource定位

    第二阶段:BeanDefinition解析

    第三阶段:BeanDefinition注册

       这一篇我们讲第一阶段Resource定位。

    阅读目录

    •     1.XmlWebApplicationContext的继承体系图
    •     2.refresh()方法
    •     3.Resource组件

    1.  XmlWebApplicationContext的继承体系图

    我们知道Spring IOC容器的默认实现类是XmlWebApplicationContext,下图是ApplicationContext的继承体系,至于BeanFactory的分支,以后再研究

    BeanFactory or ApplicationContext?

    BeanFactory和ApplicationContext都是实现IoC容器的基础接口。Application是BeanFactory的子接口,包含了BeanFactory的功能,同时增加了对Transactions和AOP的支持。所以官方更推荐开发者使用ApplicationContext及其子类实现IoC容器。特别地,Spring在实现时,大量使用ApplicationContext实现BeanPostProcessor extension point。

    2.refresh()方法

    我们追溯XmlWebApplicationContext的refresh()方法,依据继承体系图,追踪源码至AbastractApplicationContext的refresh()方法。

    refresh()是一个模板方法,执行多个方法,而且提供了各(protected)方法的(默认)实现,其子类可以重写它们 

    模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类(使用protected方法)可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 

    refresh函数中调用了多个方法,这里先不详细讲解每一个方法,可以先通过英文注释大概了解各方法的作用。

                                                                        AbastractApplicationContext的refresh()方法

    @Override
        public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                prepareRefresh();
    
                // Tell the subclass to refresh the internal bean factory.

    //告诉子类启动refreshBeanFactory()方法,bean定义资源文件的载入从子类的refreshBeanFactory()方法启动【重点】
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);
    
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);
    
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);
    
                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);
    
                    // Initialize message source for this context.
                    initMessageSource();
    
                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();
    
                    // Initialize other special beans in specific context subclasses.
                    onRefresh();
    
                    // Check for listener beans and register them.
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    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.
                    destroyBeans();
    
                    // Reset 'active' flag.
                    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...
                    resetCommonCaches();
                }
            }
        }

    这个refresh()方法就是SpringIOC 容器的初始化全过程。那么我们先关注obtainFreshBeanFactory()这个方法,因为这个方法里面会实现我们上述的Resource定位及载入解析和注册。

    2.1 obtainFreshBeanFactory()方法

    obtainFreshBeanFactory()方法完成了容器初始化的最重要最基础的功能,Bean定义资源的Resource定位、载入解析和注册。

                                                    AbstractApplicationContext的obtainFreshBeanFactory()方法

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            refreshBeanFactory();
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            if (logger.isDebugEnabled()) {
                logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
            }
            return beanFactory;
        }

    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

    public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

    这里使用了委派设计模式,obtainFreshBeanFactory()中调用了两个抽象方法,定义了obtainFreshBeanFactory的算法骨架,实际的行为交给其子类(AbstractRefreshableApplicationContext)实现。

                                        AbstractRefreshableApplicationContext的refreshBeanFactory()方法

    @Override
        protected final void refreshBeanFactory() throws BeansException {
            if (hasBeanFactory()) { //如果已经有容器,销 毁 容器中的bean,关闭容器  以保证在refresh之后使用的是新建立起来的IoC容器
                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); } }

    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;

     

    由于是web容器启动的,追溯源码,我们看到XmlWebApplicationContext实现了loadBeanDefinitions()方法。

                              XmlWebApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法

    @Override
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // Create a new XmlBeanDefinitionReader for the given BeanFactory.
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
            // Configure the bean definition reader with this context's
            // resource loading environment.
            beanDefinitionReader.setEnvironment(getEnvironment());
            beanDefinitionReader.setResourceLoader(this);
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
            // Allow a subclass to provide custom initialization of the reader,
            // then proceed with actually loading the bean definitions.
            initBeanDefinitionReader(beanDefinitionReader);
            loadBeanDefinitions(beanDefinitionReader);
        }

    上面的XMLBeanDefinitionReader我们先不理睬,后面BeanDefinition解析的时候会讲到这个类,我们直接看loadBeanDefinitions方法,这里是具体载入bean信息,那么要载入就要知道bean的定义文件在哪,所以Resource定位还要继续跟下去,来看这个方法的源码:

                                XmlWebApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    //获取ConfigLocations,也就是配置文件路径 String[] configLocations
    = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { reader.loadBeanDefinitions(configLocation); } } }

    看到这里大家还记得之前我们的web.xml里面配置的context-param吗?还记得我们的ContextLoader里面设置了wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM))吗?这里我们就取到了xml的位置。但是这里我们并没有看到我们一直在说的Resource,那么继续看代码:经过了一次重载的方法,我们最终可以看到这个方法:

                                  AbstractBeanDefinitionReader的loadBeanDefinitions方法

    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    //这里得到当前定义的ResourceLoader,默认的使用DefaultResourceLoader ResourceLoader resourceLoader
    = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } //下面这堆是对Resource的路径模式进行解析(像我们在web.xml里面有可能使用通配符等),得到需要的Resource集合,这些Resource集合指向了我们定义好的BeanDefinition的信息,可以是多个文件。 if (resourceLoader instanceof ResourcePatternResolver) { try {
    //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源(Resource) Resource[] resources
    = ((ResourcePatternResolver) resourceLoader).getResources(location); 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. 这里通过ResourceLoader来完成位置定位 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }

    那么对于取得Resource的具体过程,((ResourcePatternResolver) resourceLoader).getResources(location)这个方法大家点开看看就可以知道,是PathMatchingResourcePatternResolver中的实现的,它具体里面就是针对我们配置的是否以“classpath*:” 开头分别处理。对于resourceLoader.getResource(location)方法,具体是交给继承ResourceLoader的子类完成的。这里不再多述了。

    总结一下,容器初始化各个父类方法调用,不然就更懵了!

                                                                                                    

    3.Resource组件

         Resource组件与ResourceLoader组件一起工作,将字符串格式指示的资源解析为Resource对象。事实上ResourceLoader是Resource的工厂类, ResourceLoader的核心工作就是解析location。
    location示例:”classpath:applicationContext.xml”,”classpath:applicationContext-.xml”,”file:/some/resource/path/myTemplate.txt”,"http://XX/resource/path/myTemplate.txt“  ResourceLoader根据所指示的前缀返回特定的Resource对象。看下Resource的结构图

                                                                  

  • 相关阅读:
    cas 单点登录(SSO)实验之二: cas-client
    前端要给力之:语句在JavaScript中的值
    【Linux】Tomcat安装及一个服务器配置多个Tomcat
    【Linux】 JDK安装及配置 (tar.gz版)
    Android自动化测试中AccessibilityService获取控件信息(1)
    Android自动化框架 模拟操作 模拟测试
    Android应用程序如何调用shell脚本(一)
    关于lidroid xUtils 开源项目
    android 开源项目列表【持续整理中。。。】
    android开发常用组件【持续更新中。。。】
  • 原文地址:https://www.cnblogs.com/whx7762/p/7772278.html
Copyright © 2011-2022 走看看