SpringMVC源码(二):MVC容器启动

  SpringMVC是一个基于Java实现了MVC设计模式的请求驱动类型的轻量级Web框架,SpringMVC是在Spring框架的基础上做的拓展。

一、分析思路

  在使用配置文件的方式设置SpringMVC的时候,都会配置web.xml,下面我们看看web.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
   <!--Spring配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>mvc-test</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--SpringMVC配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-test</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

web.xml文件的内容包括:

  1、Spring的相关bean配置文件;

  2、SpringMVC的相关bean配置文件;

  3、Servlet配置;

  4、监听器配置。

1.1、两个IOC容器

  在SpringMVC中包含两个容器,Spring容器和SpringMVC的容器,Spring作为父容器,SpringMVC作为子容器。为什么要单独为SpringMVC创建一个容器呢?为SpringMVC创建一个容器是为了与Spring的容器做隔离,SpringMVC容器有其特定的功能,并且可以继承Spring父容器,用父容器相关的bean。

1.2、容器启动入口

1.2.1、Spring父容器启动入口

  在Tomcat启动后,通过配置的容器加载监听器ContextLoaderListener#contextInitialized会优先加载Spring的配置,创建好Spring父容器,ContextLoaderListener#contextInitialized为Spring父容器启动入口,ContextLoaderListener#contextInitialized是在Tomcat源码中调用的,在Tomcat源码中再做详细分析。

1.2.2、SpringMVC容器启动入口

  在创建SpringMVC容器时,需优先加载SpringMVC的配置文件,在web.xml文件中,SpringMVC配置文件设置在Servlet标签下,因此需要先创建好对应的Servlet对象,加载到SpringMVC的配置文件后,创建MVC容器。

  MVC容器启动与Servlet相关,Servlet的生命周期中有三个重要的方法: init() -> service() -> destory()。MVC容器启动是否在init() - Servlet的初始化中的呢?

  在web.xml中servlet标签配置的DispatcherServlet及相关父类中是否有init()方法,在DispatcherServlet父类HttpServletBean中,可以找到init()初始化方法。在初始化方法中,通过调用FrameworkServlet#initServletBean()创建MVC容器。

二、SpringMVC容器启动流程图

2.1、Spring父容器启动流程

  ContextLoaderListener#contextInitialized方法作为切入点分析:
 
   

2.2、SpringMVC容器启动流程

  Spring父容器的启动,通过监听器可以完成,那么SpringMVC容器的启动,又是在哪里切入的呢?首先在配置文件web.xml中,分析出SpringMVC本质上是一个Servlet,那么必然要遵守Sevlet的生命周期,在Servlet生命周期中最重要的三个方法init()、doService()、destory()。

 
  

  对于SpringMVC的分析配置文件中的DispatcherServlet中做进一步的分析,DispatcherServlet类图结构如下:

 

  依照类图,按从子类到父类的顺序,找到init()方法,可在HttpServletBean中找到init()方法,执行流程如下

    

三、SpringMVC容器启动核心源码

3.1、Spring容器启动

  容器加载监听器中,容器初始化完成操作,ContextLoaderListener#contextInitialized 核心伪代码:

1 // 初始化web应用父容器
2 @Override
3 public void contextInitialized(ServletContextEvent event) {
4    // 初始化web应用父容器
5    initWebApplicationContext(event.getServletContext());
6 }

  初始化Web应用上下文环境,Spring父容器的处理, ContextLoaderListener#initWebApplicationContext核心伪代码:

