文章源自JAVA秀-https://www.javaxiu.com/19961.html
前一章节回顾: 通过前一个章节的了解,我们基本上了解了Spring父子容器的基本使用、原理和一些需要注意的点。本章节我们跟着源码看看,Spring父子容器到底是如何启动的,文章的最后也会对SpringMVC系列的文章做最后的总结。源码阅读其实并不难,难得只是我们少了一份好奇心和坚持。文章源自JAVA秀-https://www.javaxiu.com/19961.html
文章源自JAVA秀-https://www.javaxiu.com/19961.html
1. Spring容器启动顶级模板方法startInternal的基本分析
我们把startInternal这个方法再次拿出来进行完整的分析,我们发现启动一个web后台服务,最少需要一个关键步骤(非基于spring开发的web后台服务),最多需要三个步骤(基于spring父子容器方式开发的web后台服务)。文章源自JAVA秀-https://www.javaxiu.com/19961.html
@Override
protected synchronized void startInternal() throws LifecycleException {
//代码略...
try {
if (ok) {
//web后台服务启动关键步骤一:解析web.xml文件,将该文件转换成为一个ServletContext对象,该对象就是一个web后台服务核心对象(根对象)。该对象创建成功后,web后台服务启动的最重要的工作就完成了。如果是非基于spring开发的后台服务,那么后台服务等于基本完成了,后面的代码都是非核心的处理代码了;如果是基于spring开发的后台服务,那么现在还只是完成一部分启动工作,后面还要启动spring。
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
}
//代码略...
// Configure and call application event listeners
if (ok) {
//web后台服务启动关键步骤二:如果当前web.xml中配置的将要使用的spring容器是父子容器结构的话,那么先在这里创建父容器,为后续创建子容器做准备(如果是父子结构,那么子容器需要持有父容器,所以一定要先创建父容器)。
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
//代码略...
// Load and initialize all "load on startup" servlets
if (ok) {
//web后台服务启动关键步骤三:1.如果当前的spring服务是单容器服务,那么这一行开始就是启动spring容器的开始;2.如果当前的spring服务是父子容器服务,那么这一行代码就是创建子容器的开始。
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
//代码略...
}
2. Spring父容器启动过程
要启动父容器,必须在web.xml中配置两个关键内容:文章源自JAVA秀-https://www.javaxiu.com/19961.html
<!-- 配置spring父容器的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
!-- 配置spring父容器的启动事件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
第一步:我们正式开始分析父容器的启动过程,因为web.xml中配置了上面的内容之后,tomcat容器在启动后台服务的过程中,会执行startInternal方法中的下面的代码(上一节有提到过):文章源自JAVA秀-https://www.javaxiu.com/19961.html
if (ok) {
//web后台服务启动关键步骤二:如果当前web.xml中配置的将要使用的spring容器是父子容器结构的话,那么先在这里创建父容器,为后续创建子容器做准备(如果是父子结构,那么子容器需要持有父容器,所以一定要先创建父容器)。
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
第二步:开始执行listenerStart方法,本方法专门用于执行web.xml中配置的监听器的监听方法。因为我们在web.xml中配置了ContextLoaderListener监听器,所以这个方法会调用ContextLoaderListener的初始化方法。文章源自JAVA秀-https://www.javaxiu.com/19961.html
public boolean listenerStart() {
if (log.isDebugEnabled())
log.debug("Configuring application event listeners");
// Instantiate the required listeners
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
if (getLogger().isDebugEnabled())
getLogger().debug(" Configuring event listener class '" +
listeners[i] + "'");
try {
String listener = listeners[i];
results[i] = getInstanceManager().newInstance(listener);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.applicationListener", listeners[i]), t);
ok = false;
}
}
if (!ok) {
getLogger().error(sm.getString("standardContext.applicationSkipped"));
return false;
}
// Sort listeners in two arrays
List<Object> eventListeners = new ArrayList<>();
List<Object> lifecycleListeners = new ArrayList<>();
for (int i = 0; i < results.length; i++) {
if ((results[i] instanceof ServletContextAttributeListener)
|| (results[i] instanceof ServletRequestAttributeListener)
|| (results[i] instanceof ServletRequestListener)
|| (results[i] instanceof HttpSessionIdListener)
|| (results[i] instanceof HttpSessionAttributeListener)) {
eventListeners.add(results[i]);
}
if ((results[i] instanceof ServletContextListener)
|| (results[i] instanceof HttpSessionListener)) {
lifecycleListeners.add(results[i]);
}
}
// Listener instances may have been added directly to this Context by
// ServletContextInitializers and other code via the pluggability APIs.
// Put them these listeners after the ones defined in web.xml and/or
// annotations then overwrite the list of instances with the new, full
// list.
for (Object eventListener: getApplicationEventListeners()) {
eventListeners.add(eventListener);
}
setApplicationEventListeners(eventListeners.toArray());
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
// Send application start events
if (getLogger().isDebugEnabled())
getLogger().debug("Sending application start events");
// Ensure context is not null
getServletContext();
context.setNewServletContextListenerAllowed(false);
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
for (int i = 0; i < instances.length; i++) {
if (!(instances[i] instanceof ServletContextListener))
continue;
ServletContextListener listener =
(ServletContextListener) instances[i];
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
//关键代码行:此时执行的是ContextLoaderListener监听器的contextInitialized方法
listener.contextInitialized(tldEvent);
} else {
//关键代码行:此时执行的是ContextLoaderListener监听器的contextInitialized方法
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error
(sm.getString("standardContext.listenerStart",
instances[i].getClass().getName()), t);
ok = false;
}
}
return ok;
}
第三步:从这里开始我们可以感觉到离创建Spring父容器越来越近了,实际上就是在initWebApplicationContext中完成了父容器对象的创建和初始化的工作。文章源自JAVA秀-https://www.javaxiu.com/19961.html
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
第四步:正式开始父容器对象的创建和初始化的工作文章源自JAVA秀-https://www.javaxiu.com/19961.html
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
//重要执行一:创建父容器对象,将servletContext当作参数传入进去,意味着我们的父容器对象会持有ServletContext对象。
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);
}
//重要调用执行二:配置父容器对象信息,并刷新容器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;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
//创建Spring父容器对象
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() + "]");
}
//简单明了的使用BeanUtils工具类的instantiateClass方法创建一个applicationContext对象(即父容器对象)。
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
//配置和刷新Spring父容器对象
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
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()));
}
}
//让父容器对象持有ServletContext对象,也就意味着父容器对象可以和ServletContext对象一样,就可以代表是我们的一个后台服务了。
wac.setServletContext(sc);
//将web.xml中配置的contextConfigLocation信息,设置到父容器对象中
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();
}
3. Spring子容器启动过程
要启动子容器,必须在web.xml中配置一个关键内容:文章源自JAVA秀-https://www.javaxiu.com/19961.html
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
上面一小节,我们看到了Spring父容器的创建的工作,本小节我们开始看看子容器的启动过程。其实子容器的启动过程我们之前已经讲解过了,这里我们在简单的回顾一下:文章源自JAVA秀-https://www.javaxiu.com/19961.html
第一步:我们正式开始分析子容器的启动过程,因为web.xml中配置了上面的内容之后,tomcat容器在启动后台服务的过程中,会执行startInternal方法中的下面的代码(第一节有提到过):文章源自JAVA秀-https://www.javaxiu.com/19961.html
if (ok) {
//web后台服务启动关键步骤三:1.如果当前的spring服务是单容器服务,那么这一行开始就是启动spring容器的开始;2.如果当前的spring服务是父子容器服务,那么这一行代码就是创建子容器的开始。
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
第二步:从loadOnStartup方法,到initServletBean的调用过程就不重复了,在springmvc篇-(六)SpringMVC的Spring容器是如何启动的文章中有提到,这里就不做多的讲解。我们从initServletBean方法开始,往下看看spring的子容器是如何被创建出来的。注意,后面的文字解释里面没有说Spring子容器,而是说Spring容器,实际上是一个意思。不管是单容器启动,还是父子容器方式启动,都会执行下面的代码。当我们配置Spring单容器启动的时候,下面就是创建一个spring容器;当我们配置Spring父子容器方式启动的时候,下面就是创建一个Spring子容器。文章源自JAVA秀-https://www.javaxiu.com/19961.html
//本方法也不直接启动spring容器,而是进一步将启动spring容器的工作委托给initWebApplicationContext方法。从这个方法命名,我们就知道离真正的创建一个applicationcontext对象越来越近了。
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
//本方法就是真正的开始我们的spring容器的启动的核心方法。
protected WebApplicationContext initWebApplicationContext() {
//在此处就会完成applicationcontext对象的创建和初始化的工作
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 从这里创建一个applicationcontext对象
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
//本方法就是真正的开始我们的spring容器的启动的核心方法。
protected WebApplicationContext initWebApplicationContext() {
//在此处就会完成applicationcontext对象的创建和初始化的工作
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 从这里创建一个applicationcontext对象
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
第三步:创建applicationcontext对象以及初始化applicationcontext对象文章源自JAVA秀-https://www.javaxiu.com/19961.html
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
//本方法主要是创建spring容器对象也就是ConfigurableWebApplicationContext对象
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取当前需要创建的spring容器的实际类,也就是XmlWebApplicationContext类,这个就是真正实例化的spring容器的类。
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//使用BeanUtils工具类的instantiateClass方法创建一个XmlWebApplicationContext实例,也就是spring容器。
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置当前服务环境信息
wac.setEnvironment(getEnvironment());
//spring容器启动有两种方式:一种比较简单,就是单容器启动方式;一种稍微复杂,就是父子容器启动方式。父子容器的意思是会创建两个applicationcontext对象,但是这两个对象一起构成了一个整体的spring容器(形态就是子容器对象持有父容器对象,两个对象作为一个整体代表一个spring容器)。因为本章节讲解的时单容器启动,这里不多做讲解,这里的的parent父容器也就是为空。
wac.setParent(parent);
//此处就是在web.xml中servlet配置的contextConfigLocation属性配置的内容,也就是spring配置文件所在位置
wac.setConfigLocation(getContextConfigLocation());
//前面的几步只是初步的设置了ConfigurableWebApplicationContext实例的基本信息,本行代码调用将进一步的初始化ConfigurableWebApplicationContext实例
configureAndRefreshWebApplicationContext(wac);
return wac;
}
//本方法主要用于使得Spring容器对象持有有ServletContext对象,这样我们得spring容器也就具备了代表一个web后台服务得能力。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
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
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
//我们前面讲过一个web.xml文件最终被解析成为一个ServletContext对象,这个对象也就可以理解为一个web后台服务的实例对象。而我们的spring容器启动过程中,会将这个web后台服务实例对象添加到自己的属性中。也就是说基于spring开发的后台服务,我们可以认为spring容器是我们的后台服务实例对象,因为他更能代表这个后台服务。
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 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(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//到这里就回到我们熟悉的刷新spring容器环境的方法了。
wac.refresh();
}
通过上面的代码的跟踪,我们基本上也就了解了Spring的父子容器的整体过程了。文章源自JAVA秀-https://www.javaxiu.com/19961.html
4. SpringMVC系列文章的总结
- 在没有Spring的时候,我们使用原生的Servlet做业务开发,我们在doGet,doPost方法里面去做业务开发,这两个方法和我们在Spring的Controller里面的方法很像。在这一步,我们的理解是一个请求来服务器后,服务器会被Tomcat容器接收到,tomcat接收到请求之后,会层层嵌套调用,直到调用我们的业务Servlet的doGet和doPost方法进行业务处理。
当我们使用SpringMVC做后台服务开发的时候,我们发现Tomcat会将层层调用,直到调用我们的DispatcherServlet类的doPost方法进行业务处理。后面我们不再使用层层调用这种说法,我们用转发或者转派更加好理解。而DispatcherServlet类不会直接处理这个请求,而是转交给一个叫做doDispatch的核心模板方法进行处理。这个方法主要做了一个工作,就是根据用户的请求,找到能处理这个请求的方法集合(过滤器链和业务处理方法),并依次进行处理。文章源自JAVA秀-https://www.javaxiu.com/19961.html
我们对doDispatch的核心模板展开了进行了仔细的分析,过滤器链是如果构建的,执行顺序是怎么样的,业务方法是怎么拿到的等等。文章源自JAVA秀-https://www.javaxiu.com/19961.html
经过前面的三个步骤,我们基本上知道了SpringMVC的工作原理,但是又引入了一个更深的问题,就是我们的war包是如何被tomcat启动起来的呢?而且基于Spring开发的war包是怎么创建一个spring容器的呢?为了解决这个问题,我们又深入的介绍了一下web.xml和ServletContext这两个概念。web.xml就是一个还未运行的后台服务实例核心对象的前身,也就是ServletContext对象的前身,它记录和承载了一个web后台服务的核心信息。当我们的web容器启动我们的web后台应用的时候,会把web.xml文件读取解析成为一个ServletContext对象,这个对象也就可以看成是一个运行中的后台服务实例。文章源自JAVA秀-https://www.javaxiu.com/19961.html
我们对web.xml文件和ServletContext概念深入了解了之后,我们扒开了tomcat的源码,我们知道了StandardContext类的startInternal方法,就是启动我们的web后台服务的核心方法,这个方法里面进行web.xml的解析工作,一步一步的将web.xml文件解析成为一个ServletContext对象,也就是一个web后台服务核心对象。文章源自JAVA秀-https://www.javaxiu.com/19961.html
我们知道了web后台服务是如何启动的过程了,我们再startInternal方法里面进一步查找,我们发现了原来在loadOnStartup方法里面层层委托,直到委托给FrameworkServlet类的initWebApplicationContext方法,真正开始了我们spring容器的启动的工作。但是在分析代码的过程中,又引入一个新的问题,就是原来springmvc的使用还有两种方式,一种是单容器的方式使用,一种是父子容器的方式使用。文章源自JAVA秀-https://www.javaxiu.com/19961.html
前面说了父子容器的使用方式,我们简单的介绍了如何进行父子容器的配置和使用,也简单的介绍了父子容器的原理和一些注意的事项:在使用切面和事务的时候,需要特别注意,否则会出现不生效的情况。文章源自JAVA秀-https://www.javaxiu.com/19961.html
最后一章节我们跟着源码走了一遍,我们发现启动一个基于Spring开发的web后台服务,最复杂的情况下,我们需要做三次关键的初始化的工作,这三个初始化的调用的开始的地方在startInternal方法里面。第一个是将web.xml解析成为一个ServletContext对象,而这个过程我们可以成为启动web后台服务;第二个是启动Spring父容器的工作,这个是通过我们在web.xml中配置的ContextLoaderListener来最终完成的;第三个是启动Spring子容器的工作,在这个过程中会使Spring子容器持有Spring父容器对象,这二者一起构成一个Spring容器进行工作。文章源自JAVA秀-https://www.javaxiu.com/19961.html
到这里本系列文章的介绍基本上也就结束了,这里读者可能会有很多的疑问,读者可以自行进行进一步的挖掘,在挖掘源码的过程也是在一步一步的抽象自己思维的过程,一定会有很多的收获。文章源自JAVA秀-https://www.javaxiu.com/19961.html
文章源自JAVA秀-https://www.javaxiu.com/19961.html
更多知识请关注公众号文章源自JAVA秀-https://www.javaxiu.com/19961.html

评论