Spring启动过程(一)

    Spring的启动过程,就是其IoC容器的启动过程,本质就是创建和初始化bean的工厂(BeanFactory),BeanFactory其实就是整个SpringIoc的核心,Spring 使用 BeanFactory 来实例化、配置和管理 Bean。

    对于web程序,IoC容器启动过程即是建立上下文的过程,在web应用中,web容器会提供一个全局的ServletContext上下文环境,ServletContext上下文为Spring IoC提供了一个宿主环境。

    Spring应用在Web容器中启动的整个过程如下:

    

先介绍下源码中出现的各位小伙伴:

  • Resource:是spring对于资源的一种抽象,因为资源的来源可能很丰富,利于File,Class Path Resource,Url Resource等,进行统一封装,暴露出getInputStream进行统一读取解析
  • Document:这个没啥好讲的,XML文档对象
  • EncodedResource:封装了Resource,指定Resource的编码
  • ReaderContext:Bean Definition解析过程中的上下文对象,封装了Resource、ProblemReporter、EventListener、SourceExtractor等
  • Element:XML中的元素节点对象
  • BeanDefinition:这个接口及其实现类非常重要,他描述了XML中一个bean节点及其子节点的所有属性,将xml中的描述转变成内部的field信息,举例:scope,lazyinit,ConstructorArgumentValues(描述构造器),PropertyValues(描述属性值)等,是一个保罗万象的接口,其子类实现包括GenericBeanDefinition、RootBeanDefinition、ChildBeanDefinition等
  • BeanDefinitionHolder:顾名思义包含了一个BeanDefinition,同时其包含了beanName和aliases,更好的封装了一次
  • BeanDefinitionReader:定义了读取BeanDefinition的接口,主要作用是从Resource中读取Bean定义,XmlBeanDefinitionReader是其具体的实现类
  • BeanDefinitionDocumentReader:定义了从Document对象中解析BeanDefinition并且注册到Registry中设计到的接口,其默认实现类是DefaultBeanDefinitionDocumentReader,主要是被XmlBeanDefinitionReader委派去处理Document对象
  • BeanDefinitionParserDelegate:看到Delegate就知道这个哥们是个受苦的对象,是个最终干活的人,官方定义是“Stateful delegate class used to parse XML bean definitions.* Intended for use by both the main parser and any extension* {@link BeanDefinitionParser BeanDefinitionParsers} or* {@link BeanDefinitionDecorator BeanDefinitionDecorators}.”是用于最终处理XML bean定义的人,它做的可都是脏活累活,import/alias/bean等element以及element的子节点以及属性都是它解析并且填充到BeanDefinition中然后使用ReaderContext中的Registry(实际就是DefaultListableBeanFactory)来将该BeanDefinition注册

在Web项目使用Spring,是通过在web.xml里面配置:

org.springframework.web.context.ContextLoaderListener 来初始化IOC容器的。

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

确切的说,ContextLoaderListener这个监听对象,监听的是ServletContext这个,当web容器初始化,ServletContext发生变化的时候,会触发相应的事件。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

ContextLoaderListener继承了ContextLoader,并实现了ServletContextListener接口,在web容器初始化的时候,会触发ServletContextListener接口中的contextInitialized()方法,同理,在容器关闭的时候,会触发对应的contextDestroyed()方法。

其中,initWebApplicationContext()方法为父类ContextLoader中的方法:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext)

由于initWebApplicationContext()方法源码较长,我们进行拆分阅读。

if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
	throw new IllegalStateException(
			"Cannot initialize context because there is already a root application context present - " +
			"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
//日志记录
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
	logger.info("Root WebApplicationContext: initialization started");
}
//启动startTime,记录启动耗时
long startTime = System.currentTimeMillis();

首先判断是否创建了WebApplicationContext,正常情况下创建了一个WebApplicationContext后,会把context set到ServletContext中,setAttribute的本质其实是往LinkedHashMap中set值。

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

如果从servletContext中根据key获得了WebApplicationContext对象,则表示之前已经创建过了根上下文WebApplicationContext,此时抛出异常,提示检查web.xml中的配置,避免重复创建root上下文,保证只有一个Spring容器。