// 容器属性
@Nullable
private WebApplicationContext context;

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   // 初始化Spring容器日志打印
   servletContext.log("Initializing Spring root WebApplicationContext");
   // 记录Spring父容器启动开始时间
   long startTime = System.currentTimeMillis();

   // 初始化context,首次执行获取到一个root webApplicationcontext
   if (this.context == null) {
     this.context = createWebApplicationContext(servletContext);
   }
   // 若当前容器是ConfigurableWebApplicationContext类型
   if (this.context instanceof ConfigurableWebApplicationContext) {
     ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
     // 若容器未被激活
     if (!cwac.isActive()) {
        // 当前容器父容器为空,判断是否为当前容器设置了父容器
        if (cwac.getParent() == null) {
           // 获取当前容器的父容器,并设置父子依赖
           ApplicationContext parent = loadParentContext(servletContext);
           cwac.setParent(parent);
        }
        // 创建容器并刷新
        configureAndRefreshWebApplicationContext(cwac, servletContext);
     }
   }
   // 将创建的context对象记录在servletContext中,创建并且准备好了spring容器
   servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
   
   // 返回创建好的context
   return this.context;
}

  创建web应用上下文对象,通过反射实现,ContextLoader#createWebApplicationContext 核心伪代码:

1 //  创建Spring父容器对象
2 protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
3    // 获取contextClass的Class对象
4    Class<?> contextClass = determineContextClass(sc);
5    
6    // 实例化容器对象
7    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
8 }

  加载Spring配置文件,并刷新父容器,ContextLoader#configureAndRefreshWebApplicationContext 核心伪代码:

 1 // 刷新容器
 2 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
 3    // 为Spring容器设置Servlet上下文
 4    wac.setServletContext(sc);
 5    // 获取Spring父容器的配置,用于刷新Spring容器
 6    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
 7    if (configLocationParam != null) {
 8       wac.setConfigLocation(configLocationParam);
 9    }
10    
11    // 刷新Spring父容器
12    wac.refresh();
13 }

  加载Spring的配置文件,用于Spring父容器的刷新操作。

  wac.refresh();步骤会进入到SpringIOC启动流程中,AbstractApplicationContext#refresh(),可查看Spring源码分析系列,此处不再赘述。

3.2、SpringMVC容器启动

  MVC容器刷新入口遵循Servlet的生命周期,HttpServletBean#init() 核心伪代码:
 1 public final void init() throws ServletException {
 2 
 3    // 将servlet中配置的init-param参数封装到pvs变量中,获取SpringMVC的配置文件
 4    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
 5    if (!pvs.isEmpty()) {
 6        // 将当前的servlet对象转化成BeanWrapper对象,从而能够以spring的方法来将pvs注入到该beanWrapper中
 7        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
 8        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
 9        // 注册自定义属性编辑器,一旦有Resource类型的属性,将会使用ResourceEditor进行解析
10        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
11        // 模板方法,可以在子类调用,做一些初始化工作,bw代表的是DispatcherServlet
12        initBeanWrapper(bw);
13        // 以spring的方式来将pvs注入到该beanWrapper对象中,将配置的初始化值(contextConfigLocation)设置到DispatcherServlet
14        bw.setPropertyValues(pvs, true);
15    }
16    // 模板方法,子类初始化的入口方法,查看FrameworkServlet#initServletBean方法
17    initServletBean();
18 }

1、SpringMVC容器配置文件获取

  获取SpringMVC的配置文件,用于刷新MVC容器做的准备工作。

 

2、servlet对象的封装

  将当前的servlet对象转化成BeanWrapper对象,从而能够以spring的方法来将pvs注入到该beanWrapper中

3、注册定义属性编辑器

  注册自定义属性编辑器,一旦有Resource类型的属性,将会使用ResourceEditor进行解析

4、加载初始化配置

  以spring的方式来将pvs注入到该beanWrapper对象中,将配置的初始化值(contextConfigLocation)设置到DispatcherServlet

5、SpringMVC容器启动

  初始化Servlet相关bean,FrameworkServlet#initServletBean 核心伪代码

 1 // 初始化Servlet相关的bean
 2 protected final void initServletBean() throws ServletException {
 3    // 记录开启时间
 4    long startTime = System.currentTimeMillis();
 5       
 6    // 创建或刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化
 7    this.webApplicationContext = initWebApplicationContext();
 8    
 9    // 模板方法,空实现,留给子类扩展
10    initFrameworkServlet();
11 }

