SpringMVC+Spring是常用的Java框架,但SpringMVC有着和Spring完全不同的启动过程。
一、SpringMVC的启动文件 1、SpringMVC是一个Servlet,而普通web应用的启动文件便是WEB-INF/web.xml,web.xml的读取顺序大致是:context-param >> listener >> filter >> servlet
2、context-param和listener是一对,用于初始化Spring根容器,配置文件是applicationContext.xml
3、servlet和servlet-mapping是另一对,用于初始化SpringMVC容器,配置文件是servletContext.xml
4、这个web.xml文件配置将会初始化两个Spring容器,SpringMVC容器是Spring根容器的子容器。所以一般Controller一般配置在SpringMVC容器中,Service和DAO配置在Spring容器中,可以实现Spring和MVC层的解耦。
web.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app > <display-name > Archetype Created Web Application</display-name > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:applicationContext.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <servlet > <servlet-name > SpringMvc</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:servletContext.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > SpringMvc</servlet-name > <url-pattern > *.html</url-pattern > </servlet-mapping > </web-app >
二、SpringMVC的启动过程 1、先回顾一下web.xml文件的加载过程:context-param >> listener >> filter >> servlet
2、context-param配置的读取是由容器完成的,可以不用关心,Tomcat容器会将context-param的值作为参数传给listener
3、Tomcat容器会加载web.xml中的listener,这里配置的listener是ContextLoaderListener,它实现了sevlet提供的ServletContextListener接口,其中里面的contextInitialized便是Tomcat容器启动时首先调用的方法。
这段代码主要做了以下几件事:
1)WebApplicationContext存在性的验证,如果已经有了就抛出异常
2)创建WebApplicationContext实例,如果没有指定容器类型,这个容器默认类型是XmlWebApplicationContext。来源是ContextLoader.properties这个配置文件的默认配置。
3)将配置文件applicationContext.xml设置到XmlWebApplicationContext容器中,调用容器的refresh方法来初始化容器。(是不是很熟悉,refresh方式是ApplicationContext容器的标准初始化入口,用于注册后置处理器、实例化bean等一切Spring容器有关的工作)
4)将XmlWebApplicationContext容器记录到servletContext中
ContextLoaderListener.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Override public void contextInitialized (ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } 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!" ); } servletContext.log("Initializing Spring root WebApplicationContext" ); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started" ); } long startTime = System.currentTimeMillis(); try { if (this .context == null ) { this .context = createWebApplicationContext(servletContext); } if (this .context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this .context; if (!cwac.isActive()) { if (cwac.getParent() == null ) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .context); ...... }
4、由于没有配置filter,就不说filter的启动了,道理和listener类似,无非是实现一个接口,Tomcat容器会自动调用罢了。
5、这里配置了一个servlet,实现类是DispatcherServlet,这是SpringMVC启动的核心。DispatcherServlet实现了Servlet接口,其中Tomcat容器启动时会首先调用init方法来初始化,这也是SpringMVC启动的初始化方法。
在这个方法的调用栈中,我们最关心的无非是initWebApplicationContext方法,这个方法会再创建一个XmlWebApplicationContext容器,并初始化SpringMVC的九大组件,来看看具体做了哪些事:
1)寻找或创建一个新的SpringMVC容器,这个容器的类型也是XmlWebApplicationContext
2)将刚才创建的Spring容器设置为SpringMVC容器的父容器,并调用refresh来初始化容器并实例化servletContext.xml配置文件中的所有bean
3)调用onRefresh方法初始化SpringMVC的九大组件
4)将XmlWebApplicationContext容器记录到servletContext中
HttpServletBean.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 @Override public final void init () throws ServletException {...... initServletBean(); } @Override protected final void initServletBean () throws ServletException {...... try { this .webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } ...... } protected WebApplicationContext initWebApplicationContext () { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null ; if (this .webApplicationContext != null ) { wac = this .webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null ) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null ) { wac = findWebApplicationContext(); } if (wac == null ) { wac = createWebApplicationContext(rootContext); } if (!this .refreshEventReceived) { synchronized (this .onRefreshMonitor) { onRefresh(wac); } } if (this .publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
三、SpringMVC启动流程图 不管是Listener、Filter还是Servlet,都是实现了一个Java提供的接口,而Tomcat容器启动时会按顺序调用他们的init方法来初始化。
SpringMVC正是在Servlet的基础实现了一些接口,达到了启动Spring容器的目的,如果看流程图还是比较简单的,但看代码却是一个十分伤脑筋的事情!
创建父容器
WebApplicationContext
再创建一个子容器
WebApplicationContext
http://zhanjindong.com/assets/pdf/Servlet3.1-Specification.pdf
https://www.jianshu.com/p/7097fea8ce3f