上篇文章介绍了Spring容器的初始化,接下来介绍SpringMvc容器的初始化web
上文讲过一个Web项目的启动在加载listener、fliter初始化后,再进行servlet初始化。那SpringMvc如何与Servlet联系起来?看web.xml配置文件,有一个专门配置SpringMvc的servlet,就是DispatcherServlet。看下DispatcherServlet类继承关系面试
如上图,DispatcherServlet本质上是一个Servlet。DispatcherServlet类的设计很巧妙,上层父类不一样程度的实现了相关接口的部分方法,并留出了相关方法用于子类覆盖,将不变的部分统一实现,将变化的部分预留方法用于子类实现。对Servlet有必定了解的,Servlet初始化会首先调用init()方法。子类最后重写init()的是HttpServletBean,因此最开始对HttpServletBean的init()方法进行分析设计模式
PropertyValues主要解析web.xml定义中<servlet>元素的子元素<init-param>中的参数值。见上图,有一个键值对就是SpringMvc的配置文件。bw.setPropertyValues(pvs, true) 将上一步解析的servlet初始化参数值绑定到DispatcherServlet对应的字段上;app
接着就是执行initServletBean方法,由于HttpServletBean中的initServletBean就是个空方法,经过观察上述类图,发现子类FrameworkServlet重写了其initServletBean。因而对FrameworkServle的initServletBean进行分析ide
@Overrideprotected 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");}}
该方法中比较重要的就是initWebApplicationContext()方法的调用,该方法仍由FrameworkServlet抽象类实现,继续查看其源码以下所示:函数
protected WebApplicationContext initWebApplicationContext() {/*获取由ContextLoaderListener建立的根IoC容器获取根IoC容器有两种方法,还可经过key直接获取*/WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;
if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = 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, etcif (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/*若是当前Servelt存在一个WebApplicationContext即子IoC容器而且上文获取的根IoC容器存在,则将根IoC容器做为子IoC容器的父容器 */cwac.setParent(rootContext);}//配置并刷新当前的子IoC容器,功能与前文讲解根IoC容器时的配置刷新一致,用于构建相关BeanconfigureAndRefreshWebApplicationContext(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//若是当前Servlet不存在一个子IoC容器则去查找一下wac = findWebApplicationContext();}if (wac == null) {// No context instance is defined for this servlet -> create a local one//若是仍旧没有查找到子IoC容器则建立一个子IoC容器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方法完成“可变”的初始化过程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;}
该方法的主要做用一样是建立一个WebApplicationContext对象,即Ioc容器,上文咱们已经建立过一个根Ioc容器,即Spring容器。Web第一次启动时,经过Debug,会执行wac = createWebApplicationContext(rootContext);将根IOC容器做为参数,调用createWebApplicationContex建立一个子IOC容器学习
这里简单提一下父子IOC容器,父子容器相似于类的继承关系,子类能够访问父类中的成员变量,而父类不可访问子类的成员变量,一样的,子容器能够访问父容器中定义的Bean,但父容器没法访问子容器定义的Bean。在一个SpringMvc项目中,父容器一般就是咱们所说的Spring容器,它是加载Spring.xml配置文件,来管理Spring.xml中的Bean,这些Bean是全局共享的,即在任何当前容器或子容器中都能使用,咱们通常配置Service,dao等bean。Service类中能够调用其余Service,dao。子容器一般是咱们所说的SpringMvc容器,它所配置的Bean只能被当前子容器使用,但可使用父容器的Bean。咱们通常在子容器配置Controller、Interceptor等重要组件。这也就说明了咱们为何能够在Controller中使用service或dao,而反过来不行this
接下来继续看createWebApplicationContex源码:spa
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {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");}ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());wac.setParent(parent);wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;}
该方法用于建立一个子IoC容器并将根IoC容器作为其父容器,接着进行配置和刷新操做用于构造相关的Bean。至此,根IoC容器以及相关Servlet的子IoC容器已经配置完成。子IOC容器配置完成后,调用onRefresh(wac)方法,经过类图可知,onRefresh具体实现是由DispatcherServlet类实现debug
@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}
/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*/protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
摘抄一段评论:onRefresh()方法直接调用了initStrategies()方法,源码如上,经过函数名能够判断,该方法用于初始化建立multipartResovle来支持图片等文件的上传、本地化解析器、主题解析器、HandlerMapping处理器映射器、HandlerAdapter处理器适配器、异常解析器、视图解析器、flashMap管理器等,这些组件都是SpringMVC开发中的重要组件,相关组件的初始化建立过程均在此完成。
在Debug源码中,涉及到了不少设计模式,想起校招面试时面试官问我,你知道Spring源码中有哪些设计模式吗,哈哈哈,一脸懵逼,不过如今也是。看来之后得好好学习设计模式了。
至此,对Tomcat启动一个Spring项目已有了大概认知,仍是很开心。小白进阶之路任重而道远。