网络文章

IOC容器初始化过程

IOC容器的初始化分为三个过程实现:第一个过程是Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。第二个过程是BeanDefinition的载入过程。这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。第三个过程是向IOC容器注册

IOC容器的初始化分为三个过程实现:

  • 第一个过程是Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。

  • 第二个过程是BeanDefinition的载入过程。这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。

  • 第三个过程是向IOC容器注册这些BeanDefinition的过程,这个过程就是将前面的BeanDefition保存到HashMap中的过程。

上面提到的过程一般是不包括Bean的依赖注入的实现。在Spring中,Bean的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。下面的一张图描述了这三个过程调用的主要方法,图中的四个过程其实描述的是上面的第二个过程和第三个过程:

1 Resource定位

下面来看看主要的三个ApplicationContext的实现类是如何定位资源的,也就是找到我们通常所说“applicationContetx.xml”等配置文件的。

1.1 ClassPathXmlApplicationContext与FileSystemXmlApplicationContext

这两个类都是非Web容器时,常用的ApplicationContext类。他们很相似,所有的构造方法都在重载调用一段核心的代码。这段代码虽然很短,但是其中是一个很复杂的执行过程,它完成了IOC容器的初始化。

super(parent);  
        setConfigLocations(configLocations);  
        if (refresh) {  
            refresh();  
        }

这其中的setConfigLocations方法就是在进行资源定位。这个方法在AbstractRefreshableConfigApplicationContext类中实现。这个方法首先进行了非空了检验。这个Assert是Spring框架的一个工具类,这里面进行了一个非空判断。然后对这个路径进行了一些处理。这样就完成了资源的定位。这个定位其实就是使用者主动把配置文件的位置告诉Spring框架。

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++) {  
                this.configLocations[i] = resolvePath(locations[i]).trim();  
            }  
        }  
        else {  
            this.configLocations = null;  
        }

1.2 XmlWebApplicationContext

这个类是web容器初始化spring IOC容器的类。对于web应用来说,我们通常是不是直接去初始化这个容器的,它的装载是一个自动进行的过程。这是因为我们在web.xml中配置了这样一句话,这其实就是spring的入口

<listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>

(1)下面来看这个类ContextLoaderListener,从它的定义就能看出,这是一个ServletContextListener,它的核心方法就是下面的contextInitialized事件,也就是当web容器初始化的时候,spring容器也进行了初始化。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener
/** 
 * Initialize the root web application context. 
 */  
@Override  
public void contextInitialized(ServletContextEvent event) {  
    initWebApplicationContext(event.getServletContext());  
}

这个方法将servletContext作为参数传入,它的目标就是为了读取web.xml配置文件,找到我们对spring的配置。

(2)下面来看initWebApplicationContext方法,它完成了对webApplictionContext的初始化工作。这个方法里的有比较重要的几段代码,他们主要完成了webAppliction构建,参数的注入,以及保存
构建webApplictionContext

if (this.context == null) {  
                this.context = createWebApplicationContext(servletContext);  
            }

这段代码看字面意思就知道是新建了一个webApplicationContext。它是由一个工具类产生一个新的wac,这个方法中调用了determineContextClass方法,它决定了容器初始化为哪种类型的ApplicationContext,因为我们可以在web.xml中对这种类型进行指定。而如果没有指定的话,就将使用默认的XmlWebApplicationContext。

protected Class<?> determineContextClass(ServletContext servletContext) {  
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);  
        if (contextClassName != null) {  
            try {  
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());  
            }  
            catch (ClassNotFoundException ex) {  
                throw new ApplicationContextException(  
                        "Failed to load custom context class [" + contextClassName + "]", ex);  
            }  
        }  
        else {  
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());  
            try {  
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());  
            }  
            catch (ClassNotFoundException ex) {  
                throw new ApplicationContextException(  
                        "Failed to load default context class [" + contextClassName + "]", ex);  
            }  
        }  
    }
  • 注入参数,初始化这个空的容器 。这个过程的入口是configureAndRefreshWebApplicationContext这个方法中完成了wac的Id设置,将servletContext注入到wac中,还有最重要的方法,就是setConfigLocation.这里从web.xml中寻找指定的配置文件的位置,也就是我们通常配置的“contextConfigLocation”属性

String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);  
        if (configLocationParam != null) {  
            wac.setConfigLocation(configLocationParam);  
        }

那么如果没有指定呢?在XMLWebApplicationContext中这样一些常量,他们表示了配置文件的默认位置

/** Default config location for the root context */  
    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";  
  
    /** Default prefix for building a config location for a namespace */  
    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";  
  
    /** Default suffix for building a config location for a namespace */  
    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

  • spring容器初始化完成后,放入serverletContext中,这样在web容器中就可以拿到applicationContext

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

2 BeanDefinition载入

这个过程是最繁琐,也是最重要的一个过程。这一个过程分为以下几步,

  • 构造一个BeanFactory,也就是IOC容器

  • 调用XML解析器得到document对象 

  •  按照Spring的规则解析BeanDefition

对于以上过程,都需要一个入口,也就是前面提到的refresh()方法,这个方法AbstractApplicationContext类中,它描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessgaeSource和PostProcessor的注册等等。它更像是个初始化的提纲,这个过程为Bean的声明周期管理提供了条件。

public void refresh() throws BeansException, IllegalStateException {  
        synchronized (this.startupShutdownMonitor) {  
            // Prepare this context for refreshing.  
            prepareRefresh();  
  
            // Tell the subclass to refresh the internal bean factory.  
            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) {  
                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;  
            }  
        }  
    }

2.1 构建IOC容器

这个过程的入口是refresh方法中的obtainFreshBeanFactory()方法。整个过程构建了一个DefaultListableBeanFactory对象,这也就是IOC容器的实际类型。这一过程的核心如下:

2.1.1 obtainFreshBeanFactory

这个方法的作用是通知子类去初始化ioc容器,它调用了AbstractRefreshableApplicationContext的refreshBeanFactory 方法 进行后续工作。同时在日志是debug模式的时候,向日志输出初始化结果。

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

2.1.2 refreshBeanFactory

这个方法在创建IOC容器前,如果已经有容器存在,那么需要将已有的容器关闭和销毁,保证refresh之后使用的是新建立的容器。同时 在创建了空的IOC容器后,开始了对BeanDefitions的载入

protected final void refreshBeanFactory() throws BeansException {  
        if (hasBeanFactory()) {  
            destroyBeans();  
            closeBeanFactory();  
        }  
        try {  
            DefaultListableBeanFactory beanFactory = createBeanFactory();//创建了IOC容器  
            beanFactory.setSerializationId(getId());  
            customizeBeanFactory(beanFactory);  
            loadBeanDefinitions(beanFactory);// 启动对BeanDefitions的载入  
            synchronized (this.beanFa

原创不易,完成人机校验,阅读全文

相关推荐