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父容器启动流程
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容器启动
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、注册定义属性编辑器
4、加载初始化配置
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父容器;
3、获取SpringMVC配置文件,为MVC容器刷新做准备
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容器就已经完成了启动。