Spring MVC 文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc
1. Spring Web MVC
1.1. 简介
Spring MVC是基于Servlet API和Spring框架构建的项目,同时,Spirng WebFlux是支持反应式web构建的框架。
1.2. DispatcherServlet
DispatcherServlet用于分发请求,实际的请求处理工作由其他组件处理。
DispatcherServlet可以使用Java代码或者在web.xml中配置。其需要处理的工作有:请求映射、视图处理、异常处理等。
如下代码是DispatcherServlet的注册和初始化,该类会自动被Servlet容器检测到。
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
如下是web.xml中DispatcherServlet的注册和初始化配置。
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
1.2.1. Context层次结构
DispatcherServlet依赖WebApplicationContext进行配置。多个DispatcherServlet可以共用一个WebAplicationContext实例。Root WebApplicationContext包括数据服务bean,业务服务bean等,而Servlet WebpplicationContext仅包含特定servlet处理bean。
image
如下是WebApplicationContext的配置示例:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
如果不需要应用上下文,只需要使用getRootConfigClasses()返回配置并且getServletConfigClasses()返回null即可。
对应的web.xml如下:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
如果应用不需要上下文,仅需要配置root上下文并设置contextConfigLocation参数为空。
1.2.2. 特殊Bean类型
DispatcherServlet委派特殊Bean处理请求并返回相应。如下是DispatcherHandler能够检测的特殊Bean:
1.HandlerMapping:映射请求到Handler,有两个实现:RequestMappingHandlerMapping和SimpleUrlHandlerMapping。
2.HandlerAdapter:用于帮助DispatcherServlet调动handler。
3.HandlerExceptionResolver:解决映射异常,html错误视图等。
4.VieResolver:主要作用是把一个逻辑上的视图名称解析为一个真正的视图,SpringMVC中用于把View对象呈现给客户端的是View对象本身,而ViewResolver只是把逻辑视图名称解析为对象的View对象。
4.LocaleResolver、LocaleContextResolver:国际化资源处理。
5.ThemeResolver:主题处理,可以个性化主题处理。
6.MultipartResolver:处理multi-part request,如上传等。
7.FlashMapManager:存储FlashMap,用于处理重定向问题。
1.2.3. Web MVC配置
应用可以声明上述特殊bean来处理对应的请求。DispatcherServlet会检查WebApplicationContext,如果没有发现特殊bean,将返回DispatcherServlet.properties中定义的默认类型bean。
1.2.4. Servlet配置
在Servlet 3.0+环境中,支持在代码中和web.xml中配置servlet。如下是注册DispacherServlet的例子:
import org.springframework.web.WebApplicationInitializer;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
继承WebApplicationInitializer保证你的servlet被Servlet3容器加载。AbstractDispatcherServletInitializer是WebApplicationInitializer的抽象类,它通过重写方法来简化Servlet的配置。
下面是推荐的基于Java的应用配置方法:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
如果使用基于XML的配置,就应该直接扩展AbstractDispatcherServletInitializer。
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
AbstractDispatcherServletInitializer也提供简单的方法添加Filer实例。
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
AbstractDispatcherServletInitializer的isAsyncSupported保护方法提供 异步支持,默认设置为true。
如果想自定义DispatcherServlet,重写createDispatcherServlet方法。
1.2.5. Processing
DispatcherServlet处理请求流程如下:
1.查找WebApplicationContext并将其作为属性和请求绑定以便使用。
2.绑定local resolver到请求处理资源国际化,可以不使用。
3.绑定theme resolver到请求处理主题,可以不使用。
4.如果设定了multipart resolver并发送了multipart请求,请求的内容采用MultipartHttpServletRequest包装以便后续处理
5.查找匹配的handler,找到后顺序执行:preprocessors/postprocessors/controllers。
6.查找模型,找到则渲染视图,找不到则不渲染视图。
WebApplicationContext中的HandlerExceptionResolver用于处理异常请求。支持自定义异常处理。
DispatcherServlet也支持返回上次修改日期,其流程为:DispatcherServlet查找对应的映射处理器并检查该处理器是否实现LastModified接口,如果继承该接口,通过方法long getLastModified(request)返回上次修改时间值给客户端。
你可以通过添加Servlet初始化参数(init-param)在web.xml中自定义DispatcherServlet。下面是支持的初始化参数:
1.contexClass:继承自WebApplicationContext,实力化Servlet使用的上下文,默认使用XmlWebApplicationContext。
2.contextConfigLocation:上下文配置文件路径,支持多个路径配置多个上下文,对于在不同路径配置文件中重复定义的bean,以最后的定义为准。
3.namespace:WebApplicationContext的命名空间,默认[servlet-name]-servlet。
1.2.6. 拦截器
所有映射处理器都支持拦截器,用于对请求添加特殊处理。拦截器需要实现org.springframework.web.servlet包下的HandlerInterceptor接口的三个方法:
1.preHandler(..)-handler执行前处理
2.postHandler(..)-handler执行后处理
3.afterCompletion(..)-handler处理完成之后执行
preHandler(..)方法返回布尔值,你可以通过该函数打断或者继续handler的处理流程。true继续执行,false不继续执行接下来的拦截器或者handler。
1.2.7. 异常
如果在请求映射或者处理时发生异常,DispatcherServlet委托HandlerExceptionResolver解决异常,例如准备错误页面等。
下面是HandlerExceptionResolver的实现类:
1.SimpleMappingExceptionResolver:异常类与错误视图名称的映射,用于在浏览器中渲染错误页面。
2.DefaultHandlerExceptionResolver:处理异常并将其映射成HTTP错误码。
3.ResponseStatusExceptionResolver:使用@ResponseStatus注解处理异常并映射为HTTP错误码。
4.ExceptionHandlerExceptionResolver:在使用@Controller和@ControllerAdvice注解的类种,通过@ExceptionHandler注解处理异常。
异常处理过程:设置多个异常处理resolver,如果需要,设置resolver的优先级,优先级越高,执行的越晚。
HandlerExceptionResolver约定返回内容如下:
1.指向错误页面的ModeAndView。
2.如果异常在resolver中被处理,返回空的ModeAndView。
3.如果异常无法处理,返回null后继续下一个resolvers;如果均无法处理,异常抛给Servlet容器。
异常处理的配置就是在spring配置中添加HandlerExceptionesolver。对于Spring MVC的异常,使用@ResponseStatus注解的异常和@ExceptionHandler方法注解的异常,MVC配置自动声明内置的异常处理器。
容器中的错误页面
如果HandlerExceptionResolver依然无法处理异常或者响应错误码是4xx,5xx,Servlet容器会呈现一个错误页面。自定义容器的错误页面,需要在web.xml 中进行如下定义。
<error-page>
<location>/error</location>
</error-page>
当由servlet容器处理异常或者错去码是4xx或者5xx时,servlet容器根据配置的 url(/error)分发错误。然后DispatcherServlet可以通过@Controller映射处理返回异常视图名称,如下:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
1.2.8. 视图
Spring MVC定义ViewResolver和View放假鹅口来处理视图。ViewResolver定义名和实际视图的映射,View用于展示视图之前的数据准备。下面是ViewResolver的类层次结构:
1.AbstractCachingViewResolvre:用于缓存处理的视图实例。缓存可以提升性能,可以将cache属性设置为false来关闭缓存。如果必须刷新缓存,可以调用removeFromCache(String viewName, Locale loc)方法。
2.XmlViewResolver:实现ViewResolver接口,采用xml的配置方式,默认配置文件是/WEB-INF/view.xml。
3.ResourceBundleViewResolver:实现ViewResolver接口,使用ResourceBundle读取配置文件中视图类和视图的映射关系。配置文件中配置方式为:[viewname].(class)指定视图类,[viewname].url指定视图。
4.UrlBasedViewResolver:简单实现ViewResolver接口,关联逻辑视图名与url,不使用明确的视图定义。
5.InternalResourceViewResolver:UrlBaseViewResolver的子类,支持InternalResourceView/JstlView和TilesView。
6.FreeMarkerViewResolver:UrlBasedViewResolver的子类,支持FreeMarkerView视图处理。
7.ContentNegotiatingViewResolver:实现ViewResolver接口,用于根据请求名或者Accep请求消息头类型处理视图。
处理流程:可以设置多个resolver组成视图处理链,甚至可以设置处理的优先级。记住,优先级越高,越晚执行。
ViewResolver返回null表示视图未找到。在处理jsp时,InternalResourceViewResolver一定要设置为最后处理的resolver。下文会介绍ViewResolver的配置。
重定向:
特殊的重定向:通过视图名的前缀进行重定向。UrlBaseViewResolver识别前缀然后将前缀后的内容作为重定向的地址。
控制器Controller返回RedirectView也是重定向。例如,逻辑视图名:redirect:/myapp/some/resource基于当前上下文重定向相对路径,而redirect:http://myhost.com/some/arbitrary/path基于绝对路径进行重定向。
如果控制器的方法使用@ResponseStatus注解,那么注解值优先于RedirectView设置的响应值。
转发:UrlBasedViewResolver可以根据视图名处理转发。通过创建InternalResourceView执行RequestDispatcher.forward()。这样对于InternalResourceViewResolver和InternalResourceView而言前缀失效,不进行重定向而进行转发。
内容协商:协商返回的数据格式。
ContentNegotiatingViewResolver不直接处理view,而是委托给其他符合客户端请求格式的resolver。检测Accept消息头中的数据格式或者从参数(如/path?form=pdf)中提取格式。ContentNegotiatingViewResolver通过比较media type(就是Content-Type)来选取合适的视图处理。如果没有找到对应的,使用DefaultView。Accept消息头可以设置统配,例如text/*可以匹配到text/xml的视图处理。
1.2.9. 国际化资源
DispatcherServlet支持根据客户端地区自动消息转换,这是通过LocaleResolver实现的。
请求来到时,DispatcherServlet查找对应的LocaleResolver处理国际化。使用RequestContext.getLocale()方法获取当前处理国际化resolver的地区。
LocaleContextResolver提供LocaleContext,用以获取当前client的时区。请求的时区信息开通过RequestContext.getTmeZone获取。
地区信息还可以使用Header Resolver从accept-language消息头中获取并处理
地区信息还可以使用Cookie Resolver从cookie中获取并处理。CookieLocaleResolver例子如下:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="clientlanguage"/>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
<property name="cookieMaxAge" value="100000"/>
</bean>
CookieLocaleResolver属性如下:
1.cookieName:cookie名称
2.cookieMaxAge:客户端存储coodie的最大值,-1表示不存储cookie,客户端关闭browser时cookie清空。
3.cookiePath:存储cookie的地址。
SessionLocaleResolver也允许从session中获取Locale和TimeZone。该信息存储于Servlet容器的HttpSession中。
地区拦截器:通过添加LocaleChangeInterceptor处理特定映射的地区。
1.2.10. 主题
主题是一系列静态资源,如样式表、图片等。Spring MVC支持主题的更改。
定义主题:要想使用主题,必须实现org.springframework.ui.context.ThemeSource接口。WebApplicaionContext继承ThemeSource但将处理任务委派给另一个实现:org.springframework.ui.context.support.ResourceBundleThemeSource。它读取配置文件中主题的定义构建主题,配置如下:
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
对于JSP,使用spring:theme自定义标签,如下:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
</head>
<body style="background=<spring:theme code='background'/>">
...
</body>
</html>
主题处理器有:FixedThemeResolver、SessionThemeResolver和CookieThemeResolver。提供ThemeChangeInterceptor拦截器根据请求改变主题。
1.2.11. Multipart resolver
Multipart resolver用于处理诸如上传等multipart请求。使用时需要在DispatcherServlet中定义MultipartResolver,然后会监听content-type为multipart/form-data的请求,然后解析内容并将当前的HttpServletRequest打包为MultipartHttpServletRequest以便处理。
Apache文件上传:通过配置CommonsMultipartResolver来使用Apache文件上传。需要依赖commons-fileupload包。
在Servlet3.0中,添加multipart支持需要进行注册。在web.xml中添加<multipart-config>然后添加StandardServletMultipartResolver处理multipart类型请求。
网友评论