11 SpringMVC
11 SpringMVC
前述
SpringMVC 是基于 Servlet API 构建的原始 Web 框架,也是 Spring 框架的一部分。它提供了灵活可扩展的 MVC 架构,方便开发者构建高性能的 Web 应用程序,并与 Spring 生态系统无缝集成。
SpringMVC 或者其他较为成熟的 MVC 框架,解决的问题无外乎以下几点。
- 将 Web 页面的请求传给服务器。
- 根据不同的请求处理不同的逻辑单元。
- 返回处理结果数据并跳转至响应的页面。
代码根目录为 https://gitee.com/shj.com/sample-projects/tree/master/back/sample-src-code/
。
11.1 SpringMVC 快速体验
SpringMVC 快速体验
步骤如下。
- 配置 web.xml。
SpringMVC 的实现原理是通过 Servlet 拦截所有 URL 来达到控制的目的,所以 web.xml 的配置是必需的。
关键的是要配置两个地方 contextConfigLocation 和 DispatcherServlet。
2. 创建 Spring 配置文件 springmvc.xml。
3. 创建 model。
4. 创建 controller。
5. 创建 Jsp 视图 文件。
6. 创建 Servlet 配置文件吗,这里我们用 Controller 上面的映射代替。
详见 sample-spring-mvc-demo,idea 对着项目右击->run(debug) maven -> tomcat7:run。或者直接在 Maven 项目中找到对应插件启动。
输入 http://localhost:8081/sample-spring-mvc-demo/queryItem
11.2 ContextLoaderListener
Spring Boot 打成 war 包时,会给容器注入 SpringBootServletInitializer 对象,如下 onStartup 时会注入 ContextLoaderListener 监听器,在 Tomcat 启动时加载 Spring 上下文。
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("logging.register-shutdown-hook", false);
this.logger = LogFactory.getLog(this.getClass());
WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
}
注意其他方式,如 Spring boot Jar 启动没有利用 ContextLoaderListener 类,而是通过 DispatcherServlet 的 init() 方法处理的,且 ContextLoaderListener 执行顺序在 DispatcherServlet 加载前面。
ContextLoaderListener
ContextLoaderListener 的作用就是启动 Web 容器时,自动装配 ApplicaionContext 的配置信息。因为它实现了 ServletContextListener 这个接口,在 web.xml 配置这个监听器,启动容器时,就会默认执行它实现的方法,使用 ServletContextListener 接口,开发者能够在为客户端请求提供服务之前向 ServletContext 中添加任意的对象。这个对象在 ServletContext 启动的时候被初始化,然后在 ServletContext 整个运行期间都是可见的。
每一个 Web 应用都有一个 ServletContext 与之相关联。ServletContext 对象在应用启动时被创建,在应用关闭时被销毁。ServletContext 在全局范围内有效,类似于应用中的一个全局变量。
在 ServletContextListener 中的核心逻辑便是初始化 WebApplicationContext 实例并存放至 ServletContext 中。
11.2.1 ServletContextListener 的使用
ServletContextListener 的使用
使用步骤如下。
- 创建自定义的 ServletContextListener,见 MyDataContextListener。
- 注册监听器。
在 web.xml 文件中需要注册自定义的监听器。
<listener>
<listener-class>com.stu.springmvc.listener.MyDataContextListener</listener-class>
</listener>
- 测试。
一旦 Web 应用启动的时候,我们就能在任意的 Servlet 或者 JSP 中通过下面的方式获取我们初始化的参数,如String myData = (String)getServletContext().getAttribute("myData")
,可以见 ItemController 示例。
11.2.2 Spring 中的 ContextLoaderListener
Spring 中的 ContextLoaderListener
ServletContext 启动之后会调用 ServletContextListener 的 contextInitialized 方法,那么我们就从这个函数开始进行分析。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
}
public class ContextLoader {
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 1. WebApplicationContext 存在性的验证。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
// web.xml 中存在多次 ContextLoader 定义
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 {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// 初始化 context
if (this.context == null) {
// 2. 创建 WebApplicationContext 实例。
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 3. 将实例记录在 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) {
// 4. 映射当前的类加载器与创建的实例到全局变量 currentContextPerThread 中。
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;
}
}
}
该函数主要步骤如下。
- WebApplicationContext 存在性的验证。
通过查看 ServletContext 实例中是否有对应 key 的属性验证 WebApplicationContext 是否已经创建过实例。如果没有通过 createWebApplicationContext() 方法来创建实例,并存放至 ServletContext 中。
2. 创建 WebApplicationContext 实例。
如果通过验证,则 Spring 将创建 WebApplicationContext 实例。
如下代码,在初始化的过程中,程序首先会读取 ContextLoader 类的同目录下的属性文件 ContextLoader.properties,并根据其中的配置提取将要实现 WebApplicationContext 接口的实现类,并根据这个实现类通过反射的方式进行实例的创建。
public class ContextLoader {
// 文件在 spring-web 模块源码的 org.springframework.web.context 包下,内容如下
// org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
}
- 将实例记录在 servletContext 中。
- 映射当前的类加载器与创建的实例到全局变量 currentContextPerThread 中。
11.3 DispatcherServlet
DispatcherServlet
在 Spring 中,ContextLoaderListener 只是辅助功能,用于创建 WebApplicationContext 类型实例,而真正的逻辑实现是在 DispatcherServlet 中进行的,DispatcherServlet 是实现 servlet 接口的实现类。
servlet 的生命周期是由 servlet 的容器来控制的,它可以分为 3 个阶段:初始化、运行和销毁(网上也有说四个、五个阶段的,参考理解就行)。
可参考文章理解 Servlet 系列:生命周期(init、 service、destroy)详解。
11.3.1 servlet 的使用
servlet 的使用
使用步骤如下。
- 建立 servlet,见 MyServlet 类。
- web.xml 添加配置。
访问 http://localhost:8081/sample-spring-mvc-demo/myservlet.htm
11.3.2 DispatcherServlet 的初始化
DispatcherServlet 的初始化
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 解析 init-param 并封装至 pvs 中
// 1. 封装及验证初始化参数。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 将当前的这个 sevlet 类转化为一个 BeanWrapper, 从而能够以 Spring 的方式来对 init-param 的值进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// 注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 空实现,留给子类覆盖
initBeanWrapper(bw);
// 属性注入
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 留给子类扩展 Let subclasses do whatever initialization they like.
initServletBean();
}
}
属性注入主要包含以下几个步骤。
- 封装及验证初始化参数。
- 将当前 servlet 实例转化成 BeanWrapper 实例。
- 注册相对于 Resource 的属性编辑器。
- 属性注入。
- servletBean 的初始化。
1. 封装及验证初始化参数
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
private static class ServletConfigPropertyValues extends MutablePropertyValues {
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
}
封装属性主要是对初始化的参数进行封装。也就是 servlet 中配置的 <init-param>
中配置的封装。
2. servletBean 的初始化
父类 FrameworkServlet 覆盖了 initServletBean 函数,如下。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
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();
}
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");
}
}
}
加了个计时器,而关键的初始化逻辑实现委托给了 initWebApplicationContext()。
11.3.3 WebApplicationContext 的初始化
WebApplicationContext 的初始化
initWebApplicationContext 函数的主要工作就是创建或刷新 WebApplicationContext 实例并对 servlet 功能所使用的变量进行初始化。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
protected WebApplicationContext initWebApplicationContext() {
// 1. 寻找或创建对应的 WebApplicationContext 实例
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
// context 实例在构造函数中被注入
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 刷新上下文环境
// 2. configureAndRefreshWebApplicationContext
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 根据 contextAttribute 属性加载 WebApplicationContext
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 3. 刷新
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
}
本函数的初始化主要包含几个部分。
- 寻找或创建对应的 WebApplicationContext 实例。
- configureAndRefreshWebApplicationContext
- 刷新
1. 寻找或创建对应的 WebApplicationContext 实例
1. 通过构造函数的注入进行初始化
initWebApplicationContext 函数中,有个判断逻辑 this.webApplicationContext != null 后,便可以确定 this.webApplicationContext 是否是通过构造函数来进行初始化的。
2. 通过 contextAttribute 进行初始化
通过在 web.xml 文件中配置的 servlet 参数 contextAttribute 来查找 ServletContext 中对应的属,默认为 WebApplicationContext.class.getName + ".ROOT"。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
}
3. 重新创建 WebApplicationContext 实例
如果通过以上两种方式并没有找到任何突破,那就没办法了,只能在这里重新创建新的实例了。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 根据 servlet 的初始化参数 contextClass,如果没有配置默认为 XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
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");
}
// 通过反射方式实例化 contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// parent 为在 ContextLoaderListener 中创建的实例
// 在 ContextLoaderListener 加载的时候初始化的 WebApplicationContext 类型实例
wac.setParent(parent);
// 获取 contextConfigLocation 属性,配置在 servlet 初始化参数中
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 初始化 Spring 环境包括加载配置文件等
configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
2. configureAndRefreshWebApplicationContext
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
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);
applyInitializers(wac);
// 加载配置文件及整合 parent 到 wac,无论哪种方式一定会执行
wac.refresh();
}
}
3. 刷新
刷新
子类 DispatcherServlet 中进行了重写,主要用于刷新 Spring 在 Web 功能实现中所必须使用的全局变量。
public class DispatcherServlet extends FrameworkServlet {
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}
1. 初始化 MultipartResolver
MultipartResolver 主要用来处理文件上传。默认情况下,Spring 是没有 multipart 处理的,开启功能需要添加 multipart 解析器,开启配置略。
public class DispatcherServlet extends FrameworkServlet {
private void initMultipartResolver(ApplicationContext context) {
try {
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.
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
}
因为之前已经完成了 Spring 中配置文件的解析,所以在之类只要在配置文件注册过都可以通过 ApplicationContext 提供的 getBean 方法来直接获取对应 bean,进而初始化 MultipartResolver 中的 multipartResolver 变量。
2. 初始化 LocaleResolver
在 Spring 的国际化配置中一共有 3 种使用方式,关注页面配置和 Spring 配置,详细可参考 SpringMVC4.3x 教程之七国际化的三种实现详解。
- 基于 URL 参数的配置。
- 基于 session 的配置。
- 基于 cookie 的国际化配置。
public class DispatcherServlet extends FrameworkServlet {
private void initLocaleResolver(ApplicationContext context) {
try {
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.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
}
提取配置文件中设置的 LocalResolver 来初始化 DispatcherServlet 中的 localeResolver 属性。
3. 初始化 ThemeResolver
即在 Web 开发中经常会通过主题来控制网页风格,目前主流都是前后端分离结构,这里不展开明细。关注以下要点。
- 主题资源。
- 主题解析器。主题解析器接口是
org.springframework.web.servlet.ThemeResolver
- 拦截器。可以根据用户请求来改变主题。
public class DispatcherServlet extends FrameworkServlet {
private void initThemeResolver(ApplicationContext context) {
try {
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.themeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +
"': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
}
}
}
}
4. 初始化 HandlerMappings
加载 HandlerMapping 可能存在三种情况。
- 从 Spring 容器中获取所有类型为 HandlerMapping 的 bean,作为 handerMappings 成员变量。
- 从 Spring 容器中获取名为 handlerMapping 的 bean,作为 handerMappings 成员变量。
- 从 DispatcherServlet.properties 配置文件获取默认 HandlerMapping 实现类的全限定类名,实例化并作为 handerMappings 成员变量。
public class DispatcherServlet extends FrameworkServlet {
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
// 读取默认配置的适配器
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 1. 从 Spring 容器中获取所有类型为 HandlerMapping 的 bean,作为 handerMappings 成员变量。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 2. 从 Spring 容器中获取名为 handlerMapping 的 bean,作为 handerMappings 成员变量。
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
// 3. 从 DispatcherServlet.properties 配置文件获取默认 HandlerMapping 实现类的全限定类名,实例化并作为 handerMappings 成员变量。
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
}
5. 初始化 HandlerAdapters
派遣器 servlet 通过处理器映射得到处理器后,会轮询处理器适配器模块,查找能够处理当前 HTTP 请求的处理器适配器的实现,处理适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型、注解控制器类型或者远程调用处理器类型,来选择一个适当的处理器适配器的实现,从而适配当前的 HTTP 请求。
关注以下要点。
- HTTP 请求处理器适配器(HttpRequestHandlerAdapter)。
- 简单控制器处理器适配器(SimpleControllerHandlerAdapter)。
- 注解方法处理器适配器(AnnotationMethodHandlerAdapter)。
如下理解 HandlerAdapter。
- HttpRequestHandlerAdapter 主要是适配静态资源处理器,静态资源处理器就是实现了 HttpRequestHandler 接口的处理器,这类处理器的作用是处理通过 SpringMVC 来访问的静态资源的请求。
- SimpleControllerHandlerAdapter 是 Controller 处理适配器,适配实现了 Controller 接口或 Controller 接口子类的处理器,比如我们经常自己写的 Controller 来继承 MultiActionController。
- SimpleServletHandlerAdapter 是 Servlet 处理适配器, 适配实现了 Servlet 接口或 Servlet 的子类的处理器,我们不仅可以在 web.xml 里面配置 Servlet,其实也可以用 SpringMVC 来配置。Servlet,不过这个适配器很少用到,而且 SpringMVC 默认的适配器没有他,默认的是前面的三种。
public class DispatcherServlet extends FrameworkServlet {
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
// 如果无法找到对应的 bean,那么系统会尝试加载默认的适配器
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
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<>();
}
}
}
6. 初始化 HandlerExceptionResolvers
基于 HandlerExceptionResolvers 接口的异常处理,使用这种方式只需要实现 resolverException 方法,该方法返回一个 ModelAndView 对象,在方法内部对异常的类型进行判断,然后尝试生成对应的 ModelAndView 对象,如果该方法返回了 null,则 Spring 会继续寻找其他的实现了 HandlerExceptionResolver 接口的 bean(必须在 Spring 容器中)。
public class DispatcherServlet extends FrameworkServlet {
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");
}
}
}
}
7. 初始化 RequestToViewNameTranslator
当 Controller 处理器方法没有返回一个 View 对象或逻辑视图名称,并且在该方法中没有直接往 response 的输出流里面写数据的时候,Spring 就会采用约定好的方式提供一个逻辑视图名称。
这个视图名称是通过 Spring 定义的 org.springframework.web.servlet.RequestToViewNameTranslator 接口的 getViewName 方法来实现的,我们可以自定义实现,返回对应的视图。
默认的 viewNameTranslator 是 DefaultRequestToViewNameTranslator。
public class DispatcherServlet extends FrameworkServlet {
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.viewNameTranslator);
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
if (logger.isTraceEnabled()) {
logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
"': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");
}
}
}
}
8. 初始化 initViewResolvers
在 SpringMVC 中,当 Controller 将请求处理结果放入到 ModelAndView 中以后,DispatcherServlet 会根据 ModelAndView 选择合适的视图进行渲染。那么在 SpringMVC 中是如何选择合适的 View 呢?View 对象是如何创建的呢?答案就在 ViewResolver 中。
ViewResolver 接口定义了 resolveViewName 方法,根据 viewName 创建合适类型的 View 实现。
为了使用 InternalResourceViewResolver 我们都会在 SpringMVC 的配置文件中进行如下配置。
<!-- 自定义视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
public class DispatcherServlet extends FrameworkServlet {
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
}
else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default ViewResolver later.
}
}
// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
}
9. 初始化 initFlashMapManager
SpringMVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向时候非常必要,例如 Post/Redirect/Get 模式。Flash attributes 在重定向之前暂存以便重定向之后还能使用,并立即删除。
SpringMVC 有两个主要的抽象来支持 flash attributes。FlashMap 用于保持 flash attributes,而 initFlashMapManager 用于存储、检索、管理 FlashMap 实例。
flash attributes 支持默认开启("on")并不需要显示启用,它永远不会导致 HTTP Session 的创建。这两个 FlashMap 实例都可以通过静态方法 RequestContextUtils 从 SpringMVC 的任何位置访问。
public class DispatcherServlet extends FrameworkServlet {
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.flashMapManager);
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
if (logger.isTraceEnabled()) {
logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +
"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");
}
}
}
}
11.4 DispatcherServlet 的逻辑处理
DispatcherServlet 的逻辑处理
我们查看常用的 get 和 post 对应的两个函数的实现。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* 对于不同的方法,Spring 并没有做特殊处理,而是统一将程序再一次地引导至 processRequest 中
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 记录当前时间,用于计算 web 请求的处理时间
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 1. 为了保证当前线程的 LocaleContext 以及 RequestAttributes 可以在当前请求后还能恢复,提取当前线程的两个属性。
// 2. 根据当前 request 创建对应的 LocaleContext 和 RequestAttributes,并绑定到当前线程。
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 3. 委托给 doService 方法进一步处理。
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 4. 请求处理结束后恢复线程到原始状态。
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
// 5. 请求处理结束后无论成功与否发布时间通知。
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
}
主要工作如下。
- 为了保证当前线程的 LocaleContext 以及 RequestAttributes 可以在当前请求后还能恢复,提取当前线程的两个属性。
- 根据当前 request 创建对应的 LocaleContext 和 RequestAttributes,并绑定到当前线程。
- 委托给 doService 方法进一步处理。
- 请求处理结束后恢复线程到原始状态。
- 请求处理结束后无论成功与否发布时间通知。
doService()
public class DispatcherServlet extends FrameworkServlet {
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
}
doService 中做了一些准备工作,即将前面创建的 localeResolver、themeResolver 等设置在 request 属性中,而这些属性会在接下来的处理中派上用场。
doDispatch()
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 如果是 multipartContent 类型的 request 则转换 request 为 MultipartHttpServletRequest 类型的 request
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 根据 request 信息寻找对应的 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果没有找到对应的 handler 则通过 response 反馈错误信息
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 根据当前的 handler 寻找对应的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 如果当前 handler 支持 last-modified 头处理
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 拦截器的 preHandler 方法的调用
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 真正的激活 handler 并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 应用所有拦截器的 posthandler 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理页面跳转、完成处理激活触发器等
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
// 视图名称转换应用于需要添加前缀后缀的情况
if (mv != null && !mv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
// 如果在 Handler 实例的处理中返回了 view,那么需要做页面的处理
if (mv != null && !mv.wasCleared()) {
// 处理页面跳转
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 完成处理激活触发器
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
}
public class HandlerExecutionChain {
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
}
11.4.1 multipartContent 类型的 request 处理
checkMultipart
public class DispatcherServlet extends FrameworkServlet {
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
}
11.4.2 根据 request 信息寻找对应的 Handler
对应的 Controller 是怎么转为 HandlerExecutionChain 的。
getHandler
getHandler
public class DispatcherServlet extends FrameworkServlet {
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
}
之前我们学过,在系统启动时 Spring 会将所有的映射类型的 bean 注册到 this.handlerMappings 变量中,所以此函数的目的就是遍历所有的 HandlerMapping,并调用其 getHandler 方法进行封装处理。
以 SimpleUrlHandlerMapping 为例查看其 getHandler 方法如下。
SimpleUrlHandlerMapping
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 1. 根据 request 获取对应的 handler
Object handler = getHandlerInternal(request);
if (handler == null) {
// 如果没有对应 request 的 handler 则使用默认的 handler
handler = getDefaultHandler();
}
// 如果也没有提供默认的 handler 则无法继续处理返回 null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 2. 加入拦截器到执行链
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
函数中首先会使用 getHandlerInternal 方法根据 request 信息获取对应的 Handler,如果以 SimpleUrlHandlerMapping 为例分析,那么我们推断此步骤提供的功能很可能就是根据 URL 找到匹配的 Controller 并返回。
当然如果没有找到对应的 Controller 处理器那么程序会尝试去查找配置中的默认处理器,当然,当查找的 controller 为 String 类型时,那就意味着返回的是配置的 bean 名称,需要根据 bean 名称查找对应的 bean,最后,还要通过 getHandlerExecutionChain 方法对返回的 Handler 进行封装,以确保满足返回类型的匹配。
1. 根据 request 查找对应的 Handler
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 截取用于匹配的 url 有效路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 根据路径寻找 Handler
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
// 如果请求的路径仅仅是 "/",那么使用 RootHandler 进行处理
if (StringUtils.matchesCharacter(lookupPath, '/')) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
// 无法找到 handler 则使用默认 handler
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
// 根据 beanName 获取对应的 bean
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
// 模板方法
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
}
1.1 根据路径寻找 Handler
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
// 直接匹配情况的处理
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
// 通配符匹配的处理
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
}
1.2 将 Handler 封装成 HandlerExecutionChain
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
}
2. 加入拦截器到执行链
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
}
11.4.3 没找到对应的 Handler 的错误处理
没找到对应的 Handler 的错误处理
没找到 Handler 以及默认请求也未设置就会出现 Handler 为空的情况,就只能通过 response 向用户返回错误信息。
public class DispatcherServlet extends FrameworkServlet {
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}
11.4.4 根据当前 Handler 寻找对应的 HandlerAdapter
根据当前 Handler 寻找对应的 HandlerAdapter
在 WebApplicationContext 的初始化过程中我们讨论了 HandlerAdapter 的初始化,了解了在默认情况下普通的 Web 请求会交给 SimpleControllerHandlerAdapter 去处理。
public class DispatcherServlet extends FrameworkServlet {
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");
}
}
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
}
11.4.5 缓存处理
略。关注下 Last-Modified
缓存机制。
11.4.6 HandlerInterceptor 的处理
HandlerInterceptor 的处理
即通过实现 HandlerInterceptor 接口,我们就可以拦截 Web 请求,进行前置处理和后置处理(调用链还是在 DispatcherServlet 的 doDispatch 方法中)。
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
11.4.7 逻辑处理
逻辑处理
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
}
对于普通的 Web 请求,Spring 默认使用 SimpleControllerHandlerAdapter 类进行处理,我们进入 SimpleControllerHandlerAdapter 类的 handler 方法如下。
handle
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
}
public abstract class AbstractController extends WebContentGenerator implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", getAllowHeader());
return null;
}
// Delegate to WebContentGenerator for checking and preparing.
checkRequest(request);
prepareResponse(response);
// Execute handleRequestInternal in synchronized block if required.
// 如果需要 session 内的同步执行
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// 调用用户的逻辑
return handleRequestInternal(request, response);
}
}
}
// 调用用户的逻辑
return handleRequestInternal(request, response);
}
}
11.4.8 异常视图的处理
异常视图的处理
public class DispatcherServlet extends FrameworkServlet {
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
}
11.4.9 根据视图跳转页面
根据视图跳转页面
public class DispatcherServlet extends FrameworkServlet {
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 1. 解析视图名称
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 2. 页面跳转
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
}
1. 解析视图名称
public class DispatcherServlet extends FrameworkServlet {
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
}
以 InternalResourceViewResolver 为例来分析。
1.1 InternalResourceViewResolver
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
// 不存在缓存的情况下直接创建视图
return createView(viewName, locale);
}
else {
// 直接从缓存中提取
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
}
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
// 重写了 createView 函数
protected View createView(String viewName, Locale locale) throws Exception {
// 如果当前解析器不支持当前解析器如 viewName 为空等情况
if (!canHandle(viewName, locale)) {
return null;
}
// 处理前缀为 redirect:xx 的情况
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}
// 处理前缀为 forward:xx 的情况
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
InternalResourceView view = new InternalResourceView(forwardUrl);
return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
// 添加前缀以及后缀
view.setUrl(getPrefix() + viewName + getSuffix());
view.setAttributesMap(getAttributesMap());
String contentType = getContentType();
if (contentType != null) {
// 设置 contentType
view.setContentType(contentType);
}
String requestContextAttribute = getRequestContextAttribute();
if (requestContextAttribute != null) {
view.setRequestContextAttribute(requestContextAttribute);
}
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
}
主要考虑到了几个方面的处理。
- 基于效率考虑,提供了缓存的支持。
- 提供了对 redirect:xx 和 forward:xx 前缀的处理。
- 添加了前缀及后缀,并向 View 中加入了必须的属性设置。
2. 页面跳转
doDispatch() 里面的 render 方法会执行 视图的 render 方法。
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
/**
* 将要用到的属性放入 request 中,以便在其他地方可以直接调用
*/
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
HttpServletRequest request, HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
return mergedModel;
}
}
public class InternalResourceView extends AbstractUrlBasedView {
// 处理页面跳转
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
// 将 model 中的数据以属性的方式设置到 request 中
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
}