Fork me on GitHub

IOC容器的创建

一、IOC容器创建方式

Ioc容器的创建时通过ApplicationContext接口的相关实现类进行的。
在这里插入图片描述
如上图所示:有三种创建IOC容器的方式。

ClassPathXmlApplicationContext:从项目的根目录下加载配置文件

FileSystemXmlApplicationContext:从磁盘中的加载配置文件

AnnotationConfigApplicationContext:当使用注解配置容器对象时使用此类进行注解读取,创建容器。

二、IOC容器创建入口

1、Java中IOC容器创建

Java中通过如下代码进行配置文件读取:

ApplicationContext context = new ClassPathXmlApplicationContext("xml路径");

创建ClassPathXmlApplicationContext对象的过程中,调用refresh方法完成容器的创建与初始化。

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        //容器的创建与初始化
        refresh();
    }
}

2、Web中IOC容器创建

web项目中,Spring启动是在web.xml配置监听器,如下所示:

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

ContextLoaderListener类实现了Tomcat容器的ServletContextListener接口,与其他Servlet监听一样,重写了两个方法:contextInitialized()方法进行web容器初始化,contextDestroyed()方法进行容器销毁。

//初始化
@Override
public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}
//销毁
@Override
public void contextDestroyed(ServletContextEvent event) {
	closeWebApplicationContext(event.getServletContext());
	ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

初始化的过程中调用ContextLoader中的initWebApplicationContext()方法,代码如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	//判断ServletContext是否已经存在WebApplication,如果存在则抛出异常	
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            //抛出异常...
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			if (this.context == null) {
                //创建WebApplicationContext
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
                        //得到根上下文的父上下文,然后设置到根上下文,一般的web项目parent为空  
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                    //从web.xml加载参数,初始化根上下文WebApplicationContext,创建bean工厂和bean对象
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			return this.context;
		}
		//异常处理...
	}

这个方法中ServletContext是由web容器监听器(ContextLoaderListener)提供。首先判断servlectContext中是否已经存在根上下文,如果存在,则抛出异常;否则通过createWebApplicationContext方法创建新的根上下文。然后通过loadParentContext()方法为其设置父上下文。再通过configureAndRefreshWebApplicationContext为根上下文构建bean工厂和bean对象。最后把上下文存入servletContext,并且存入currentContextPerThread。至此初始化过程完毕,接下来可以获取WebApplicationContext,进而用getBean("bean name")得到bean。

2.1、创建上下文

下面首先针对createWebApplicationContext进行分析,createWebApplicationContext方法用于创建跟上下文,其代码如下:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    //从web.xml配置的contextClass参数中获取上下文类名,如果contextClass为空,则使用默认的。
    Class<?> contextClass = determineContextClass(sc);
    //根上下文必须是ConfigurableWebApplicationContext的子类,否则抛出异常  
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    //根据类名创建类
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

determineContextClass方法用于返回根上下文的类名,代码如下:

protected Class<?> determineContextClass(ServletContext servletContext) {
    //从web.xml获得参数contextClass,在一般的web项目中,此参数为null  
    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 {
        //获得根上下文WebApplicationContext的默认实现类的类名,defaultStrategies是Properties类型,在CotnextLoader类开头static语句块中初始化  
        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);
        }
    }
}

Properties类型的初始化静态代码块:

static {
		try {
             //获取当前包下面的ContextLoader.properties文件
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}

2.2、初始化根上下文

初始化上下文的方法configureAndRefreshWebApplicationContext代码如下:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	//设置应用程序上下文Id
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
	//获取环境中配置的属性
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);
    //容器的创建与初始化
    wac.refresh();
}

综上,对Web中创建IOC容器的流程总结的序列图如下:

3、refresh方法

从上面的分析看到,无论是Java或Web,最后的容器创建于初始化都会进入refresh方法中,下面对refresh进行分析,refresh方法在AbstractApplicationContext类中实现,其代码如下:

// 完成IoC容器的创建及初始化工作
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) 
        // 1: 刷新预处理
        prepareRefresh();
        // 2:
        // a) 创建IoC容器(DefaultListableBeanFactory)
        // b) 加载解析XML文件(最终存储到Document对象中)
        // c) 读取Document对象,并完成BeanDefinition的加载和注册工作
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3: 对IoC容器进行一些预处理(设置一些公共属性)
        prepareBeanFactory(beanFactory);

        try {
            // 4:post-processing对BeanDefinition处理
            postProcessBeanFactory(beanFactory);

            // 5: 调用BeanFactoryPostProcessor后置处理器对BeanDefinition处理
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6: 注册BeanPostProcessor后置处理器
            registerBeanPostProcessors(beanFactory);

            // 7: 初始化一些消息源(比如处理国际化的i18n等消息源)
            initMessageSource();

            // 8: 初始化应用事件广播器
            initApplicationEventMulticaster();

            // 9: 初始化一些特殊的bean
            onRefresh();

            // 10: 注册一些监听器
            registerListeners();

            // 11: 实例化剩余的单例bean(非懒加载方式)
            // 1)、bean的实例化(创建)
            // 2)、bean的属性填充
            // 3)、bean的初始化(实现InitializingBean接口的类,在bean标签中的init-method属性)
            // 注意事项:Bean的IoC、DI和AOP都是发生在此步骤
            finishBeanFactoryInitialization(beanFactory);

            // STEP 12: 完成刷新时,需要发布对应的事件
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - "
                            + "cancelling refresh attempt: " + ex);
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}

refresh方法是初始化Spring容器的核心代码,共分为12个步骤,具体功能如代码所示,将在后续文章中将对refresh中的步骤进行说明。

posted @ 2019-01-24 22:01  紫焱luis  阅读(1090)  评论(0编辑  收藏  举报