问题:SpringMVC 在启动过程中主要做了什么事情?
SpringMVC在启动过程中是什么时候解析web.xml文件的,又是什么时候初始化9大内置对象的?
<?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:applicationContext.xml</param-value>
</context-param>
<!--Spring监听器-->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener </listener-class>
</listener>
<!--SpringMVC前端控制器-->
<servlet>
<servlet-name> SpringMVC </servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
<!-- 配置springMVC需要加载的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
补充:相关概念
在Spring MVC中,有两个容器:父容器(Root WebApplicationContext)和子容器(Servlet WebApplicationContext)。
(1)父容器(Root WebApplicationContext)
主要负责加载应用程序中的共享bean和配置。它通常包含以下内容:
- 数据源和事务管理器:用于处理数据库操作和事务管理。
- 持久化层(DAO)和业务层(Service)的bean:用于处理数据访问和业务逻辑。
- 安全配置:用于配置安全相关的bean,如认证和授权配置。
- 缓存配置:用于配置缓存相关的bean,如缓存管理器和缓存注解处理器。
- 其他共享bean:例如,公共工具类、全局异常处理器等。
(2)子容器(Servlet WebApplicationContext)
主要负责加载与Web应用程序相关的bean和配置。它通常包含以下内容:
- 控制器(Controller)和视图解析器(ViewResolver):用于处理请求和生成响应。
- Web相关的bean:例如,处理文件上传的MultipartResolver、处理静态资源的ResourceHandler等。
- Web安全配置:用于配置Web安全相关的bean,如Spring Security配置。
- 其他与Web应用程序相关的bean:例如,国际化资源处理器、消息转换器等。
Spring本身的ApplicationContext属于父容器(Root WebApplicationContext),它通常包含整个应用程序范围内的bean和配置。这包括父容器中的共享bean,以及子容器中的bean。在Spring MVC中,通常使用ContextLoaderListener来加载父容器的ApplicationContext。
总结起来,父容器(Root WebApplicationContext)包含应用程序范围内的共享bean和配置,子容器(Servlet WebApplicationContext)包含与Web应用程序相关的bean和配置,而Spring本身的ApplicationContext属于父容器,包含整个应用程序的bean和配置。
Spring 父子容器是指 Spring 容器的层次结构,其中一个容器作为另一个容器的父容器。父容器可以提供资源和 bean 给子容器,子容器可以覆盖或扩展父容器的 bean 定义。
这种父子关系在 Spring Web 应用中非常常见。通常情况下,在 Web 应用中会有两个 Spring 容器:Root WebApplicationContext 和 Servlet WebApplicationContext。
(3)SpringMVC组件的配置方式
1、在Spring和SpringMVC整合得时候通常需要进行Spring的MVC得配置,这个配置可以是XML得方式也可以是无配置化即注解得方式。即@EnableWebMvc和<mvc:annotation-driven />
(4)SPI机制
1、从 servlet 3.0 开始,Tomcat 启动时会自动加载实现了 ServletContainerInitializer 接口的类(需要在 META-INF/services 目录下新建配置文件),也称为 SPI 机制;
package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
实现类SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* 该 onStartup() 方法会被 tomcat 服务启动调用
* 这个方法主要是为 Web 容器
* 1、添加 WebServlet 请求
* 2、添加 WebFilter 过滤器
* 3、添加 WebListener 监听器
* 参数1:webAppInitializerClasses 表示加载被注解 @HandlesTypes(WebApplicationInitializer.class) 修饰的接口的所有子类实现,
* 主要有一下三类重要的子类:
* 1.AbstractContextLoaderInitializer ---> 对应 ContextLoaderListener
* 2.AbstractDispatcherServletInitializer --> 对应 DispatcherServlet
* 2.1.AbstractAnnotationConfigDispatcherServletInitializer
* 3.AbstractReactiveWebInitializer --> 对应 React
*
* 参数2:servletContext servlet 容器上下文
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
// initializers 里面会包含我们自己定义的 WebApplicationInitializer 子类实现
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
WebApplicationInitializer 接口
@HandlesTypes 注解还是 Tomcat SPI 规范中的,会被 Tomcat 解析,也就是说会解析被注解 @HandlesTypes 修饰的 WebApplicationInitializer 接口的所有子类,注意此时的 WebApplicationInitializer 接口是由 Spring 提供的,这个接口就是 Spring 整合 SpringMVC 的核心入口, Spring 有三个较重要的实现子类:
1.AbstractContextLoaderInitializer —> 对应 ContextLoaderListener
2.AbstractDispatcherServletInitializer --> 对应 DispatcherServlet
2.1.AbstractAnnotationConfigDispatcherServletInitializer --> 对应注解配置版的 DispatcherServlet
3.AbstractReactiveWebInitializer --> 对应 React
1、Tomcat 会扫描所有实现了 WebApplicationInitializer 接口的子类,然后放到 SpringServletContainerInitializer 类中的 onStartup() 方法中的第一个参数 Set 集合中;
2、Tomcat 在启动时会调用 SpringServletContainerInitializer 的 onStartup() 方法(因为 SpringServletContainerInitializer 实现了 Tomcat ServletContainerInitializer 接口),然后再遍历调用 WebApplicationInitializer 接口的所有子类(上述的三类重要子类在这里就被回调了)的 onStartup() 方法;
总结:实现了 WebApplicationInitializer 接口就会被 Tomcat 再调用 SpringServletContainerInitializer 类的 onStartup() 方法时遍历调用到每个 WebApplicationInitializer 子类的 onStartup() 方法。
Spring 在整合 SpringMVC 时利用了 Tomcat SPI 可扩展接口,扩展了 ServletContainerInitializer 接口,在 WebApplicationInitializer 类中进行增强处理。
0、启动过程中两个关键类
ContextLoaderListener和DispatcherServlet
Tomcat启动时候会先创建servlet容器,然后解析web.xml。
1、首先加载conf/web.xml文件,然后加载web应用程序中的WEB-INF/web.xml文件。
2、按照以下顺序读取和处理web.xml中的元素:context-param -> listener -> filter -> servlet。
context-param元素用于向ServletContext提供键值对,即应用程序上下文信息,可以写在任意位置。
servlet元素用于定义servlet的名字和类,servlet-mapping元素用于指定访问servlet的URL,servlet-mapping必须出现在servlet之后,servlet的初始化顺序按照load-on-startup元素指定的值,如果值为正数或零,则按照从小到大的顺序初始化,如果值为负数或未定义,则在第一次请求时初始化。
先解析context-param,然后创建ServletContext对象,并将context-param转换为键值对交给ServletContext,然后再解析listener-class,并创建监听器实例。如果监听器类实现了ServletContextListener接口,那么它的contextInitialized(ServletContextEvent sce)方法和contextDestroyed(ServletContextEvent sce)方法会在ServletContext对象创建和销毁时被调用,这两个方法的参数sce可以通过getServletContext()方法获取ServletContext对象。
1、ContextLoaderListener
ContextLoaderListener实现了Servlet规范中的javax.servlet.ServletContextListener,WEB容器在启动过程中即ServletContext对象创建时会回调ServletContextListener.contextInitialized(), 同理在销毁的时候会回调contextDestroyed(ServletContextEvent sce)
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
调用contextInitialized()
创建WebApplicationContext,设置必要的参数,如配置文件位置contextConfigLocation。
调用refresh()。在这个过程中,XmlWebApplicationContext 会解析 XML 配置文件,加载 bean 定义,并创建并初始化所有的 bean。将这个容器当作父容器存到servletContext里边。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
/**
* 首先通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个String类型的静态变量获取一个父容器
* 父容器作为全局变量存储在application对象中,如果存在则有且只能有一个
* 如果在初始化父容器时发现已经存在则直接抛出异常
* 这个值在下面的代码中会放入application中
*/
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// context代表父容器,已经有值了
// xml会在这里创建
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); // null
cwac.setParent(parent);
}
// 创建spring容器,调用refresh()方法完成父容器的初始化
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将父容器放入到了servletContext中
//在servlet域中设置根容器(在子容器就可以直接拿到了)
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);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
2、DispatcherServlet
当创建完成父容器之后,就开始创建子容器。解析servlet标签,即DispatcherServlet。
1、在web.xml文件中配置dispatcherServlet的servlet-name,servlet-class,init-param和load-on-startup等信息。
2、会根据load-on-startup的值,按照从小到大的顺序初始化servlet实例,并调用它们的init()方法
先看一下DispatcherServlet的继承关系:
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext); // 创建ContextLoaderListener
registerDispatcherServlet(servletContext); // 创建DispatcherServlet
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = createServletApplicationContext(); // 子容器
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 创建DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); // null
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings()); // 处理哪些映射
registration.setAsyncSupported(isAsyncSupported()); // 默认为true,支持异步
Filter[] filters = getServletFilters(); // 过滤器
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
// 添加filter到servlet容器中
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
1. HttpServletBean#init()
WEB容器启动后调用Servlet的init()方法进行初始化,此方法的实现实在父类HttpServletBean中:
org.springframework.web.servlet.HttpServletBean#init
@Override
public final void init() throws ServletException {
// 解析 init-param 并封装只 pvs 中(xml)
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
// 将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
// 修改servlet状态,并将pvs里边的值赋值给servlet
bw.setPropertyValues(pvs, true);
}
// 初始化Servlet,创建Spring容器
initServletBean();
}
2. FrameworkServlet#initServletBean()
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 {
// 对子容器进行初始化
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet(); // NOP
}
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. FrameworkServlet#initWebApplicationContext()
protected WebApplicationContext initWebApplicationContext() {
// 从ServletContext中获取父容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext; // 子容器
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext); // 父子间建立关系
}
// 调用refresh()方法对子容器进行初始化
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) { // 不会进入
wac = findWebApplicationContext();
}
if (wac == null) { // 不会进入
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) { // refreshEventReceived=true不会进入
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
// 将子容器也放入到ServletContext中
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
4. FrameworkServlet#configureAndRefreshWebApplicationContext()
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 添加了一个监听子容器刷新的事件
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac); // NOP
applyInitializers(wac);
// invoke refresh method
wac.refresh();
}
在子容器初始化之前添加了一个监听容器刷新的事件ContextRefreshListener,当容器刷新完成后将会调用ContextRefreshListener.onApplicationEvent()方法。发布容器刷新事件ContextRefreshedEvent,最终会再刷新事件处理器中调用FrameworkServlet.onApplicationEvent()。
5. FrameworkServlet.onApplicationEvent()
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
6. FrameworkServlet#onApplicationEvent()
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
7. DispatcherServlet#onRefresh()
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// 初始化用于处理文件上传的MultipartResolver,即:从IOC中获取名称为“multipartResolver”的bean
initMultipartResolver(context);
// 初始化用于国际化的LocaleResolver(从IOC中获取名称为“localeResolver”的bean)
initLocaleResolver(context);
// 初始化用于主题风格的ThemeResolver(从IOC中获取名称为“themeResolver”的bean)
initThemeResolver(context);
// 初始化用于处理Request请求及流转的HandlerMapping,根据变量”detectAllHandlerMappings“,有如下3种获取方式:
// 方式1:获取IOC中所有类型为HandlerMapping的bean集合
// 方式2:获取名称为“handlerMapping”的bean
// 方式3:获取DispatcherServlet.properties文件中配置的HandlerMapping的bean列表
initHandlerMappings(context);
// 初始化用于进行请求处理的HandlerAdapter,根据变量”detectAllHandlerAdapters“,有如下3种获取方式:
// 方式1:获取IOC中所有类型为HandlerAdapter的bean集合
// 方式2:获取名称为“handlerAdapter”的bean
// 方式3:获取DispatcherServlet.properties文件中配置的HandlerAdapter的bean列表
initHandlerAdapters(context);
// 初始化用于对异常的类型进行处理并生成ModelAndView的HandlerExceptionResolver,根据变量”detectAllHandlerExceptionResolvers“,有如下3种获取方式:
// 方式1:获取IOC中所有类型为HandlerExceptionResolver的bean集合
// 方式2:获取名称为“handlerExceptionResolver”的bean
// 方式3:获取DispatcherServlet.properties文件中配置的HandlerExceptionResolver的bean列表
initHandlerExceptionResolvers(context);
// 初始化用于获取“逻辑视图名称”的RequestToViewNameTranslator(从IOC中获取名称为“viewNameTranslator”的bean)
initRequestToViewNameTranslator(context);
// 初始化用于创建View对象的ViewResolver,根据变量”viewResolver“,有如下3种获取方式:
// 方式1:获取IOC中所有类型为ViewResolver的bean集合
// 方式2:获取名称为“viewResolver”的bean
// 方式3:获取DispatcherServlet.properties文件中配置的ViewResolver的bean列表
initViewResolvers(context);
// 初始化用于管理FlashMap的FlashMapManager(从IOC中获取名称为“flashMapManager”的bean)
initFlashMapManager(context);
}
8、总结
如果在web.xml中配置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在启动的时候会先加载父容器,并将其放到ServletContext中;
然后会加载DispatcherServlet,因为DispatcherServlet实质是一个Servlet,所以会先执行它的init方法。这个init方法在HttpServletBean这个类中实现,其主要工作是做一些初始化工作,将我们在web.xml中配置的参数书设置到Servlet中,然后再触发FrameworkServlet的initServletBean()方法;
FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其放入ServletContext中;
FrameworkServlet在调用initServletBean()的过程中同时会触发DispatcherServlet的onRefresh()方法,这个方法会初始化Spring MVC的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。
3、DispatcherServlet.properties
DispatcherServlet中默认的组件
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
4、SpringMVC中的9大内置组件初始化
4.1 初始化文件解析器initMultipartResolver()
初始化multipart解析器的, MultipartResolver是一个接口,在Web开发中,multipart解析器主要用于处理HTTP请求中的multipart/form-data类型的数据,这种数据类型通常用于文件上传。
SpringMVC提供了两种multipart解析器:
1、CommonsMultipartResolver:基于Apache Commons FileUpload库实现的multipart解析器。
2、StandardServletMultipartResolver:基于Servlet 3.0规范实现的multipart解析器。
在Spring MVC中,如果你定义了一个名为"multipartResolver"的bean,那么Spring MVC就会使用你定义的这个bean作为multipart解析器。否则,Spring MVC就不会处理multipart/form-data类型的请求。
LOCALE_RESOLVER_BEAN_NAME = “localeResolver”;
1、一种是我们自定义上传文件解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置最大上传文件为5MB -->
<property name="maxUploadSize" value="5242880"/>
</bean>
或者--------------
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(5242880);
return multipartResolver;
}
2、默认文件上传解析器
如果我们没有自定义上传文件的bean,那么就会利用spi去配置文件里边取值,然后加载到子容器中。
// 通过PropertiesLoaderUtils工具类加载DispatcherServlet.properties
//DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
//spi
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
4.2 初始化国际化应用initLocaleResolver()
初始化 DispatcherServlet 中的 LocaleResolver。在Spring MVC中,LocaleResolver是一个接口,它定义了如何解析客户端的地区信息。地区信息通常被用于国际化应用,可以根据不同的地区显示不同的信息。
该方法的主要逻辑如下:
首先,尝试从Spring上下文中获取名为localeResolver的bean,如果获取到了,就使用该bean作为地区解析器。
如果没有获取到,那么就会使用getDefaultStrategy方法来获取一个默认的地区解析器。具体的实现类是在DispatcherServlet.properties中配置的和上边的一样也是利用SPI技术。
在Spring MVC中,有几个预定义的LocaleResolver的实现:
- AcceptHeaderLocaleResolver: 根据HTTP的Accept-Language头来解析地区信息。
- FixedLocaleResolver: 提供固定的地区信息,无论请求是什么。
- CookieLocaleResolver: 将地区信息保存在浏览器的Cookie中。
- SessionLocaleResolver: 将地区信息保存在HTTP Session中。
- 如果我们没有自己配置LocaleResolver的Bean,Spring
MVC将默认使用AcceptHeaderLocaleResolver。
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);
}
}
4.3 初始化主题initThemeResolver()
初始化 ThemeResolver 的方法。ThemeResolver 是一个接口,定义了如何解析应用程序的主题的规则。主题是Spring MVC中用于改变视图层外观的一种机制,例如颜色、CSS样式等。在Spring MVC中,可以为每个用户设置不同的主题,或者为所有用户设置一个统一的主题。
具体查找和上边一样,查找的是themeResolverbean,默认实现的是FixedThemeResolver,保存到themeResolver 。
4.4 初始化HandlerMapping()
初始化处理器映射(Handler Mapping),这里也是和上边一样如果我们自己定义HandlerMapping,就用我们自己定义的,我们可以定义多个,如果没有自定义用默认的,默认有三个实现。
4.4.1 自定义实现HandlerMapping
实现HandlerMapping接口或者继承AbstractHandlerMapping。创建一个CustomHandlerMapping类,它继承自AbstractHandlerMapping。
然后,在Spring MVC配置中注册这个自定义的HandlerMapping。当一个请求的URL路径为"/custom"时,CustomHandlerMapping就会返回CustomHandler作为处理器。
自定义的HandlerMapping与Spring MVC默认的HandlerMapping(比如RequestMappingHandlerMapping)是可以共存的(需要手动注入了,因为有默认的了,就不加载默认的了)。当一个请求到来时,Spring MVC会按照HandlerMapping的顺序来查找处理器。你可以通过实现Ordered接口或者使用@Order注解来调整HandlerMapping的顺序。
public class CustomHandlerMapping extends AbstractHandlerMapping {
//保存映射关系
Map<String, Object> pathHandlers = new HashMap<>();
public CustomHandlerMapping() {
// 添加一些自定义的映射
// 此处仅作示例,一般我们不会在构造函数中添加映射
// 更常见的是在配置类中或者通过其他方式动态添加映射
this.pathHandlers.put("/custom", new CustomHandler());
}
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 根据请求的URL路径查找对应的处理器
String path = request.getServletPath();
return this.pathHandlers.get(path);
}
}
class CustomHandler {
// 这里可以添加自定义的处理器方法
}
4.4.2 默认实现BeanNameUrlHandlerMapping
BeanNameUrlHandlerMapping:这个处理器映射使用 Spring bean 的名称作为 URL
来映射请求。例如,一个名为 “/test” 的 bean 将会处理来自 “/test” URL
的请求。这种方式在实际开发中用的比较少,因为它不够灵活且易于混淆。
BeanNameUrlHandlerMapping的寻找流程:
1、找出Spring容器中所有的beanName
2、判断beanName是不是以“/”开头
3、如果是,则把它当作一个Handler,并把beanName作为key,bean对象作为value存入handlerMap中,handlerMap就是一个Map
4.4.3 默认实现SimpleUrlHandlerMapping
SimpleUrlHandlerMapping:这个处理器映射允许我们显式地指定 URL与处理器之间的映射关系。然后将handlerMapping都保存到handlerMappings集合中。
4.4.4 默认实现 RequestMappingHandlerMapping
RequestMappingHandlerMapping:这个是最常用的处理器映射。它会扫描 Spring容器中的所有控制器(Controller),找出带有 @RequestMapping 注解的方法,然后根据注解的参数(例如,HTTP方法、URL 等)来建立请求与方法之间的映射。在处理 HTTP 请求时,它会找到与请求参数匹配的那个方法来处理请求。
RequestMappingHandlerMapping查找handler流程
默认得处理器映射(Handler Mapping)是spring创建的,是经过了bean得生命周期得,也就是通过getBean() -> createBean()那一套流程得,RequestMappingHandlerMapping类图:
RequestMappingHandlerMapping实现了InitializingBean,在初始化时候会执行afterPropertiesSet方法然后调用initHandlerMethods方法,这个方法会去解析所有的handler。
在 Spring MVC 中,RequestMappingHandlerMapping 是负责处理标有 @RequestMapping 注解的 Controller 的。它的工作流程如下:
1、当 Spring 容器启动的时候,RequestMappingHandlerMapping 会进行初始化,在初始化的过程中,afterPropertiesSet 方法会被调用。这个方法会进一步调用 initHandlerMethods 方法。
2、在 initHandlerMethods 方法中,会获取Spring 容器中所有的Bean。对于获取到的每一个 Bean,RequestMappingHandlerMapping 都会检查它是否标有 @Controller 或者 @RequestMapping 注解。只有标有这些注解的 Bean 才会被认为是一个有效的 Controller。
3、如果一个 Bean 被认为是一个有效的 Controller,那么 RequestMappingHandlerMapping 会进一步检查这个 Controller 中的所有方法。对于每一个方法,如果它上面有 @RequestMapping 注解,那么 RequestMappingHandlerMapping 会根据这个注解以及它所在的 Controller 的信息,创建一个 RequestMappingInfo 对象。
4、RequestMappingInfo 对象包含了请求的 URL、请求的方法、请求的参数等信息。这个对象会被用来与进来的 HTTP 请求进行匹配,以决定哪一个方法应该被用来处理这个请求。
5、最后将保存k-v,将path-List保存到pathLookup map中,为什么是list,因为相同的路径有get、post区分。将 RequestMappingInfo-headlMethod保存到registry map中。
创建handlerMethod
当解析到带有@RequestMapping注解的方法时,会创建一个HandlerMethod对象,该对象包含了方法的相关信息,如所属的类、方法名、参数列表等
构建RequestMappingInfo:
代表了一个请求路径和请求方法的映射关系
4.5 初始化handler适配器initHandlerAdapters()
处理器适配器(HandlerAdapter)负责调用处理器(Handler)的处理方法,最后的方法都封装成了handler, 不同的handler有不同的handlerAdapter来负责调用。
首先从应用程序上下文中获取所有类型为HandlerAdapter的bean。如果找不到任何HandlerAdapter的bean,那么它会使用一组默认的处理器适配器。这组默认的处理器适配器包括RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter、HandlerFunctionAdapter,并将其保存到handlerAdapters集合中。
适配逻辑:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
4.5.1 自定义HandlerAdapter
当我们自定义了handlerMapping就需要自定义HandlerAdapter来对他的handler进行匹配,当然,也可以利用默认的。
4.5.2 HttpRequestHandlerAdapter
HttpRequestHandlerAdapter:这个类是用于处理实现了HttpRequestHandler接口的类,这个接口只有一个方法handleRequest,用于直接处理请求和响应。它会将HttpServletRequest和HttpServletResponse对象作为参数传递给handleRequest方法,并将返回值封装成一个ModelAndView对象。
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
4.5.3 SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter:这个类是用于处理实现了Controller接口的类,这个接口也只有一个方法handleRequest,用于返回一个ModelAndView对象。它会将HttpServletRequest和HttpServletResponse对象作为参数传递给handleRequest方法,并直接返回其返回值。
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
4.5.4 HandlerFunctionAdapter
HandlerFunctionAdapter:这个类是用于处理实现了HandlerFunction接口的类,这个接口是一个函数式接口,用于定义一个函数,接受一个ServerRequest对象并返回一个Mono对象。它会将HttpServletRequest和HttpServletResponse对象转换成ServerRequest和ServerResponse对象,并调用apply方法来执行函数,并将返回值转换成一个ModelAndView对象。
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);
上面这几个接收的直接就是Requeset对象,不用SpringMVC做额外的解析,所以比较简单,比较复杂的是RequestMappingHandlerAdapter,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。
4.5.5 RequestMappingHandlerAdapter
RequestMappingHandlerAdapter:这个类是用于处理带有@RequestMapping注解的方法。它会根据请求的参数,头部,属性等来解析方法的参数,并调用反射来执行方法。
此处比较复杂,后续单独说明
4.6 初始化异常initHandlerExceptionResolvers()
负责将在处理器处理过程中抛出的异常转换为对应的模型-视图结果。
在initHandlerExceptionResolvers()方法中,获取所有类型为HandlerExceptionResolver的Bean。如果找不到任何HandlerExceptionResolver的Bean,那么它会使用一组默认的处理器异常解析器。
这里的默认是会取读取DispatcherServlet.properties
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
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) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
4.6.0 DispatcherServlet.properties
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
4.6.1 ExceptionHandlerExceptionResolver
用于处理通过@ExceptionHandler注解处理的方法抛出的异常。
4.6.2 ResponseStatusExceptionResolver
用于处理通过@ResponseStatus注解处理的异常
4.6.3 DefaultHandlerExceptionResolver
用于处理Spring MVC自己抛出的一些特定的异常
4.7 initRequestToViewNameTranslator
initRequestToViewNameTranslator方法用于初始化RequestToViewNameTranslator的,RequestToViewNameTranslator是一个接口,用于根据请求解析出一个默认的视图名称,当ModelAndView对象不为null,但是没有指定视图时,就会使用这个接口来获取视图名称。
寻找bean名字为viewNameTranslator试图解析器,没有的话用DefaultRequestToViewNameTranslator,并存放到viewNameTranslator。
工作原理是:它会去掉请求URL的前缀和后缀,然后将剩下的部分作为视图名称。例如,如果我们设置prefix为"/app/“,suffix为”.html",stripExtension为false,那么请求的URL"/app/home.html"就会被解析为"home.html"。如果我们设置stripExtension为true,那么就会被解析为"home"。
4.8 初始化视图解析器initViewResolvers()
默认是寻找所有类型为ViewResolver的bean,将他们作为试图解析器,默认的试图解析器是InternalResourceViewResolver。它们是用于将控制器返回的逻辑视图名解析为具体的视图对象,这个对象可以是一个 JSP、一个 HTML 页面、一个 PDF 视图、一个 Thymeleaf 模板等。
4.9 初始化重定向initFlashMapManager()
默认的是SessionFlashMapManager。
FlashMap是Spring MVC提供的一种临时存储数据的方式,它用于存储一次请求的数据,并在下一次请求中使用。FlashMap通常用于重定向请求时传递数据,比如,当你在处理POST请求后重定向到一个GET请求时,你可能希望重定向的GET请求能够访问POST请求处理的一些结果数据,这就可以使用FlashMap来实现。例如可以这样存储Flash属性:
@RequestMapping(method = RequestMethod.POST)
public String handlePostData(@ModelAttribute("data") Data data, RedirectAttributes redirectAttrs) {
// 处理POST请求...
// 将一些数据存储在FlashMap中,以便在重定向后的GET请求中使用
redirectAttrs.addFlashAttribute("message", "123");
// 重定向到GET请求
return "redirect:/get-data";
}
然后在重定向后的GET请求中提取Flash属性:
@RequestMapping(method = RequestMethod.GET)
public String displayData(Model model) {
// 如果存在Flash属性,它们会自动添加到Model中
if (model.containsAttribute("message")) {
System.out.println("Message-FlashMap: " + model.getAttribute("message"));
}
// 处理GET请求...
}