Spring启动过程(一)
2018-04-08 18:48
246 查看
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里面配置:
确切的说,ContextLoaderListener这个监听对象,监听的是ServletContext这个,当web容器初始化,ServletContext发生变化的时候,会触发相应的事件。
ContextLoaderListener继承了ContextLoader,并实现了ServletContextListener接口,在web容器初始化的时候,会触发ServletContextListener接口中的contextInitialized()方法,同理,在容器关闭的时候,会触发对应的contextDestroyed()方法。
其中,initWebApplicationContext()方法为父类ContextLoader中的方法:
由于initWebApplicationContext()方法源码较长,我们进行拆分阅读。
首先判断是否创建了WebApplicationContext,正常情况下创建了一个WebApplicationContext后,会把context set到ServletContext中,setAttribute的本质其实是往LinkedHashMap中set值。
如果从servletContext中根据key获得了WebApplicationContext对象,则表示之前已经创建过了根上下文WebApplicationContext,此时抛出异常,提示检查web.xml中的配置,避免重复创建root上下文,保证只有一个Spring容器。
再看下面的代码:
代码片段较长,一步一步来,try中有俩关键的地方:
createWebApplicationContext()和configureAndRefreshWebApplicationContext()
先逐步往下看
首先是判断 this.context == null,通过createWebApplicationContext方法创建一个
WebApplicationContext,具体代码如下:
determineContextClass,字面意思为检测Context的class类型,会读取servletContext的初始化参数contextClass,大部分情况下我们不会配置此参数,在未配置的情况下,Spring会去org.springframework.web.context包中的ContextLoader.properties配置文件读取默认配置:
通过Spring提供的ClassUtil进行反射,反射出XmlWebApplicationContext类,再通过BeanUtils进行反射,调用无参构造器,instance出一个WebApplicationContext并返回ConfigurableWebApplicationContext。
获得WebApplicationContext后,下一步判断获得的context是否为
ConfigurableWebApplicationContext的实例,默认的XmlWebApplicationContext满足判断条件。
判断父上下文是否为active状态,如果active为false下,需要判断父上下文是否为null,如果parent上下文为null的情况,则执行loadParentContext()。loadParentContext()方法,是一个默认的是模板实现方法,用于加载/获取ApplicationContext,此context为根WebApplicationContext的父上下文。
下一步,进入configureAndRefreshWebApplicationContext
将上面创建的XmlWebApplicationContext进行初始化操作。
主要创建一个默认id,
创建好id后,把servletContext放入创建好的XmlWebApplicationContext中,便于spring后续获得servletContext上下文。
紧接着会去读取web.xml中<param-name>contextConfigLocation</param-name>的配置
如果在web.xml中未配置,则会去读取WEB-INF下面的applicationContext.xml配置文件,
即XmlWebApplicationContext中的属性 DEFAULT_CONFIG_LOCATION的默认值
读取到contextConfigLocation相关文件配置路径后,设置到XmlWebApplicationContext中,用于加载指定路径的配置文件。
下一步 customizeContext(sc, wac);
主要用于自定义context相关配置,比如定义bean是否可以循环引用,bean定义是否可以被覆盖等,通常情况下不做任何操作。
最后一步:
整个容器启动的最核心方法,在这个refresh()方法中,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作。
refresh()方法的相关逻辑,下一篇继续深入学习。
参考链接:https://www.jianshu.com/p/375ef7095139
对于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
相关文章推荐
- spring启动过程之源码跟踪(上)--spring Debug
- 详解contextConfigLocation|Spring启动过程详解
- springboot的tomcat启动过程
- Spring Boot 2.x 启动全过程源码分析(上)入口类剖析
- Spring启动过程(精简版)
- SpringBoot启动过程之初始化器initializer和监听器listeners
- 详解contextConfigLocation|Spring启动过程详解
- spring启动过程之源码跟踪(下)--spring Debug
- spring的启动过程——spring和springMVC父子容器的原理
- Spring启动后扫描解析注解的过程
- Spring 启动过程分析
- spring启动component-scan类扫描加载过程---源码分析
- Spring容器-ApplicationContext的启动过程
- Spring Boot启动过程(五)之Springboot内嵌Tomcat对象的start教程详解
- spring 启动过程
- Spring Boot启动过程(六):内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动
- spring启动加载过程源码分析
- spring的启动过程
- spring容器启动的加载过程(三)
- Spring容器启动过程