再看下面的代码:

try {
	// Store context in local instance variable, to guarantee that
	// it is available on ServletContext shutdown.
	if (this.context == null) {
		this.context = createWebApplicationContext(servletContext);
	}
	if (this.context instanceof ConfigurableWebApplicationContext) {
		ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
		if (!cwac.isActive()) {
			// The context has not yet been refreshed -> provide services such as
			// setting the parent context, setting the application context id, etc
			if (cwac.getParent() == null) {
				// The context instance was injected without an explicit parent ->
				// determine parent for root web application context, if any.
				ApplicationContext parent = loadParentContext(servletContext);
				cwac.setParent(parent);
			}
			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;
}

代码片段较长,一步一步来,try中有俩关键的地方:

createWebApplicationContext()和configureAndRefreshWebApplicationContext()

先逐步往下看

首先是判断 this.context == null,通过createWebApplicationContext方法创建一个

WebApplicationContext,具体代码如下:

	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		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,字面意思为检测Context的class类型,会读取servletContext的初始化参数contextClass,大部分情况下我们不会配置此参数,在未配置的情况下,Spring会去org.springframework.web.context包中的ContextLoader.properties配置文件读取默认配置:

通过Spring提供的ClassUtil进行反射,反射出XmlWebApplicationContext类,再通过BeanUtils进行反射,调用无参构造器,instance出一个WebApplicationContext并返回ConfigurableWebApplicationContext。

if (this.context instanceof ConfigurableWebApplicationContext) {
	ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
	if (!cwac.isActive()) {
		// The context has not yet been refreshed -> provide services such as
		// setting the parent context, setting the application context id, etc
		if (cwac.getParent() == null) {
			// The context instance was injected without an explicit parent ->
			// determine parent for root web application context, if any.
			ApplicationContext parent = loadParentContext(servletContext);
			cwac.setParent(parent);
		}
		configureAndRefreshWebApplicationContext(cwac, servletContext);
	}
}

获得WebApplicationContext后,下一步判断获得的context是否为

ConfigurableWebApplicationContext的实例,默认的XmlWebApplicationContext满足判断条件。

判断父上下文是否为active状态,如果active为false下,需要判断父上下文是否为null,如果parent上下文为null的情况,则执行loadParentContext()。loadParentContext()方法,是一个默认的是模板实现方法,用于加载/获取ApplicationContext,此context为根WebApplicationContext的父上下文。

下一步,进入configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)

将上面创建的XmlWebApplicationContext进行初始化操作。

if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
	// The application context id is still set to its original default value
	// -> assign a more useful id based on available information
	String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
	if (idParam != null) {
		wac.setId(idParam);
	}
	else {
		// Generate default id...
		wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
				ObjectUtils.getDisplayString(sc.getContextPath()));
	}
}

主要创建一个默认id,org.springframework.web.context.WebApplicationContext:+项目的ContextPath

//设置sc到wac中,便于Spring获得ServletContext
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
	wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
	((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();

创建好id后,把servletContext放入创建好的XmlWebApplicationContext中,便于spring后续获得servletContext上下文。

紧接着会去读取web.xml中<param-name>contextConfigLocation</param-name>的配置

如果在web.xml中未配置,则会去读取WEB-INF下面的applicationContext.xml配置文件,

即XmlWebApplicationContext中的属性 DEFAULT_CONFIG_LOCATION的默认值

/** Default config location for the root context */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

读取到contextConfigLocation相关文件配置路径后,设置到XmlWebApplicationContext中,用于加载指定路径的配置文件。

下一步 customizeContext(sc, wac);

主要用于自定义context相关配置,比如定义bean是否可以循环引用,bean定义是否可以被覆盖等,通常情况下不做任何操作。

最后一步:

wac.refresh();

整个容器启动的最核心方法,在这个refresh()方法中,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作。

refresh()方法的相关逻辑,下一篇继续深入学习。

参考链接:https://www.jianshu.com/p/375ef7095139

猜你喜欢

转载自my.oschina.net/klausprince/blog/1791357