通过web.xml配置Spring MVC
先看web.xml
相关配置
<!-- 省略非关键的配置 -->
<!-- [1] Spring配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
</context-param>
<!-- ====================================== -->
<!-- [2] Spring MVC配置 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value> // 默认
</init-param>
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
ServletContextListener初始化流程
在servlet容器启动的时候,会先初始化ServletContextListener
的contextInitialized
接口,而ContextLoaderListener
就实现了该接口。
/**
* 初始化Spring 根容器
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
初始化容器方法如下:
// 省略部分异常或日志代码
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
// 重复初始化,抛出错误(只能配置一个)
}
Log logger = LogFactory.getLog(ContextLoader.class);
long startTime = System.currentTimeMillis();
try {
// 创建容器
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
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);
}
}
// 设置servletContext属性,就是第一行检查的属性
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 配置类加载器
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
return this.context;
}
catch (RuntimeException | Error ex) {
// 抛出异常
}
}
重点在createWebApplicationContext
和configureAndRefreshWebApplicationContext
,前者负责创建容器,后者负责刷新容器。
createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 确定上下文的class
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
// 抛出异常
}
// 通过反射调用,实例化容器上下文
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
// 通过init-param指定(一般都不用配置)
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
// 省略
}
}
else {
// 从defaultStrategies中获取,defaultStrategies是在ContextLoaderListener创建的时候初始化的
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// 省略
}
}
}
// 初始化defaultStrategies
private static final Properties defaultStrategies;
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
static {
// 静态代码块初始化,查找ContextLoader.properties文件并加载到defaultStrategies中
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
//
}
}
// 而ContextLoader.properties中只有一个配置:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
也就是说通过web.xml
配置的Spring MVC默认初始化了一个XmlWebApplicationContext
的上下文环境
configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 设置容器的id,如果没有配置就生成一个
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 设置容器的ServletContext
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
// 设置配置文件的路径,就是我们通过xml配置进来的
wac.setConfigLocation(configLocationParam);
}
// 配置上下文环境
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 执行用户配置的容器初始化
customizeContext(sc, wac);
// 刷新容器,进行BeanDefinition的加载等操作
wac.refresh();
}
重点在于refresh
方法,具体可以查看Spring 容器初始化流程,这里不展开分析。
DispatcherServlet初始化流程
省略部分继承关系,只展示了Servlet相关的

通过上面的类图,我们知道DispatcherServlet
实际上就是Servlet,Servlet容器在初始化Servlet时会调用其init
方法,而init方法在HttpServletBean
类中重写了。
@Override
public final void init() throws ServletException {
// 从servlet的init参数中组装PropertyValues
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
// 省略
}
}
// 由子类实现的初始化
initServletBean();
}
我们关注initServletBean
方法就行,该方法在FrameworkServlet
重写了:
@Override
protected final void initServletBean() throws ServletException {
// 记录启动时间
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
// 空实现
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
// 省略
}
}
protected WebApplicationContext initWebApplicationContext() {
// 获得根 WebApplicationContext 对象
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用,我们只关注这种就行
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
// 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
// 如果是默认情况,应该在ServletContextListener初始化的时候就refresh了
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
wac = findWebApplicationContext();
}
if (wac == null) {
// 第三种,创建一个 WebApplicationContext 对象
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 如果未触发刷新事件,则主动触发刷新事件
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// 将 context 设置到 ServletContext 中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
在initWebApplicationContext
方法中,我们只需关注第一种情况,就是在Servlet容器中获取Spring上下文,因为在前面有提到,ServletContextListener
中默认初始化了XmlWebApplicationContext
,这里可以直接获取。
而initFrameworkServlet
方法目前Spring没有具体实现。
在WebApplicationContext
初始化好了以后,调用onRefresh
触发刷新时间,而该事件的实现在DispatcherServlet
,这里也是DispatcherServlet
所有组件初始化的入口!
// DispatcherServlet.java
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 初始化各种组件的策略
protected void initStrategies(ApplicationContext context) {
// 初始化 MultipartResolver
initMultipartResolver(context);
// 初始化 LocaleResolver
initLocaleResolver(context);
// 初始化 ThemeResolver
initThemeResolver(context);
// 初始化 HandlerMappings
initHandlerMappings(context);
// 初始化 HandlerAdapters
initHandlerAdapters(context);
// 初始化 HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
// 初始化 RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 初始化 ViewResolvers
initViewResolvers(context);
// 初始化 FlashMapManager
initFlashMapManager(context);
}
initMultipartResolver
初始化 MultipartResolver
private void initMultipartResolver(ApplicationContext context) {
try {
// 尝试从容器中获取multipartResolver
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
// 没有multipartResolver组件
this.multipartResolver = null;
}
}
initLocaleResolver
初始化 LocaleResolver
private void initLocaleResolver(ApplicationContext context) {
try {
// 尝试从容器中获取
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
// 获取不到则使用默认策略
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
}
}
其中getDefaultStrategy
方法是从DispatcherServlet.properties
文件中配置的,在DispatcherServlet
初始化的时候通过静态代码块初始到defaultStrategies
变量中。
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.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
关于getDefaultStrategy
是如何获取对应策略的这里不展开分析,大概就是通过传进来的class获取到全限定名,然后到defaultStrategies
变量中查找。
initThemeResolver
初始化 ThemeResolver
private void initThemeResolver(ApplicationContext context) {
try {
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
}
}
套路跟initMultipartResolver
一样
initHandlerMappings
初始化 HandlerMappings
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 在ApplicationContext中找到所有HandlerMappings,包括父容器的上下文。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// 排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 从容器中获取
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
// 获取默认策略
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
一般我们也没有指定HandlerMapping
,所以也是从默认策略中初始化的。
initHandlerAdapters
初始化 HandlerAdapters
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// 在ApplicationContext中找到所有HandlerMappings,包括父容器的上下文。
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// 排序
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
// 从容器中获取
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerAdapters == null) {
// 获取默认策略
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
}
}
跟initHandlerMappings
一样的套路。
initHandlerExceptionResolvers
初始化 HandlerExceptionResolvers
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
}
}
一样的~
initRequestToViewNameTranslator
初始化 RequestToViewNameTranslator
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
}
catch (NoSuchBeanDefinitionException ex) {
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
}
}
还是同样的套路~
initViewResolvers
初始化 ViewResolvers
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
}
else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
}
}
过~
initFlashMapManager
初始化 FlashMapManager
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
}
catch (NoSuchBeanDefinitionException ex) {
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
}
}
还是一样~
至此,DispatcherServlet
就算初始化完了
通过javaConfig配置Spring MVC
通过xml配置Spring MVC已经过时了,我们来看看如何通过javaConfig来初始化。
Servlet3.0开始,在Servlet容器在启动的时候会扫描所有jar包下的ServletContainerInitializer
实现类调用onStartup
方法来初始化容器,而Spring基于javaConfig的配置正是通过该机制来实现的。
在spring-web
的jar中,配置了如下文件:META-INF/services/javax.servlet.ServletContainerInitializer
,通过java的SPI机制,会为ServletContainerInitializer
生成一个实现类,而这个类的全限定名就是配置在这个文件中
// javax.servlet.ServletContainerInitializer
org.springframework.web.SpringServletContainerInitializer
可见
SpringServletContainerInitializer
就是Spring容器注册的关键
SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// 检查给定的webAppInitializerClasses是否合规
// 虽然指定了HandlesTypes,但是某些Servlet容器可能没有实现HandlesTypes的逻辑,把一些不相干的Class也传进来
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
// 抛出异常
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
// 调用onStartup方法
initializer.onStartup(servletContext);
}
}
}
Spring提供的WebApplicationInitializer
体系如下:

体系中实现了onStartup
方法的类如下:

我们这里只关注前两个,逐个分析:
AbstractContextLoaderInitializer
我们先来分析AbstractContextLoaderInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
// 创建Spring容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
// 设置监听器
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
// 省略日志
}
}
// 抽象方法
protected abstract WebApplicationContext createRootApplicationContext();
createRootApplicationContext
在AbstractAnnotationConfigDispatcherServletInitializer
提供了默认实现:
// AbstractAnnotationConfigDispatcherServletInitializer.java
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 创建一个注解配置的Web应用容器
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
// 没有默认实现,需要我们手动注册
protected abstract Class<?>[] getRootConfigClasses();
总的来说,AbstractContextLoaderInitializer
的onStartup
主要是实现了容器的创建。
AbstractDispatcherServletInitializer
注意!这里就是注册
DispatcherServlet
的关键地方
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
先调用了父类的onStartup
方法,就是我们上面分析的AbstractContextLoaderInitializer#onStartup
。然后调用registerDispatcherServlet
,一看名字就知道是注册DispatcherServlet
了~
protected void registerDispatcherServlet(ServletContext servletContext) {
// 获取servletName
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 创建 WebApplicationContext 对象,这里重新创建容器,所以可支持配置多个DispatcherServlet,而且每个DispatcherServlet都有自己的容器环境
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 创建 FrameworkServlet 对象,即初始化DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 注册到servlet容器中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
// 抛出异常
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
// 注册过滤器
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
// 空实现,提供子类配置
customizeRegistration(registration);
}
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
// 直接new一个DispatcherServlet
return new DispatcherServlet(servletAppContext);
}
createServletApplicationContext
在前面已经解析过,这里重新创建容器,所以可支持配置多个DispatcherServlet
,而且每个DispatcherServlet
都有自己的容器环境。
当DispatcherServlet
注册到Servlet容器后,后续的流程跟前面通过xml配置的流程一致,忘记了的话返回到上面看看就行。
49 条评论
bonanza178 · 2023年6月19日 下午4:16
… [Trackback]
[…] Find More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
Ventilatii generale · 2023年6月19日 下午4:54
… [Trackback]
[…] Information to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
cvv sites · 2023年6月26日 下午2:08
… [Trackback]
[…] Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
colt 1911 for sale · 2023年7月3日 下午11:59
… [Trackback]
[…] Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
magic mushrooms for sale · 2023年7月13日 上午8:08
… [Trackback]
[…] Read More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
magic mushrooms for sale online australia · 2023年7月19日 下午9:32
… [Trackback]
[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
นำเข้าสินค้าจากจีน · 2023年7月20日 上午8:13
… [Trackback]
[…] Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ปั้มฟอล · 2023年7月21日 上午8:43
… [Trackback]
[…] Read More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
bonanza178 · 2023年7月21日 下午6:07
… [Trackback]
[…] Here you can find 58660 additional Information to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เย้เย้ · 2023年7月24日 上午8:06
… [Trackback]
[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เรือไปเกาะหลีเป๊ะ · 2023年7月25日 上午6:26
… [Trackback]
[…] Here you can find 89090 additional Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
pappy van winkle 23 · 2023年7月25日 下午3:58
… [Trackback]
[…] Find More Information here to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ติดเน็ตบ้าน ais พร้อม กล่องทีวี · 2023年8月3日 上午6:51
… [Trackback]
[…] Find More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
miami boat rentals with captain · 2023年8月3日 上午10:22
… [Trackback]
[…] Here you can find 66784 more Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
best shipping car company · 2023年8月3日 上午10:53
… [Trackback]
[…] Find More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
88888 คาสิโน · 2023年8月6日 上午9:35
… [Trackback]
[…] There you will find 21840 more Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
disposable carts · 2023年8月7日 下午9:35
… [Trackback]
[…] Here you can find 2708 more Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ตาสองชั้นหมอไก่ · 2023年8月8日 上午7:45
… [Trackback]
[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
buy runtz weed · 2023年8月9日 下午3:10
… [Trackback]
[…] Find More on to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
Mango · 2023年8月10日 上午5:33
… [Trackback]
[…] Read More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
qiuqiu99 agen · 2023年8月11日 下午9:05
… [Trackback]
[…] Here you can find 92467 additional Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เว็บ บอล ที่ ดี · 2023年8月12日 上午7:26
… [Trackback]
[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เรือไปหลีเป๊ะ · 2023年8月12日 上午8:07
… [Trackback]
[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
alpha88 สล็อต · 2023年8月12日 上午10:15
… [Trackback]
[…] There you will find 5357 more Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
togel idn · 2023年8月12日 下午5:49
… [Trackback]
[…] Information on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
bonanza178 · 2023年8月18日 下午4:05
… [Trackback]
[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
Christensen arms · 2023年8月20日 上午6:47
… [Trackback]
[…] Information to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เว็บพนันออนไลน์ · 2023年8月22日 上午7:23
… [Trackback]
[…] Find More Info here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ทินเนอร์คุณภาพสูง · 2023年8月26日 上午7:05
… [Trackback]
[…] Find More on to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
judi slot terpercaya · 2023年8月29日 下午4:58
… [Trackback]
[…] Find More on to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
togel online · 2023年8月29日 下午7:20
… [Trackback]
[…] Read More Information here to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
20176 zip code · 2023年8月30日 上午2:34
… [Trackback]
[…] There you can find 54510 additional Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เครื่องสำอางค์เกาหลี · 2023年8月30日 上午6:56
… [Trackback]
[…] There you will find 41849 additional Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ป้ายโฆษณา · 2023年8月30日 上午8:17
… [Trackback]
[…] Read More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เว็บพนันบอล ดีที่สุด pantip · 2023年8月31日 上午7:01
… [Trackback]
[…] Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
แทงบอลออนไลน์ · 2023年9月2日 下午2:47
… [Trackback]
[…] Find More Information here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
house for sale pattaya · 2023年9月3日 上午7:44
… [Trackback]
[…] Read More Info here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
พรมปูพื้นรถยนต์ 6d · 2023年9月5日 上午6:52
… [Trackback]
[…] Find More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เว็บคาสิโนออนไลน์ · 2023年9月5日 上午7:48
… [Trackback]
[…] Read More on on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
more · 2023年9月6日 下午6:52
… [Trackback]
[…] Read More Info here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
sidegra · 2023年9月7日 上午6:59
… [Trackback]
[…] Find More Information here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
luxury pool villas in phuket · 2023年9月7日 上午8:01
… [Trackback]
[…] There you will find 69904 additional Information on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เทคนิค พนันออนไลน์ · 2023年9月8日 上午6:33
… [Trackback]
[…] Find More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
สถาปนิกรับออกแบบบ้าน · 2023年9月8日 上午7:05
… [Trackback]
[…] Info on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ออกแบบโลโก้ · 2023年9月8日 上午7:06
… [Trackback]
[…] Here you will find 79575 more Info to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
ยูเรเนียน · 2023年9月8日 上午7:25
… [Trackback]
[…] Here you can find 22868 more Information on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
result sgp · 2023年9月10日 上午2:11
… [Trackback]
[…] Read More on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
Skrot Lilla Edet · 2023年9月10日 上午2:53
… [Trackback]
[…] Read More Information here on that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
เพิ่มยอดไลค์ · 2023年9月10日 上午7:11
… [Trackback]
[…] Find More to that Topic: hugr.cn/2019/12/18/spring-mvc-初始化流程-简单源码分析/ […]
评论已关闭。