Spring MVC 初始化源码—DispatcherServlet与子容器的初始化以及MVC组件的初始化一万字
Posted L-Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC 初始化源码—DispatcherServlet与子容器的初始化以及MVC组件的初始化一万字相关的知识,希望对你有一定的参考价值。
基于最新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注解的源码解析
文章目录
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以上是关于Spring MVC 初始化源码—DispatcherServlet与子容器的初始化以及MVC组件的初始化一万字的主要内容,如果未能解决你的问题,请参考以下文章
Spring MVC 初始化源码—@RequestMapping注解的源码解析
Spring MVC源码——Root WebApplicationContext