基于最新Spring 5.x,详细介绍了Spring MVC 初始化流程的源码,主要包括DispatcherServlet与MVC子容器的初始化,以及各种MVC组件的初始化。
上一篇文章我们讲解了ContextLoaderListener监听器与根上下文容器的初始化。
在ContextLoaderListener
的contextInitialized
方法回调完毕之后,Root WebApplicationContext
初始化完毕,随后会初始化全部的Filter,并且执行init
回调,最后会按顺序初始化全部的即时创建的Servlet,对于Spring MVC
来说,最重要的就是DispatcherServlet
,该过程同时会涉及到MVC子容器的创建和初始化,以及各种MVC组件的初始化
。一起来看看DispatcherServlet的初始化源码吧!
下面的源码版本基于5.2.8.RELEASE
。
Spring MVC源码 系列文章
Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化
Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】
Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析
Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析
文章目录
- Spring MVC源码 系列文章
- 1 DispatcherServlet的概述
- 2 HttpServletBean#init()设置init属性
- 3 FrameworkServlet#initServletBean初始化MVC容器
- 4 DispatcherServlet#onRefresh初始化MVC组件
- 5 总结
1 DispatcherServlet的概述
DispatcherServlet作为Spring MVC的核心类,基本上所有的请求都是通过该Servlet来进行分发。此前的Spring MVC的学习系列文章中我们已经详细学习了它的功能和流程,现在我们来学习它的源码。
DispatcherServlet的uml类图如下:
可以看到DispatcherServlet最终继承了HttpServlet方法,实现了Servlet
接口,因此它也是一个JavaEE中的Servlet标准实现
,并且主要处理HTTP请求。
HttpServletBean
是HttpServlet的简单的抽象实现,主要功能是解析web.xml
文件中的<servlet/>
标签下面的<init-param/>
标签配置的参数,将其设置为bean的对应的属性。该Servlet将具体的请求处理留给子类实现,仅仅继承HttpServlet的默认行为(doGet,doPost等)。
FrameworkServlet
是Spring Web框架的基础servlet。该类的功能有两个:
- 为每个该类型的servlet关联一个子WebApplicationContext实例。Servlet的配置由Servlet名称空间中的bean确定。
- 发布有关请求处理的事件,无论是否成功处理了请求。子类必须实现doService方法以执行真正的请求处理。
DispatcherServlet
是HTTP请求处理程序/控制器的中央调度程序,在初始化的时候它会初始化各个功能组件的实现类,并且在后续实现具体的请求处理流程,主要是通过调度各个组件来处理请求,几乎可以处理所有请求。
DispatcherServlet的一系列初始化操作基本都是在init
方法中完成的,这个init
方法就是Servelet接口提供初始化方法,因此我们从该方法入手。
/** * GenericServlet的属性 * 保存了ServletConfig参数 */
private transient ServletConfig config;
/** * GenericServlet的init方法 * * @param config Servlet配置 */
public void init(ServletConfig config) throws ServletException {
this.config = config;
//调用初始化方法
this.init();
}
/** 1. GenericServlet的init方法 2. <p> 3. 应该被子类重写的方法 */
public void init() throws ServletException { }
该方法整体来说做了三件事:
- 将当前
ServletConfig
的init parameters
参数填充到当前DispatcherServlet
的实例的对应的属性中。可以在web.xml中
对应的<Servlet>
标签下通过设置<init-param>
标签来配置各种属性。主要是HttpServletBean#init()
方法。 - 初始化于此
Servlet
关联的MVC子容器
,该容器的父容器就是Root Application
(可能为null),随后会以属性名:org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName
,value
为此容器的方法存入ServletContext
的属性中。主要是FrameworkServlet#initServletBean
方法。 - 初始化
Servlet
使用的MVC组件
。主要是DispatcherServlet#onRefresh
方法。
2 HttpServletBean#init()设置init属性
HttpServletBean
的init()
方法就是将当前ServletConfig
的init parameters
参数填充到当前DispatcherServlet
的实例的对应的属性中。
也就是将web.xml
中<servlet/>
标签下的<init-param/>
标签定义的属性设置到对应Servlet实例的对应名字的属性中,最常见属性就是contextConfigLocation、namespace
等等。
在属性填充完毕之后们将会调用initServletBean
方法,HttpServletBean提供了空的实现,主要用于被子类重写并实现自定义的扩展逻辑。它的子类FrameworkServlet
就重写了该方法。
/** * GenericServlet的init方法 * <p> * 应该被子类重写的方法 */
public void init() throws ServletException {
}
/** * HttpServletBean的属性 * <p> * 必须的参数集合,默认是一个空集合 */
private final Set<String> requiredProperties = new HashSet<>(4);
/** * HttpServletBean的方法 * <p> * 将配置参数映射到该servlet的bean属性上,并调用子类初始化。 * * @throws ServletException 如果bean属性无效(或缺少必需的属性),或者子类初始化失败。 */
@Override
public final void init() throws ServletException {
/* * 1 新建一个ServletConfigPropertyValues实例,根据Servlet的init参数设置bean属性 * 就是将ServletConfig内部的init parameters初始化参数存储到ServletConfigPropertyValues中 * 如果有requiredProperties中的必须属性没有设置,那么抛出ServletException */
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//如果有参数
if (!pvs.isEmpty()) {
try {
//获取当前DispatcherServlet 对象的BeanWrapper对象,以JavaBeans的样式便捷的访问属性。
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//ServletContext的资源加载器
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//自定义的属性编辑器
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//初始化DispatcherServlet的BeanWrapper,该方法是一个空实现
initBeanWrapper(bw);
/* * 2 通过BeanWrapper便捷的将解析出来的属性集合设置给DispatcherServlet的各种属性 */
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
/* * 3 扩展接口,让子类进行他们自定义的初始化 * 子类FrameworkServlet就重写了该方法 */
initServletBean();
}
/** * HttpServletBean的方法 * <p> * 子类可以重写此方法以执行自定义初始化。 * 在调用此方法之前,将设置此servlet的所有bean属性。 * <p> * 此默认实现为空。 */
protected void initServletBean() throws ServletException {
}
3 FrameworkServlet#initServletBean初始化MVC容器
FrameworkServlet重写的initServletBean方法。
其内部首先调用initWebApplicationContext
方法用于初始化并发布此Servlet关联的WebApplicationContext
容器,随后调用initFrameworkServlet
方法初始化FrameworkServlet
本身,子类可以重写此方法以执行其所需的任何初始化,默认实现为空。
/** * FrameworkServlet的属性 * <p> * 此Servlet关联的WebApplicationContext */
@Nullable
private WebApplicationContext webApplicationContext;
/** 1. FrameworkServlet的方法 2. <p> 3. 设置所有bean属性后调用的方法,主要目的是创建此Servlet的WebApplicationContext。 */
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
//当前时间戳
long startTime = System.currentTimeMillis();
try {
/* * 1 初始化并发布此Servlet关联的WebApplicationContext,核心方法 */
this.webApplicationContext = initWebApplicationContext();
/* * 2 初始化FrameworkServlet,在设置任何bean属性并加载WebApplicationContext后,将调用此方法。 * * 子类可以重写此方法以执行其所需的任何初始化,默认实现为空。 */
initFrameworkServlet();
} catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
3.1 initWebApplicationContext初始化子MVC容器
该方法用于初始化并发布此Servlet关联的WebApplicationContext,也就是子容器。实际上大部分情况是通过内部的createWebApplicationContext方法实际创建上下文,可以在子类中覆盖。
- 获取
Root WebApplicationContext
作为父上下文,Root WebApplicationContext是通过ContextLoaderListener
初始化的,可以为null。 - 如果
webApplicationContext
属性不为null,那么直接调用configureAndRefreshWebApplicationContext
配置并刷新该WebApplicationContext,一般都是null。 - 如果此前
没有
关联的WebApplicationContext,那么首先会尝试查找现成的context。首先当前Servlet中获取名为contextAttribute
的<init-param/>
初始化参数的值,该值作为属性名用于检索该servlet应该使用的WebApplicationContext。然后从当前ServletContext
中调用getAttribute
方法基于该属性名获取对应属性值。如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext
实例返回,如果获取的属性值为null
,那么抛出:“err.servlet_config_not_initialized“异常。一般都不会找到。 - 如果在属性中没有指定的
WebApplicationContext
实例,一般来说都没有。那么调用createWebApplicationContext
实例化此servlet关联的WebApplicationContext
,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 - 如果此前还没有调用
onRefresh
方法,那么调用一次onRefresh
方法,该方法是一个可以被子类重写的模板方法,用于特定的servlet的添加自定义的刷新工作,在成功刷新WebApplicationContext后调用。- 如果是
新建
子MVC容器,容器刷新完毕后会发送ContextRefreshedEvent事件
,会触发ContextRefreshListener
监听器回调。该监听器的回调就是执行FrameworkServlet的onApplicationEvent
方法,内部就会执行该方法,并更改标志为true。 - 如果上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新,那么标志就是false。
- 如果是
- 如果应该将此
WebApplicationContext
发布为ServletContext
的属性(默认需要设置),那么就设置为ServletContext
的一个属性。属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT.+servletName
。
/** * FrameworkServlet的方法 * <p> * 初始化并发布此Servlet关联的WebApplicationContext。 * 通过内部的createWebApplicationContext方法实际创建上下文,可以在子类中覆盖。 * * @return WebApplicationContext实例 */
protected WebApplicationContext initWebApplicationContext() {
/* * 1 获取Root WebApplicationContext * 父上下文通过ContextLoaderListener初始化,可以为null */
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
/* * 2 如果webApplicationContext属性不为null,那么直接配置并刷新该WebApplicationContext */
if (this.webApplicationContext != null) {
//到这里表示在构造时注入了一个上下文实例->直接使用它
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
// 上下文尚未刷新->提供诸如设置父上下文,设置应用程序上下文ID等服务。
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
//上下文实例是在没有显式父级的情况下注入的->将根应用程序上下文(如果有可能为null)设置为父级
cwac.setParent(rootContext);
}
//配置并初始化此上下文容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
/* * 3 如果wac为null,即此前没有关联的WebApplicationContext * * 首先当前Servlet中获取名为contextAttribute的<init-param/>初始化参数的值, * 该值作为属性名用于检索该servlet应该使用的WebApplicationContext。 * 然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。 * 如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回, * 如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。 */
if (wac == null) {
wac = findWebApplicationContext();
}
/* * 4 如果wac还是为null,即在属性中没有指定的WebApplicationContext实例,一般来说都没有 * * 那么实例化此servlet关联的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 */
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
/* * 5 如果此前还没有调用onRefresh方法 * * 那么调用一次onRefresh方法,该方法是一个可以被子类重写的模板方法 * 用于特定的servlet的添加自定义的刷新工作,在成功刷新WebApplicationContext后调用。 * * 如果是新建子MVC容器,容器刷新完毕后会发送ContextRefreshedEvent事件,会触发ContextRefreshListener监听器回调 * 该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法,内部就会执行该方法,并更改标志为true * 如果上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新,那么标志就是false */
if (!this.refreshEventReceived) {
//上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新
//那么在此处手动触发onRefresh初始化方法
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
/* * 6 如果应该将此WebApplicationContext发布为ServletContext的属性,那么就设置为属性,默认需要设置 */
if (this.publishContext) {
// Publish the context as a servlet context attribute.
//属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName
String attrName = getServletContextAttributeName();
//设置为ServletContext的一个属性
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
/** * FrameworkServlet的方法 * <p> * WebApplicationContext的ServletContext属性的前缀 */
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
/** 1. 返回此Servlet关联的WebApplicationContext在ServletContext中的属性名称。 2. 默认实现返回SERVLET_CONTEXT_PREFIX + servletName */
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
3.1.1 configureAndRefreshWebApplicationContext配置并刷新子容器
该方法用于配置并刷新此Servlet关联的MVC子容器,步骤类似于此前文章中讲过的Root容器的同名方法。
大概步骤如下:
- 设置该应用程序上下文的id(一般用不到),默认id就是"
org.springframework.web.context.WebApplicationContext:"+项目路径+"/"+servletName
,可以通过在web.xml中的<Servlet/>
标签下配置名为contextId
的<init-param/>
初始化参数来自定义子容器id。 - 将此项目的
ServletContext
设置给上下文的servletContext
属性,将此Servlet的ServletConfig
设置给上下文的servletConfig
属性,将此Servlet的namespace
设置给上下文的namespace
属性,手动添加一个监听器SourceFilteringListener
。SourceFilteringListener
内部包装了一个ContextRefreshListener
,容器刷新完毕后会发送ContextRefreshedEvent
事件,此时会触发监听器的回调,该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法
。
- 获取容器的
Environment
环境变量对象,随后调用initPropertySources
方法手动初始化Servlet属性源,该方法在refresh()
刷新容器的方法之前执行,以确保servlet属性源已准备就绪,可以被refresh()方法正常使用。 - 调用
postProcessWebApplicationContext
方法,在刷新给定的WebApplicationContext并将其激活关联为该Servlet的上下文之前,对其进行后处理,即自定义
容器。目前是一个空实现,子类可以重写。 - 调用
applyInitializers
方法用于继续对容器执行自定义
操作。默认实现是通过web.xml
中配置的<context-param>
全局参数globalInitializerClasses
,和来自当前<Servlet/>
内部的<init-param>
初始化参数contextInitializerClasses
来确定指定了哪些ApplicationContextInitializer
类,并执行初始化,随后使用AnnotationAwareOrderComparator
排序(支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序),最后按照排序优先级从高到低依次调用每一个实例的initialize方法来初始化给定的servletContext。
6 调用容器的refresh
方法执行刷新
操作,这是核心方法
,我们在此前IoC容器初始化源码部分已经着重讲解了。该方法将会初始化容器,包括解析配置文件,创建Spring bean实例,执行各种回调方法等等……操作(源码非常多)。
/** * FrameworkServlet的属性 * <p> * 要分配的WebApplicationContext ID,可通过<init-param/>参数配置 */
@Nullable
private String contextId;
/** * FrameworkServlet的方法 * <p> * 配置并刷新新建的mvc WebApplicationContext * 该方法中会配置一系列Servlet的属性,初始化并调用ApplicationContextInitializer的扩展点(用于自定义root context),最后会执行refresh刷新容器。 * * @param wac 此servlet关联的上下文 */
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
/* * 1 如果wac的全路径identity字符串形式等于wac的id,那么设置应用程序上下文的id */
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
//应用程序上下文ID仍设置为其原始默认值->可以通过<init-param/>参数配置
if (this.contextId != null) {
wac.setId(this.contextId);
} else {
//产生预设id,默认规则就是"org.springframework.web.context.WebApplicationContext:"+项目路径+"/"+servletName
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
/* * 2 配置一系列属性 */
//将此项目的ServletContext设置给上下文的servletContext属性
wac.setServletContext(getServletContext());
//将此Servlet的ServletConfig设置给上下文的servletConfig属性
wac.setServletConfig(getServletConfig());
//将此Servlet的namespace设置给上下文的namespace属性
wac.setNamespace(getNamespace());
//手动添加一个监听器SourceFilteringListener
//在容器刷新完毕之后会发送ContextRefreshedEvent事件,此时就会触发监听器的回调
//该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
/* * 3 获取容器的Environment环境变量对象,随后调用initPropertySources方法手动初始化属性源 * 该方法在refresh()刷新容器的方法之前执行,以确保servlet属性源已准备就绪,可以被正常使用 */
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
/* * 4 在刷新给定的WebApplicationContext并将其激活关联为该Servlet的上下文之前,对其进行后处理 * 目前是一个空实现,子类可以重写 */
postProcessWebApplicationContext(wac);
/* * 5 对当前的WebApplicationContext实例应用给定的ApplicationContextInitializer,以实现自定义上下文的逻辑 * 这类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法 */
applyInitializers(wac);
/* * 6 刷新(初始化)容器 * 这是核心方法,我们在此前IoC容器初始化源码部分已经着重讲解了 */
wac.refresh();
}
3.1.1.1 getNamespace获取名称空间
Servlet对应的容器的nameSpace
就被设置为Servlet的namespace
,可以通过设置该Servlet的名为nameSpace
的<init-param>
初始化参数手动指定名称空间,如未指定,那么默认名称空间为servletName+"-servlet"
,即如果此servlet的servlet-name为"test",则该servlet使用的默认名称空间将解析为"test-servlet"
。
/** * FrameworkServlet中的常量属性 * <p> * WebApplicationContext名称空间的后缀。 * 如果此类的servlet在上下文中被命名为"test",则servlet使用的默认名称空间将解析为"test-servlet"。 */
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
/** * FrameworkServlet的方法 * <p> * 获取此nameSpace * <p> * 返回此servlet的名称空间,如果未设置自定义名称空间,则返回默认方案: * 即默认nameSpace为servletName+"-servlet",也可以通过设置Servlet的nameSpace属性手动指定名称空间 */
public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
3.1.1.2 applyInitializers应用ApplicationContextInitializer扩展
基于ApplicationContextInitializer
自定义MVC子容器,这个方法类似于在初始化Root WebApplicationContext的时候调用的customizeContext
方法。
默认实现是通过web.xml
中配置的<context-param>
全局参数globalInitializerClasses
,和来自当前<Servlet/>
内部的<init-param>
初始化参数contextInitializerClasses
来确定指定了哪些ApplicationContextInitializer
类,并执行初始化,随后使用AnnotationAwareOrderComparator排序(支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序),最后按照排序优先级从高到低依次调用每一个实例的initialize方法来初始化给定的servletContext。
//FrameworkServlet的属性
/** * 实际要应用于上下文的ApplicationContextInitializer实例。 */
private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
new ArrayList<>();
/** * 在设置的ApplicationContextInitializer的全路径类名 */
@Nullable
private String contextInitializerClasses;
/** * FrameworkServlet的方法 * <p> * 在对给定容器应用刷新之前调用所有的ApplicationContextInitializer,一起带实现自定义容器的逻辑 * 这个方法类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法 * * @param wac 配置的WebApplicationContext(尚未刷新) */
protected void applyInitializers(ConfigurableApplicationContext wac) {
/* * 1 获取并初始化全局ApplicationContextInitializer */
//从servletContext中尝试获取globalInitializerClasses全局参数的值
String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
//如果设置了该全局参数
if (globalClassNames != null) {
//根据",; \t\n"拆分值字符串
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
//根据给定的全路径名字符串初始化指定的ApplicationContextInitializer实例并加入contextInitializers集合中
this.contextInitializers.add(loadInitializer(className, wac));
}
}
/* * 1 获取并初始化当前Servlet的ApplicationContextInitializer */
//当前Servlet如果设置了名为contextInitializerClasses的<init-param>初始化参数
if (this.contextInitializerClasses != null) {
//根据",; \t\n"拆分值字符串
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
//根据给定的全路径名字符串初始化指定的ApplicationContextInitializer实例并加入contextInitializers集合中
this.contextInitializers.add(loadInitializer(className, wac));
}
}
/* * 3 对该集合进行Order排序,可以支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序 * 比较优先级为PriorityOrdered>Ordered>@Ordered>@Priority,排序规则是order值越小排序越靠前,优先级越高 * 没有order值则默认排在尾部,优先级最低。 */
AnnotationAwareOrderComparator.sort(this.contextInitializers);
/* * 4 按照优先级从高到低依次调用ApplicationContextInitializer的initialize方法,传递的参数就是当前的应用程序上下文容器 */
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
3.1.2 findWebApplicationContext查找上下文容器
如果此前没有已存在的关联的容器,那么首先会尝试查找指定的容器。
首先当前Servlet中获取名为contextAttribute
的<init-param/>
初始化参数的值,该值作为属性名用于检索该servlet应该使用的WebApplicationContext。然后从当前ServletContext
中调用getAttribute
方法基于该属性名获取对应属性值。如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回,如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。
这个逻辑一般是不会走的,因为Servlet的名为contextAttribute
的<init-param/>
初始化参数一般没有设置。
/** * FrameworkServlet的方法 * <p> * 使用配置的名称从ServletContext属性检索已配置好的WebApplicationContext。 * 子类可以重写此方法以提供不同的WebApplicationContext检索策略。 * * @return 此Servlet的WebApplicationContext;如果找不到,则为null */
@Nullable
protected WebApplicationContext findWebApplicationContext() {
//尝试获取该Servlet的名为contextAttribute的<init-param/>初始化参数的值
String attrName = getContextAttribute();
//如果参数值为null,那么返回null
if (attrName == null) {
return null;
}
//如果参数值不为null,那么从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
//如果参数值为null,那么抛出"No WebApplicationContext found: initializer not registered?"异常
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
//返回结果
return wac;
}
/** * ServletContext的属性名字符串,可在其中找到WebApplicationContext。 */
@Nullable
private String contextAttribute;
/** 1. 返回ServletContext属性的名称,该属性应用于检索该servlet应该使用的WebApplicationContext。 */
@Nullable
public String getContextAttribute() {
return this.contextAttribute;
}
3.1.3 createWebApplicationContext创建子上下文容器
如果没有找到上下文容器,那么创建与此Servlet绑定的MVC上下文容器。其核心步骤和此前创建Root容器的步骤差不多,包含了创建和刷新
的工作。
- 获取给定的上下文类
Class
,默认是XmlWebApplicationContext.class
,可以通过此Servlet的名为contextClass
的<init-param>
初始化参数配置。 - 用无参构造器,根据给定的
Class
创建一个上下文实例。 - 设置WebApplicationContext的
配置路径
,通过此Servlet的名为contextConfigLocation
的<init-param>
初始化参数配置。 - 调用
configureAndRefreshWebApplicationContext
方法配置并刷新
新建的mvc WebApplicationContext,这个方法我们在此前介绍过了。
/** * FrameworkServlet的方法 * <p> * 实例化此servlet的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 * 内部委托给createWebApplicationContext(ApplicationContext)方法 * * @param parent 要使用的父WebApplicationContext;如果没有,则为 null * @return 该servlet关联的WebApplicationContext */
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
/** * FrameworkServlet的方法 * <p> * 实例化此servlet的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。 * * @param parent 要使用的父ApplicationContext;如果没有,则为null * @return 该servlet关联的WebApplicationContext */
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
/* * 1 获取给定的上下文类,默认是XmlWebApplicationContext.class * 可以通过此Servlet的名为contextClass的<init-param>初始化参数配置 */
Class<?> contextClass = getContextClass();
//如果不是ConfigurableWebApplicationContext类型则抛出异常
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");
}
/* * 2 调用无参构造器,根据给定的Class创建一个上下文实例 */
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置环境变量
wac.setEnvironment(getEnvironment());
//设置父容器
wac.setParent(parent);
/* * 3 设置WebApplicationContext的配置路径 */
//获取上下文的配置路径,通过此Servlet的名为contextConfigLocation的<init-param>初始化参数配置
String configLocation = getContextConfigLocation();
if (configLocation != null) {
//如果配置了该初始参数,则设置为容器的configLocation参数,支持使用",; \t\n"拆分
wac.setConfigLocation(configLocation);
}
/* * 4 配置并刷新新建的mvc WebApplicationContext */
configureAndRefreshWebApplicationContext(wac);
return wac;
}
/** * FrameworkServlet的属性 * <p> * 默认创建的WebApplicationContext实现类,就是XmlWebApplicationContext.class */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
/** * FrameworkServlet的属性 * <p> * 指定上下文配置位置。 */
@Nullable
private String contextConfigLocation;
/** * FrameworkServlet的属性 * <p> * 返回使用的上下文类 */
public Class<?> getContextClass() {
return this.contextClass;
}
/** * 返回显式指定的上下文配置位置(如果有)。 */
@Nullable
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
3.1.4 ContextRefreshListener监听器
configureAndRefreshWebApplicationContext
方法中,会手动添加一个手动添加一个监听器SourceFilteringListener
。SourceFilteringListener
内部包装了一个ContextRefreshListener
,容器刷新完毕后会发送ContextRefreshedEvent
事件,此时会触发监听器的回调,该监听器的回调就是执行FrameworkServlet的onApplicationEvent
方法。
/** * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance. */
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
onApplicationEvent
方法的默认实现调用onRefresh
方法,触发此Servlet的上下文相关状态的刷新。
//FrameworkServlet的方法
/** * 用于检测是否已调用onRefresh的标志。 */
private volatile boolean refreshEventReceived = false;
/** * 监视同步的onRefresh方法执行的同步监视器 */
private final Object onRefreshMonitor = new Object();
/** * FrameworkServlet的方法 * <p> * 从此Servlet的WebApplicationContext接收刷新事件的回调。 * 默认实现调用onRefresh,触发此Servlet的上下文相关状态的刷新。 * * @param event 传入的ApplicationContext事件 */
public void onApplicationEvent(ContextRefreshedEvent event) {
//标志位改为true
this.refreshEventReceived = true;
//添加同步
synchronized (this.onRefreshMonitor) {
//调用onRefresh方法
onRefresh(event.getApplicationContext());
}
}
/** * FrameworkServlet的方法 * <p> * 可以重写的模板方法以添加特定于servlet的刷新工作,成功刷新上下文后调用。 * 此实现为空,子类可重写该方法 * * @param context 当前的WebApplicationContext */
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
4 DispatcherServlet#onRefresh初始化MVC组件
FrameworkServlet
的onRefresh
方法的默认实现为空
,子类DispatcherServlet
重写了该方法,用于初始化此servlet使用的各种MVC组件对象(通过initStrategies方法)
,这同样是一个核心方法
。该方法执行之前,Root WebApplicationContext和DispatcherServlet关联的Child WebApplicationContext
都已经创建并初始化完毕。
需要注意的是,通过DispatcherServlet.properties
默认加载的组件仅会创建对象实例,仅提供最基本的mvc功能而并没有激活其他默认配置,而如果通过@EnableWebMvc
注解或者<mvc:annotation-driven/>
标签开启mvc配置之后,除了会注册这些组件之外,还会加载一些默认配置,并且支持自定义配置。
比如,如果不开启MVC配置,那么mvc就不支持application/json
请求和响应(即使有jackson的依赖也不会自动注册MappingJackson2HttpMessageConverter
),也就无法提供REST风格的请求、响应和异常处理,没有默认的conversionService
!
/** * DispatcherServlet的方法 * <p> * 此实现内部调用initStrategies方法 */
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/** * DispatcherServlet的方法 * <p> * 默认实现是初始化此DispatcherServlet使用的组件对象,可以在子类中重写以实现其他的自定义逻辑 */
protected void initStrategies(ApplicationContext context) {
/* * 1 初始化MultipartResolver,用于处理上传请求 * * 文件上传时就可以使用MultipartResolver来解析上传请求中的文件数据,方便快捷的实现文件上传! */
initMultipartResolver(context);
/* * 2 初始化LocaleResolver,区域解析器 * * 用户的区域也称为Locale,Locale信息是可以由前端直接获取的,可以根据不同的用户区域展示不同的视图, * 比如为不同区域的用户可以设置不同的语言和时区,也就是提供国际化视图支持。 */
initLocaleResolver(context);
/* * 3 初始化ThemeResolver,主题解析器 * * 主题就是系统的整体样式或风格,可通过Spring MVC框架提供的主题(theme) * 设置应用不同的整体样式风格,提高用户体验。Spring MVC的主题就是一些静态资源的集合, * 即包括样式及图片,用来控制应用的视觉风格。主题也支持国际化,同一个主题不同区域也可以显示不同的风格。 */
initThemeResolver(context);
/* * 4 初始化HandlerMapping,处理器映射器 * * 用于查找能够处理请求的Handler,将请求映射为HandlerExecutionChain 对象 * (包含一个Handler处理器对象、多个 HandlerInterceptor 拦截器)对象。 */
initHandlerMappings(context);
/* * 5 初始化HandlerAdapter,处理器适配器 * 帮助 DispatcherServlet调用请求映射到的Handler,但是不管Handler实际如何调用。 * 将会返回一个ModelAndView对象,其中model是一个Map结构,存放了我们返回的所有数据,view是逻辑视图名,即ViewName。 */
initHandlerAdapters(context);
/* * 6 初始化HandlerExceptionResolver,异常解析器 * 如果在前面执行handler的过程中抛出了某个异常,将会走异常解析器的方法! * 在异常解析器中可以将此错误映射到其他handler、HTML 错误视图(错误页面)或统一抛出自己的异常。 */
initHandlerExceptionResolvers(context);
/* * 7 初始化RequestToViewNameTranslator,请求到视图名的转换器 * * 当未明确提供视图名称时,用于将传入的HttpServletRequest转换为逻辑视图名称的转换器。 */
initRequestToViewNameTranslator(context);
/* * 8 初始化ViewResolver,视图解析器 * * ViewResolver根据handler执行之后返回的ModelAndView中的String类型的逻辑视图名解析成物理视图名,即具体的资源地址 * 再生成对应的View视图对象。但是具体的事务解析以及数据填充工作由View视图自己完成(View. render方法)。 */
initViewResolvers(context);
/* * 9 初始化FlashMapManager,FlashMap管理器 * * 用于存储并检索FlashMap,这些FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。 * 也就是说FlashMap主要用在redirect中传递参数,而FlashMapManager则用于管理这些FlashMap。 */
initFlashMapManager(context);
}
从源码中我们可以看到,该方法会尝试初始化各种常见或者不常见的组件,包括MultipartResolver、LocaleResolver、ThemeResolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolver、RequestToViewNameTranslator、ViewResolver、FlashMapManager
。
下面我们看看常见组件的初始化源码!
4.1 initMultipartResolver
初始化MultipartResolver
,会尝试从此Servlet关联的ApplicationContext容器中获取beanName为multipartResolver
,类型为MultipartResolver
的bean。
找到的multipartResolver对象将绑定给此DispatcherServlet
对象的multipartResolver
属性,如果没找到则不会抛出异常,而是该属性赋值为null,即不提供multipart处理功能
。
//DispatcherServlet的的属性
/** * Bean工厂中此servlet使用的MultipartResolver对象的beanName */
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
/** * 此servlet使用的MultipartResolver。 */
@Nullable
private MultipartResolver multipartResolver;
/** * DispatcherServlet的方法 * <p> * 初始化此类使用的MultipartResolver。 * 如果在BeanFactory中没有给定名称为"multipartResolver"以及类型为MultipartResolver的bean,则不会提供multipart处理功能。 */
private void initMultipartResolver(ApplicationContext context) {
try {
//从容器中获取名称为"multipartResolver"以及类型为MultipartResolver的多部件处理器,并赋给multipartResolver
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
} else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
} catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
//如果没找到就赋值为null,即不提供multipart处理功能,而不是抛出异常
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
4.2 initHandlerMappings
初始化HandlerMapping
,HandlerMapping可以通过配置名为detectAllHandlerMappings
的<init-param>
初始化参数来控制是否尝试从此Servlet关联的ApplicationContext容器中查找所有
的HandlerMapping的bean或者仅仅在容器中查找名为“handlerMapping
”的单个
HandlerMapping的bean,该参数默认为true
。
如果以上两种方式都没有在容器中找到至少一个
HandlerMapping的bean实例,那么将尝试注册默认
的HandlerMapping来确保至少有一个HandlerMapping,spring-webmvc-5.2.8.RELEASE
的依赖中默认的HandlerMapping有三个,类型为org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping、org.springframework.web.servlet.function.support.RouterFunctionMapping
。
所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties
文件中。
//DispatcherServlet的属性
/** * 此servlet使用的HandlerMapping列表 */
@Nullable
private List<HandlerMapping> handlerMappings;
/** * 是否需要检测所有HandlerMappings还是只期望检测单个名为"handlerMapping"的bean? */
private boolean detectAllHandlerMappings = true;
/** * Bean工厂中的HandlerMapping对象的beanName,仅在关闭detectAllHandlerMappings=false时使用。 */
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
/** * DispatcherServlet的方法 * <p> * 初始化此类使用的HandlerMappings。 * 如果在BeanFactory中没有为此Servlet定义HandlerMapping的Bean,则默认为BeanNameUrlHandlerMapping。 */
private void initHandlerMappings(ApplicationContext context) {
//handlerMappings置为null
this.handlerMappings = null;
//是否需要检测所有的HandlerMapping,默认需要,可以通过init-param初始化参数指定
//如果需要
if (this.detectAllHandlerMappings) {
// 在ApplicationContext中找到所有HandlerMapping实例,包括Root ApplicationContext,不包括非单例的
//返回一个map key为beanName,value为对应的实例
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
//如果ApplicationContext中具有至少一个HandlerMapping的bean
if (!matchingBeans.isEmpty()) {
//获取map的所有value,即所有的HandlerMapping实例集合
this.handlerMappings = new ArrayList<>(matchingBeans.values());
//最后使用AnnotationAwareOrderComparator比较器对handlerMappings进行排序,这说明handlerMapping支持order排序
//该比较器支持Ordered、PriorityOrdered接口,以及@Order、@Priority注解的排序,比较优先级为PriorityOrdered>Ordered>@Ordered>@Priority,
//排序规则是order值越小排序越靠前,优先级越高,没有order值则默认排在尾部,优先级最低。
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
} else {
//如果不需要
try {
//那么仅仅获取名为"handlerMapping"类型为HandlerMapping的单个bean实例
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
//仅仅使用单个handlerMapping
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
//如果没有找到符合条件的bean,那么该异常被忽略,稍后我们将添加默认的HandlerMapping。
}
}
/* * 1 如果handlerMappings还是为null,那说明 * 在ApplicationContext中找不到任何HandlerMapping的bean或者找不到符合条件的HandlerMapping * 那么通过注册默认的HandlerMapping来确保至少有一个HandlerMapping * * 在Spring 5.2.8.RELEASE中,默认注册的HandlerMapping有三个,分别为 * org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping * org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping * org.springframework.web.servlet.function.support.RouterFunctionMapping * 最重要的就是RequestMappingHandlerMapping了 */
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
4.2.1 getDefaultStrategies获取默认组件
在初始化上面的那些组件的时候,如果在容器中没有指定组件bean,那么都会调用该方法获取默认的组件。该方法用于获取默认组件对象的列表,默认实现使用"DispatcherServlet.properties
"文件(与DispatcherServlet类位于同一包中)来确定使用的默认组件。
最开始获取的都是默认组件的全路径名字符串,随后会转换为Class
并通过ApplicationContext
内部的BeanFactory
调用createDefaultStrategy
方法来实例化指定具体类型的组件对象,该过程中将会执行Bean的完整初始化应用所有适用的BeanPostProcessor
,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调
,但是通过此方法创建的默认实例不会
被加入Spring容器中管理。
/** * DispatcherServlet的方法 * <p> * 获取默认组件对象的列表,默认实现使用"DispatcherServlet.properties"文件(与DispatcherServlet类位于同一包中)来确定使用的默认组件。 * 通过ApplicationContext内部的BeanFactory来实例化组件对象,将会执行Bean的完整初始化 * 应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调。 * * @param context 当前的WebApplicationContext * @param strategyInterface 组件接口类型 * @return 相应组件对象的列表 */
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
//组件接口的全路径名字符串
String key = strategyInterface.getName();
//获取对应的默认的组件类型字符串
String value = defaultStrategies.getProperty(key);
//如果有默认组件
if (value != null) {
//依据","拆分字符串,因为value中可能包含多个组件的全路径名,且使用","分隔。
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
//默认的组件实例集合
List<T> strategies = new ArrayList<>(classNames.length);
//遍历拆分后的组件全路径名
for (String className : classNames) {
try {
//转化为Class
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
//通过ApplicationContext内部的BeanFactory来实例化组件对象,将会执行Bean的完整初始化
//应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调。
Object strategy = createDefaultStrategy(context, clazz);
//加入到集合中
strategies.add((T) strategy);
} catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
} catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
//返回
return strategies;
} else {
//返回空集合
return new LinkedList<>();
}
}
源码中一个非常重要的属性就是defaultStrategies
,该属性在DispatcherServlet
这个类被加载的时候就在静态块
中被初始化了,其内部的数据实际上就是从相对于DispatcherServlet类的DispatcherServlet.properties
配置文件中加载的数据,就是一些默认
的组件。key
为组件接口全路径名,value
为默认组件实例的全路径名字符串,多个默认组件名使用“,
”分隔。
/** * 定义DispatcherServlet的默认组件名称的类路径资源的名称(相对于DispatcherServlet类)。 */
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
/** * DispatcherServlet.properties加载之后的map */
private static final Properties defaultStrategies;
/*DispatcherServlet的静态块,加载默认组件资源*/
static {
// 从属性文件加载默认组件实现。
// 当前这严格是内部的,并不意味着应由应用程序开发人员自定义。
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
//加载到defaultStrategies集合中
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
DispatcherServlet.properties配置文件在spring-webmvc的依赖中,和DispatcherServlet同一路径。
通过查看该配置文件的内容,即可知道,某个版本的Spring MVC将会默认使用哪些组件, spring-webmvc-5.2.8.RELEASE
版本中的配置文件内容如下:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.ser
vlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
4.3 initLocaleResolver
初始化LocaleResolver
,首先会尝试从此Servlet关联的ApplicationContext容器中查找名为“localeResolver”的单个LocaleResolver
的bean。
如果没有在容器中找到指定的LocaleResolver
的bean实例,那么将尝试注册默认
的LocaleResolver,spring-webmvc-5.2.8.RELEASE
的依赖中默认的LocaleResolver类型为org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
。
所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。
//DispatcherServlet的属性
/** * 此servlet使用的LocaleResolver。 */
@Nullable
private LocaleResolver localeResolver;
/** * Bean工厂中的LocaleResolver对象的beanName。 */
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
/** 1. DispatcherServlet的方法 2. <p> 3. 初始化此Servlet使用的LocaleResolver。 4. 如果在BeanFactory中没有为此给定名称的bean,则我们默认为AcceptHeaderLocaleResolver。 */
private void initLocaleResolver(ApplicationContext context) {
try {
//从此Servlet关联的ApplicationContext容器中查找名为“handlerMapping”的单个LocaleResolver的bean。
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.localeResolver);
} else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
}
} catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
//如果没有获取到,那么使用默认的localeResolver,即org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
4.4 其他组件的初始化
initHandlerAdapters、initHandlerExceptionResolvers、initViewResolvers这三个方法的源码原理和上面的initHandlerMappings方法非常相似,initThemeResolver、initRequestToViewNameTranslator、initFlashMapManager这三个方法的源码原理和上面的initLocaleResolver方法非常相似,在此不再赘述。所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。
spring-webmvc-5.2.8.RELEASE的依赖中:
默认的HandlerAdapter
有四个,类型为org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter、org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter、org.springframework.web.servlet.function.support.HandlerFunctionAdapter。默认的HandlerExceptionResolver
有三个,类型为org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver、org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver、org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver。默认的HandlerExceptionResolver
有一个,类型为org.springframework.web.servlet.view.InternalResourceViewResolver。默认的ThemeResolver
有一个,类型为org.springframework.web.servlet.theme.FixedThemeResolver。默认的RequestToViewNameTranslator
有一个,类型为org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator。默认的FlashMapManager
有一个,类型为org.springframework.web.servlet.support.SessionFlashMapManager。
5 总结
本次我们学习了DispatcherServlet与子容器的初始化,以及各种MVC组件的初始化,核心方法就是Servlet的init()
方法,该方法大概又可以分为三步,分别涉及到三个核心子方法:
- 将当前
ServletConfig
的init parameters
参数填充到当前DispatcherServlet
的实例的对应的属性中,方便后面的步骤中使用。可以在web.xml中
对应的<Servlet>
标签下通过设置<init-param>
标签来配置各种属性。主要是HttpServletBean#init()
方法。 - 初始化于此
Servlet
关联的MVC子容器
,该容器的父容器就是Root Application
(可能为null),随后会以属性名:org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName
,value
为此容器的方法存入ServletContext
的属性中。主要是FrameworkServlet#initServletBean()
方法。 - 初始化
Servlet
使用的MVC组件
。主要是DispatcherServlet#onRefresh()
方法。
相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring MVC 5.x 学习
Spring Framework 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!