5.1、初始化SpringMVC容器

  创建并刷新SpringMVC容器,FrameworkServlet#initWebApplicationContext 核心伪代码

 1 protected WebApplicationContext initWebApplicationContext() {
 2    // 获得根webApplicationContext对象,Spring父容器
 3    WebApplicationContext rootContext =
 4          WebApplicationContextUtils.getWebApplicationContext(getServletContext());
 5    // 声明webApplicationContext wac对象,用于SpringMVC子容器
 6    WebApplicationContext wac = null;
 7 
 8    // 当前面两种方式都无效的情况下会创建一个webApplicationContext对象,一般情况下都是使用这样的方式
 9    if (wac == null) {
10       // 创建并刷新SpringMVC容器
11       wac = createWebApplicationContext(rootContext);
12    }
13    
14    // 将applicationContext设置到servletContext中
15    if (this.publishContext) {
16       String attrName = getServletContextAttributeName();
17       getServletContext().setAttribute(attrName, wac);
18    }
19 
20    return wac;
21 }
1、获取Spring父容器
2、创建并刷新SpringMVC容器

  FrameworkServlet#createWebApplicationContext 核心伪代码

 1 protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
 2     
 3    // 获取servlet的初始化参数contextClass,如果没有配置默认为XmlWebApplicationContext.class
 4    Class<?> contextClass = getContextClass();
 5    
 6    // 通过反射方式实例化contextClass
 7    ConfigurableWebApplicationContext wac =
 8          (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
 9 
10    // parent为在ContextLoaderListener中创建的实例,在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例
11    wac.setParent(parent);
12    
13    // 获取contextConfigLocation属性,配置在servlet初始化参数中
14    String configLocation = getContextConfigLocation();
15    if (configLocation != null) {
16       // 将设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/servletName-servlet.xml
17       wac.setConfigLocation(configLocation);
18    }
19    // 配置和初始化wac
20    configureAndRefreshWebApplicationContext(wac);
21 
22    return wac;
23 }
1、获取MVC容器的Class对象,并通过反射实例化MVC容器对象;
2、为MVC容器设置Spring父容器;
       0
3、获取SpringMVC配置文件,为MVC容器刷新做准备
     0
4、配置并刷新MVC容器

  FrameworkServlet#configureAndRefreshWebApplicationContext 核心伪代码

 1 // 配置并刷新MVC容器
 2 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
 3     
 4    // 添加监听器sourceFilteringListener到wac中,实际监听的是ContextRefreshListener所监听的事件,监听ContextRefreshedEvent事件,
 5    // 当接收到消息之后会调用onApplicationEvent方法,调用onRefresh方法,并将refreshEventReceived标志设置为true,表示已经refresh过
 6    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
 7 
 8    // 获取环境对象并且添加相关的属性
 9    ConfigurableEnvironment env = wac.getEnvironment();
10    if (env instanceof ConfigurableWebEnvironment) {
11       ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
12    }
13    
14    // 刷新wac,从而初始化wac
15    wac.refresh();
16 }

  MVC容器刷新流程与Spring容器舒新流程相同,都是执行AbstractApplicationContext#refresh()方法。

3、MVC容器内置九大组件

  之前在分析Spring的源码中,创建剩余单例对象的方法是 finishBeanFactoryInitialization(beanFactory);,在MVC执行完此方法后,MVC容器内置的九大组件,并未被创建。

由此大胆猜测下,MVC的九大组件是在完成MVC容器刷新后,创建出来的,也就是在容器刷新的最后一个方法finishRefresh()中,到底是不是呢?我们接着往下分析。

MVC容器刷新时的处理,AbstractApplicationContext#finishRefresh()核心伪代码:

1 // 完成容器刷新时的处理
2 protected void finishRefresh() {
3    // ...
4    // 新建ContextRefreshedEvent事件对象,将其发布到所有监听器。
5    publishEvent(new ContextRefreshedEvent(this));
6    //...
7 }

  AbstractApplicationContext#publishEvent()发布事件核心伪代码:

 1 // 将给定事件发布到所有监听器
 2 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
 3  
 4    // 装饰事件作为一个应用事件,如果有必要
 5    ApplicationEvent applicationEvent;
 6    // 如果event是ApplicationEvent的实例
 7    if (event instanceof ApplicationEvent) {
 8       // 将event强转为ApplicationEvent对象
 9       applicationEvent = (ApplicationEvent) event;
10    }
11   
12    // 多播applicationEvent到适当的监听器
13    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
14    
15     // ...
16 }

