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