SpringMvc启动源码解析
1. 前言
上篇文章介绍了Spring容器的初始化https://www.cnblogs.com/xiaobingblog/p/11738747.html,接下来介绍SpringMvc容器的初始化
2. 初始化化过程
上文讲过一个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对应的字段上;
接着就是执行initServletBean方法,因为HttpServletBean中的initServletBean就是个空方法,通过观察上述类图,发现子类FrameworkServlet重写了其initServletBean。于是对FrameworkServle的initServletBean进行分析
@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");
}
}
该方法中比较重要的就是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 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
/*如果当前Servelt存在一个WebApplicationContext即子IoC容器并且上文获取的根IoC容器存在,则将根IoC容器作为子IoC容器的父容器 */
cwac.setParent(rootContext);
}
//配置并刷新当前的子IoC容器,功能与前文讲解根IoC容器时的配置刷新一致,用于构建相关Bean
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
//如果当前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,而反过来不行

接下来继续看createWebApplicationContex源码:
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类实现
@Override
protected 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开发中的重要组件,相关组件的初始化创建过程均在此完成。
3. 总结
在Debug源码中,涉及到了很多设计模式,想起校招面试时面试官问我,你知道Spring源码中有哪些设计模式吗,哈哈哈,一脸懵逼,不过现在也是。看来以后得好好学习设计模式了。
至此,对Tomcat启动一个Spring项目已有了大概认知,还是很开心。小白进阶之路任重而道远。
SpringMvc启动源码解析的更多相关文章
- Spring容器启动源码解析
1. 前言 最近搭建的工程都是基于SpringBoot,简化配置的感觉真爽.但有个以前的项目还是用SpringMvc写的,看到满满的配置xml文件,却有一种想去深入了解的冲动.折腾了好几天,决心去写这 ...
- Spring Boot 启动源码解析结合Spring Bean生命周期分析
转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...
- Laravel Providers——服务提供者的注册与启动源码解析
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysishttps://learnku.com/a ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- .NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入
作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着 ...
- spring-boot-2.0.3启动源码篇一 - SpringApplication构造方法
前言 spring-boot-2.0.3应用篇 - shiro集成,实现了spring-boot与shiro的整合,效果大家也看到了,工程确实集成了shiro的认证与授权功能.如果大家能正确搭建起来, ...
- laravel源码解析
本专栏系列文章已经收录到 GitBooklaravel源码解析 Laravel Passport——OAuth2 API 认证系统源码解析(下)laravel源码解析 Laravel Passport ...
- 阿里P7终于讲完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源码
前言 这里普及一下,每个公司都有职别定级系统,阿里也是,技术岗以 P 定级,一般校招 P5, 社招 P6 起.其实阅读源码也是有很多诀窍的,这里分享几点心得: 首先要会用.你要知道这个库是干什么的,掌 ...
随机推荐
- 1小时让你掌握响应式编程,并入门Reactor
我看同步阻塞 “你知道什么是同步阻塞吗”,当然知道了.“那你怎么看它呢”,这个... 在同步阻塞的世界里,代码执行到哪里,数据就跟到哪里.如果数据很慢跟不上来,代码就停在那里等待数据的到来,然后再带着 ...
- Spring Boot(一) Hello World
一.Spring Boot之我见 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从 ...
- 30 道 Vue 面试题
前言 本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性.框架的原理以问题的形式进行整理汇总,意在帮助作者及读者自测下 Vue 掌握的程度. 本文章节结构以从易到难进行组织,建议读者按章节顺 ...
- Hadoop入门 之 Hadoop常识
1.Hadoop是什么? 答:Hadoop是开源的分布式存储和分布式计算平台. 2.Hadoop的组成是什么? 答:Hadoop由HDFS和MapReduce这两个核心部分组成. HDFS(Hadoo ...
- 转:LinkedHashMap和HashMap的比较使用
import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.uti ...
- docker部署jenkins
步骤一: 查找jenkins镜像(也可以直接去jenkins官网找镜像docker pull jenkins/jenkins)(官方版本文档https://hub.docker.com/_/jenki ...
- Find the Multiple POJ-1426
题目链接:Find the Multiple 题目大意 找出一个只由0和1组成的能整除n的数. 思路 所有由0和1组成的数可以看作是某个只由0.1组成的数a经过以下两种变化得到 1.a * 10 2. ...
- java8泛型
目录 1,泛型中的相关操作符 2,泛型基本使用示例 3,通配符 3.1, T和?的区别 3.2,上下界通配符 4, 附加约束(&) 泛型,也就是将类型参数化,然后在使用类或者方法的时候可以 ...
- Kotlin学习系列(二)
IF表达式 if在kotlin可以当做表达式使用跟java的三元操作符类似: var max = if( a > b ) a else b if分支可以使用代码块,最后一个表达式是返回值: va ...
- 前沿科技-混合现实(MR)远程协作辅助工具:微缩虚拟形象Mini-Me
今天分享一篇在刚刚结束的CHI’2018上发表的full paper.该文章由来自澳洲University of South Australia的Piumsomboon等人和来自新西兰Universi ...