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项目已有了大概认知,还是很开心。小白进阶之路任重而道远。
原文地址:https://www.cnblogs.com/xiaobingblog/p/11743163.html
- 万物智联慧结成网:信息技术驱动物流产业转型升级
- 用Qt写软件系列五:一个安全防护软件的制作(1)
- Linux文件管理
- 为什么区块链会成为消除数字化营销障碍的解决方案
- TinyOS和Deluge的安装模拟(二)
- Kubernetes的服务网格(第4部分):通过流量切换持续部署
- QTableView表格视图的列宽设置
- OpenProcess打开进程返回错误的问题
- Python标准库01 正则表达式 (re包)
- 剑指OFFER之栈的压入、弹出序列(九度OJ1366)
- Python标准库03 路径与文件 (os.path包, glob包)
- AI人工智能时代已经到来 “北斗即时判”实现纯语音交互
- 剑指OFFER之链表中倒数第k个节点(九度OJ1517)
- 用Qt写软件系列四:定制个性化系统托盘菜单
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android中Glide库的使用小技巧总结
- Android Studio手动配置Gradle的方法
- Android仿微信@好友功能 输入@跳转、删除整块
- Android开发实现广告无限循环功能示例
- Android仿微信底部菜单栏效果
- MySQL 案例:Update 死锁详解
- Android 线程之自定义带消息循环Looper的实例
- 详解Androidstudio3.0 关于Gradle报错的问题(小结)
- Android开发实现文件关联方法介绍
- Android开发获取重力加速度和磁场强度的方法
- Android自定义View圆形和拖动圆跟随手指拖动
- 简单好用的Adapter—ArrayAdapter详解
- Android开发之图片切割工具类定义与用法示例
- Android开发之超强图片工具类BitmapUtil完整实例
- Android使用URL读取网络资源的方法