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启动源码解析的更多相关文章

  1. Spring容器启动源码解析

    1. 前言 最近搭建的工程都是基于SpringBoot,简化配置的感觉真爽.但有个以前的项目还是用SpringMvc写的,看到满满的配置xml文件,却有一种想去深入了解的冲动.折腾了好几天,决心去写这 ...

  2. Spring Boot 启动源码解析结合Spring Bean生命周期分析

    转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...

  3. Laravel Providers——服务提供者的注册与启动源码解析

      本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysishttps://learnku.com/a ...

  4. Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  5. Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  6. .NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着 ...

  7. spring-boot-2.0.3启动源码篇一 - SpringApplication构造方法

    前言 spring-boot-2.0.3应用篇 - shiro集成,实现了spring-boot与shiro的整合,效果大家也看到了,工程确实集成了shiro的认证与授权功能.如果大家能正确搭建起来, ...

  8. laravel源码解析

    本专栏系列文章已经收录到 GitBooklaravel源码解析 Laravel Passport——OAuth2 API 认证系统源码解析(下)laravel源码解析 Laravel Passport ...

  9. 阿里P7终于讲完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源码

    前言 这里普及一下,每个公司都有职别定级系统,阿里也是,技术岗以 P 定级,一般校招 P5, 社招 P6 起.其实阅读源码也是有很多诀窍的,这里分享几点心得: 首先要会用.你要知道这个库是干什么的,掌 ...

随机推荐

  1. 深入理解Three.js中正交摄像机OrthographicCamera

    前言 在深入理解Three.js中透视投影照相机PerspectiveCamera那篇文章中讲解了透视投影摄像机的工作原理以及对应一些参数的解答,那篇文章中也说了会单独讲解Three.js中另一种常用 ...

  2. Docker学习之volume

    提供独立于容器之外的持久化存储 容器中的数据会随着容器的消失而消失,为了解决这个问题,产生了数据卷volume. 例子,比如说mysql容器,msyql中的数据应该是持久化的,故应该存储在volume ...

  3. [Leetcode] 第306题 累加数

    一.题目描述 累加数是一个字符串,组成它的数字可以形成累加序列. 一个有效的累加序列必须至少包含 3 个数.除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和. 给定一个只包含数字 ' ...

  4. Unity3D-游戏场景优化之遮挡剔除(Occlusion Culling)的使用

    在大型3D游戏场景中,如何优化游戏性能是非常重要的一步.一般遮挡剔除是非常常用的.接下来我们看看如何使用遮挡剔除. 假设这是一个游戏场景. 下面这是相机的视口,相机的视觉是看不到很大立方体后面的那些小 ...

  5. linux 进程消耗查看

    Linux下如何查看哪些进程占用的CPU内存资源最多 linux下获取占用CPU资源最多的10个进程,可以使用如下命令组合: ps aux|head -1;ps aux|grep -v PID|sor ...

  6. windows如何利用计划任务自动关机?

    第一步打开控制面板,然后选择计划任务,打开它 选择创建基本任务 输入任务名称,描述,选择下一步 根据需要选择,我这里选择的是每天,然后选择下一步 选择任务开始时间,然后选择下一步 选择启动程序,然后选 ...

  7. python简单爬虫(爬取pornhub特定关键词的items图片集)

    请提前搭好梯子,如果没有梯子的话直接403. 1.所用到的包 requests: 和服务器建立连接,请求和接收数据(当然也可以用其他的包,socket之类的,不过requests是最简单好用的) Be ...

  8. passwd、shadow、group文件格式

    [root@bogon ~]# cat /etc/passwd root:x:0:0:root:/root:/bin/bash 登录名:密码占位符:UID:GID:注释:家目录:用户的默认shell ...

  9. Spring入门之AOP实践:@Aspect + @Pointcut + @Before / @Around / @After

    零.准备知识 1)AOP相关概念:Aspect.Advice.Join point.Pointcut.Weaving.Target等. ref: https://www.cnblogs.com/zha ...

  10. ELK 学习笔记之 elasticsearch Mget操作

    Mget操作: 查询多个文档: curl -XGET 'http://192.168.1.151:9200/_mget' -d '{"docs": [{"_index&q ...