1、事件强转为applicationEvent类型;

2、通过多播器广播事件

  SimpleApplicationEventMulticaster#multicastEvent 核心伪代码

 1 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 2     
 3    // 如果事件类型eventType不为null就引用eventType;否则将event转换为ResolvableType对象再引用
 4    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 5    // 获取此多播器的当前任务线程池
 6    Executor executor = getTaskExecutor();
 7    // getApplicationListeners方法是返回与给定事件类型匹配的应用监听器集合
 8    // 遍历获取所有支持event的监听器
 9    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
10       //回调listener的onApplicationEvent方法,传入event
11       invokeListener(listener, event);
12    }
13 }
 

3、调用事件处理监听器,处理MVC容器刷新完成发布的事件

  SimpleApplicationEventMulticaster#doInvokeListener 核心伪代码

1 / /回调listener的onApplicationEvent方法
2 // 传入event:contextrefreshListener:onapplicaitonEvent:FrameworkServlet.this.onApplicationEvent()
3 private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
4     // 处理事件        
5     listener.onApplicationEvent(event);
6 }

 

3.1、通过多播器广播事件

  FrameworkServlet#onApplicationEvent 核心伪代码

1 public void onApplicationEvent(ContextRefreshedEvent event) {
2    // 标记 refreshEventReceived 为true
3    this.refreshEventReceived = true;
4    synchronized (this.onRefreshMonitor) {
5       // 处理事件中的 ApplicationContext 对象,空实现,子类DispatcherServlet会实现
6       onRefresh(event.getApplicationContext());
7    }
8 }

 

3.2、初始化九大组件

  MVC容器刷新完成初始化完成,初始化策略,DispatcherServlet#onRefresh 核心伪代码

 
1 protected void onRefresh(ApplicationContext context) {
2    // ...
3    initStrategies(context);
4 }

  初始化MVC容器九大组件,DispatcherServlet#initStrategies 核心伪代码

 1 // 初始化MVC组件
 2 protected void initStrategies(ApplicationContext context) {
 3    // 初始化 MultipartResolver:主要用来处理文件上传.如果定义过当前类型的bean对象,那么直接获取,如果没有的话,可以为null
 4    initMultipartResolver(context);
 5    // 初始化 LocaleResolver:主要用来处理国际化配置,基于URL参数的配置(AcceptHeaderLocaleResolver),
 6    // 基于session的配置(SessionLocaleResolver),基于cookie的配置(CookieLocaleResolver)
 7    initLocaleResolver(context);
 8    // 初始化 ThemeResolver:主要用来设置主题Theme
 9    initThemeResolver(context);
10    // 初始化 HandlerMapping:映射器,用来将对应的request跟controller进行对应
11    initHandlerMappings(context);
12    // 初始化 HandlerAdapter:处理适配器,主要包含Http请求处理器适配器,简单控制器处理器适配器,注解方法处理器适配器
13    initHandlerAdapters(context);
14    // 初始化 HandlerExceptionResolver:基于HandlerExceptionResolver接口的异常处理
15    initHandlerExceptionResolvers(context);
16    // 初始化 RequestToViewNameTranslator:当controller处理器方法没有返回一个View对象或逻辑视图名称,
17    // 并且在该方法中没有直接往response的输出流里面写数据的时候,spring将会采用约定好的方式提供一个逻辑视图名称
18    initRequestToViewNameTranslator(context);
19    // 初始化 ViewResolver: 将ModelAndView选择合适的视图进行渲染的处理器
20    initViewResolvers(context);
21    // 初始化 FlashMapManager: 提供请求存储属性,可供其他请求使用
22    initFlashMapManager(context);
23 }

  MVC的web应用上下文刷新成功后,Spring父容器、MVC容器就已经完成了启动。

 
posted @ 2023-02-10 20:23  无虑的小猪  阅读(443)  评论(0编辑  收藏  举报