MVC是一种通用软件架构,M
是指业务模型,V
是指用户界面,C
则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。MVC架构的工作流程如下:
用户发送请求到服务器的Controller(C)。
Controller调用相应的Model(M)处理请求。
根据处理结果渲染对应的View视图(V)进行响应。
SpringMVC是MVC架构的一种具体实现,为表述层(web页面+Servlet)开发提供了一整套完备的解决方案。
SpringMVC的主要特点如下:
Spring系列产品,与Spring的IOC容器等基础设施无缝对接。
基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理。
表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案。
内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可。
性能卓著,尤其适合现代大型、超大型互联网项目要求。
注意:
默认会标记
src\main\java
为源代码根目录,src\main\resources
为资源根目录,若没有则需手动标记。必须在pom.xml中修改打包方式为war包:
<packaging>war</packaging>
。
新建src\main\webapp\WEB-INF\web.xml
文件,配置DispatcherServlet
统一处理前端请求。
361
2<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd
5 "
6 version="4.0">
7
8 <!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
9 <servlet>
10 <servlet-name>dispatcherServlet</servlet-name>
11 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
12 <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
13 <init-param>
14 <!-- contextConfigLocation为固定值 -->
15 <param-name>contextConfigLocation</param-name>
16 <!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
17 <param-value>classpath:spring-mvc-config.xml</param-value>
18 </init-param>
19 <!--
20 作为框架的核心组件,在启动过程中有大量的初始化操作要做,而这些操作放在第一次请求时才执行会严重影响访问速度
21 因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
22 -->
23 <load-on-startup>1</load-on-startup>
24 </servlet>
25 <servlet-mapping>
26 <servlet-name>dispatcherServlet</servlet-name>
27 <!--
28 设置springMVC的核心控制器所能处理的请求的请求路径,一般为"/"。
29 "/"可以匹配/login或.html或.js或.css方式的请求路径, 但是/不能匹配.jsp请求路径的请求。
30 "/*"则可以匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法。
31 -->
32 <url-pattern>/</url-pattern>
33 </servlet-mapping>
34
35</web-app>
36
如需在Web工程启动时,加载Spring上下文配置,只需在web.xml中注册ContextLoaderListener
监听器即可。
131<!-- 配置Spring提供的监听器,用于启动服务时加载容器。默认加载WEB-INF/applicationContext.xml文件 -->
2<listener>
3 <listener-class>
4 org.springframework.web.context.ContextLoaderListener
5 </listener-class>
6</listener>
7
8<!-- 手动指定Spring配置文件位置 -->
9<context-param>
10 <param-name>contextConfigLocation</param-name>
11 <param-value>classpath:applicationContext.xml</param-value>
12</context-param>
13
在资源根目录下新建spring-mvc-config.xml
文件(与web.xml中对应即可),配置表述层组件扫描和默认视图解析器。
221
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:mvc="http://www.springframework.org/schema/mvc"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
7
8 <!-- 自动扫描包 -->
9 <context:component-scan base-package="com.huangyuanxin.notes.springmvc.controller"/>
10
11 <!-- 默认视图解析器,可用于处理jsp等视图 -->
12 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
13 <property name="order" value="3"/>
14 <property name="prefix" value="/WEB-INF/pages/"/>
15 <property name="suffix" value=".jsp"/>
16 </bean>
17
18 <!--开启注解驱动支持(虽然入门案例可以省略,但一般项目都会进行配置)-->
19 <mvc:annotation-driven></mvc:annotation-driven>
20
21</beans>
22
161package com.huangyuanxin.notes.springmvc.controller;
2
3import org.springframework.stereotype.Controller;
4import org.springframework.web.bind.annotation.RequestMapping;
5
6
7public class IndexController {
8
9 "/") (
10 public String index() {
11 //设置视图名称
12 return "index";
13 }
14
15}
16
新建src\main\webapp\WEB-INF\pages\index.jsp
文件(与视图解析器配置的前缀后缀以及视图名称相对应),编写JSP页面如下。
101
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Index</title>
6</head>
7<body>
8<h1>Hello, SpringMVC!(Jsp)</h1>
9</body>
10</html>
添加一个Tomcat服务器,部署当前应用,设置上下文路径后进行启动。
启动完成后访问http://localhost:8080/页面,显示如下即代表测试成功。
参数名 | 参数说明 |
---|---|
contextConfigLocation | spring-mvc-config.xml文件位置,支持以逗号分隔配置多个。 |
contextClass | 上下文实现类,默认为XmlWebApplicationContext 。 |
namespace | WebApplicationContext的命名空间。默认为[servlet-name]-servlet 。 |
throwExceptionIfNoHandlerFound | 在找不到请求的处理程序时是否抛出NoHandlerFoundException。默认为false,此时将响应404(NOT_FOUND),而不会引发异常。 注意:如果配置了DefaultServletHandler,则在找不到请求处理程序时一律交给它来处理。 |
请求映射指根据请求路径、请求方法、请求参数等信息,将请求映射到对应的控制器方法,一般使用@RequestMapping
注解来配置。
@RequestMapping注解的path(value)属性用来声明请求路径,类上为一级路径,方法上为二级路经,SpringMVC会自动进行拼接。
91// /mapping/path
2
3"mapping") // 一级路径 (
4public class RequestMappingController {
5 "path") // 二级路径 (
6 public String testMapping() {
7 return "success";
8 }
9}
提示:
一个控制器方法可以配置多个请求路径,用于将不同的请求映射到同一段处理逻辑。
Spring MVC默认开启
.*
后缀模式匹配,以便映射到/person
的控制器也能映射到/person.*
。建议设置useSuffixPatternMatching(false)和favorPathExtension(false)来关闭后缀匹配模式,并通过Accept请求头进行替代。
请求路径还可以嵌入
${…}
占位符,这将在启动时从系统环境等其他属性源中解析替换。
可以通过Ant风格的通配符来模糊匹配请求路径:
?
:表示任意的单个字符。
*
:表示任意的0个或多个字符。
/**/
:表示任意的一层或多层目录。
231
2"mapping") (
3public class RequestMappingController {
4
5 // /mapping/ant/a1b
6 "ant/a?b") (
7 public String testAntMapping01() {
8 return "success";
9 }
10
11 // /mapping/ant/cd
12 "ant/c*d") (
13 public String testAntMapping02() {
14 return "success";
15 }
16
17 // /mapping/ant/v1/v1.0/e
18 "ant/**/e") (
19 public String testAntMapping03() {
20 return "success";
21 }
22}
23
@RequestMapping注解的method属性用来声明请求方式,如果方式不匹配则报"405:Request method 'POST' not supported"错误。
51value = "method/post", method = RequestMethod.POST) (
2public String testPostMethodMapping() {
3 return "success";
4}
5
为了简化请求方法匹配,SpringMVC设计了一些组合注解,如@GetMapping
、@PostMapping
、@PutMapping
和@DeleteMapping
等。
41value = "method/get") (
2public String testGetMethodMapping() {
3 return "success";
4}
浏览器默认只能发送GET
和POST
请求,如需发送其它类型请求,可用POST请求+_method参数
进行伪造。
首先web.xml
文件中配置过滤器如下:
101<!--配合前端伪造PUT等其它类型请求-->
2<filter>
3 <filter-name>HiddenHttpMethodFilter</filter-name>
4 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
5</filter>
6<filter-mapping>
7 <filter-name>HiddenHttpMethodFilter</filter-name>
8 <url-pattern>/*</url-pattern>
9</filter-mapping>
10
然后前端发送请求时改为POST请求,并附带_method参数指定要伪造的请求方法。
51<form action="mapping/method/put" method="post">
2 <input type="hidden" name="_method" value="PUT">
3 <input type="submit" value="提交">
4</form>
5
这时就可以正确匹配控制器中的PUT方法了。
51value = "method/put", method = RequestMethod.PUT) (
2
3public String testPutMethodMapping() {
4 return "success!";
5}
@RequestMapping注解的params属性和headers属性分别用来声明控制器方法所需的请求参数和请求头,支持的格式如下:
"param":要求请求映射所匹配的请求必须携带param请求参数。
"!param":要求请求映射所匹配的请求必须不携带param请求参数。
"param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value。
"param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value。
111// 请求参数必须携带username、password和auth参数,不能携带limit参数,且password不能是123456,auth必须是ok。
2value = "params", params = {"username", "password!=123456", "auth=ok", "!limit"}) (
3public String testParamsMapping() {
4 return "success";
5}
6
7// 请求头必须携带Origin参数,且必须为www.huangyuanxin.com
8value = "headers", headers = {"Origin=www.huangyuanxin.com"}) (
9public String testHeadersMapping() {
10 return "success";
11}
特别的,对于Content-Type和Accept请求头,可分别通过consumes
和produces
属性来匹配。
91path = "/pets", consumes = "application/json") (
2public void addPet( Pet pet) {
3}
4
5path = "/pets/{petId}", produces = "application/json;charset=UTF-8") (
6
7public Pet getPet( String petId) {
8 // ...
9}
提示:
MediaType
常量类中提供了常用的Content-Type常量,例如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE。
SpringMVC可对请求映射进行精确控制,主要有两种方式:
通过重写RequestMappingHandlerMapping的getCustomMethodCondition方法,返回自己的RequestCondition。
通过Java方式注册处理器映射:
101
2public class MyConfig {
3
4 public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserController controller) throws NoSuchMethodException {
5 RequestMappingInfo info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build();
6 Method method = UserController.class.getMethod("getUser", Long.class);
7 mapping.registerMapping(info, controller, method);
8 }
9}
10
默认情况下,前端不能访问服务器的静态资源(html/css/js/img等),需要对其进行映射配置。
141<!--静态资源映射(方式一)-->
2<!-- location 表示路径,mapping 表示文件,**表示该目录下的文件以及子目录的文件 -->
3<mvc:resources location="/html/" mapping="/html/**"/>
4<mvc:resources location="/css/" mapping="/css/**"/>
5<mvc:resources location="/js/" mapping="/js/**"/>
6<mvc:resources location="/img/" mapping="/img/**"/>
7<mvc:resources location="/public, classpath:/static/" mapping="/resources/**" cache-period="31556926" />
8
9<!--静态资源映射(方式二)(注意开启MVC注解驱动)-->
10<!--<mvc:default-servlet-handler/>-->
11
12<!--开启注解驱动支持-->
13<mvc:annotation-driven></mvc:annotation-driven>
14
提示:如需生成“版本化”的URL,可考虑配置VersionResourceResolver。
控制器方法常用的参数类型如下:
Servlet相关对象:HttpServletRequest、MultipartHttpServletRequest、HttpSession、InputStream、Reader、OutputStream、Writer、PushBuilder。
Spring内部对象:RequestEntity<T>、WebRequest、NativeWebRequest、SessionStatus、HttpMethod、Errors、BindingResult、UriComponentsBuilder。
域数据共享对象:Map、Model、ModelMap、RedirectAttributes。
其它对象支持:Locale、TimeZone+ZoneId、Principal。
任何其他参数:将会根据类型转换器进行属性映射。
参数常用的注解如下:
用于获取参数:@RequestParam、@RequestHeader、@CookieValue、@PathVariable、@MatrixVariable、@RequestBody(读取请求正文并通过HttpMessageConverter反序列化为Object)、@RequestPart。
用于数据共享:@ModelAttribute、@SessionAttribute、@RequestAttribute。
将HttpServletRequest
作为控制器方法的形参,框架在调用时会自动注入为当前请求对象,通过该对象即可获取参数和Cookie等。
81"servlet") (
2public String testServletApiParams(HttpServletRequest request) {
3 // 获取请求参数、请求头和Cookie等
4 System.out.println(request.getParameter("username"));
5 System.out.println(request.getParameter("password"));
6
7 return "success";
8}
提示:可在控制器方法中注入的对象有:HttpServletRequest、HttpServletResponse、HttpSession、java.security.Principal、Locale、InputStream、OutputStream、Reader、Writer等。
在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参。
141
2"params") (
3public class RequestParamsController {
4
5 "simple") (
6 public String testParamsBySimple(String username, String password) {
7 System.out.println(username);
8 System.out.println(password);
9
10 return "success";
11 }
12
13}
14
注意:
若有多个同名的请求参数,可以设置形参为字符串数组或者字符串类型。
若使用字符串数组作为形参,则映射为多个数组元素。
若使用字符串作为形参,则映射为一个逗号拼接的字符串。
使用Map作为参数,它将接收所有未被其它方式映射的参数。
可以在控制器方法的形参位置设置一个POJO类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。
271public class Account implements Serializable {
2 private String name;
3 private Float money;
4}
5
6public class Address implements Serializable {
7 private String provinceName;
8 private String cityName;
9}
10
11public class User implements Serializable {
12 private String username;
13 private String password;
14 private Integer age;
15 private Address address;
16 private List<Account> accountList;
17 private Map<String, Account> accountMap;
18}
19
20// 控制器方法,带有User形参
21"pojo") (
22public String testParamsByPojo(User user) {
23 System.out.println(user);
24
25 return "success";
26}
27
相应的HTML页面如下:
311
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Index</title>
6</head>
7<body>
8<h1>测试复杂POJO参数映射</h1>
9<form action="/params/pojo" method="post">
10 <!--简单属性映射-->
11 用户名称: <input type="text" name="username"><br/>
12 用户密码: <input type="password" name="password"><br/>
13 用户年龄: <input type="text" name="age"><br/>
14 <!--嵌套属性映射-->
15 用户省份: <input type="text" name="address.provinceName"><br/>
16 用户城市: <input type="text" name="address.cityName"><br/>
17 <!--List属性映射-->
18 账户1 名称: <input type="text" name="accountList[0].name"><br/>
19 账户1 金额: <input type="text" name="accountList[0].money"><br/>
20 账户2 名称: <input type="text" name="accountList[1].name"><br/>
21 账户2 金额: <input type="text" name="accountList[1].money"><br/>
22 <!--Map属性映射-->
23 账户3 名称: <input type="text" name="accountMap['one'].name"><br/>
24 账户3 金额: <input type="text" name="accountMap['one'].money"><br/>
25 账户4 名称: <input type="text" name="accountMap['two'].name"><br/>
26 账户4 金额: <input type="text" name="accountMap['two'].money"><br/>
27 <input type="submit" value="提交">
28</form>
29</body>
30</html>
31
@RequestParam注解用来指定请求参数和控制器方法形参的映射关系。该注解一共有三个属性:
value:请求参数的参数名。
required:参数是否必传,默认值为true。如果未传且无默认值则报"400:Required String parameter 'xxx' is not present"错误。
defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为""时,则使用默认值为形参赋值。
101"requestParam") (
2public String testParamsByRequestParam(
3 value = "user_name", required = true) String username, (
4 value = "pass_word", required = false, defaultValue = "123456") String password (
5) {
6 System.out.println(username);
7 System.out.println(password);
8
9 return "success";
10}
注意:如果使用
java.util.Optional
作为方法参数,并且与@RequestParam和@RequestHeader等具有required属性的注解结合使用,该注解等效于required=false。
@CookieValue
注解用来指定Cookie数据和控制器方法形参的映射关系,@RequestHeader
注解用来指定请求头信息与控制器方法形参的映射关系,其常用属性及使用方法和@RequestParam注解类似。
101"requestHeaderAndCookie") (
2public String testParamsByRequestHeader(
3 value = "Origin", required = true) String origin, (
4 value = "time-out", required = false, defaultValue = "60s") String timeOut (
5) {
6 System.out.println(origin);
7 System.out.println(timeOut);
8
9 return "success";
10}
路径变量指在请求路径中通过{xxx}占位符声明的变量,常用于RESTful风格的请求中,可通过@PathVariable
注解进行绑定。
111// pathVariable?user_name=zhangsan&pass_word=123456 -> pathVariable/zhangsan/123456
2"pathVariable/{user_name}/{pass_word}") (
3public String testParamsByPathVariable(
4 value = "user_name", required = true) String username, (
5 value = "pass_word", required = false) String password (
6) {
7 System.out.println(username);
8 System.out.println(password);
9
10 return "success";
11}
特别的,路径变量还可以使用正则表达式来进行路径匹配。
61// ==> /spring-web-3.0.5.jar
2"/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") (
3public void handle( String version, String ext) {
4 // ...
5}
6
矩阵变量(Matrix Variable)以;
开头,多个值之间用逗号隔开,SpringMVC中可通过设置<mvc:annotation-driven enable-matrix-variables="true"/>
或removeSemicolonContent=false
来开启矩阵变量功能。
231// 矩阵变量以;开头,多个值之间用逗号隔开
2/cars;color=red,green;year=2012
3/cars;color=red;color=green;color=blue
4
5// 对比路径变量和矩阵变量 GET /pets/42;q=11;r=22
6"/pets/{petId}") (
7public void findPet( String petId, int q) {
8 // petId:42,q:11
9}
10
11// 多个矩阵变量 GET /owners/42;q=11/pets/21;q=22
12"/owners/{ownerId}/pets/{petId}") (
13public void findPet( (name="q", pathVar="ownerId") int q1, (name="q", pathVar="petId") int q2) {
14 // q1:11,q:22
15}
16
17// 通过MultiValueMap获取所有的矩阵变量 GET /owners/42;q=11;r=12/pets/21;q=22;s=23
18"/owners/{ownerId}/pets/{petId}") (
19public void findPet( MultiValueMap<String, String> matrixVars, (pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
20 // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
21 // petMatrixVars: ["q" : 22, "s" : 23]
22}
23
@RequestBody注解用来将请求体内容绑定到控制器方法的形参。请求体的格式默认为&
拼接的字符串,无需消息转换器,可直接使用String类型形参接收。
111
2"message") (
3public class MessageContoller {
4
5 "req") (
6 public String reqConvert( String body) {
7 System.out.println(body);
8 return "success";
9 }
10}
11
如下页面,发送POST请求时,打印请求体的值:name=zhangsan&money=123
。
191
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>测试HTTP消息转换</title>
6</head>
7<body>
8<a href="/message/req?name=zhangsan&money=123">请求消息转换(Get)</a>
9<hr/>
10
11<form action="/message/req" method="post">
12 账户名称: <input type="text" name="name"><br/>
13 账户金额: <input type="number" name="money"><br/>
14 <input type="submit" value="请求消息转换(Post)">
15</form>
16
17</body>
18</html>
19
注意:如果请求体不存在(如GET请求方式)或形参类型不匹配(非String类型),则响应400错误。
如果请求体为JSON/XML
等特殊字符串格式,除了使用String类型形参接收外,还可以通过HttpMessageConverter绑定到相应的实体类。
首先引入Jackson的依赖如下:
61<dependency>
2 <groupId>com.fasterxml.jackson.core</groupId>
3 <artifactId>jackson-databind</artifactId>
4 <version>2.12.1</version>
5</dependency>
6
然后通过开启MVC注解驱动来自动配置MappingJackson2HttpMessageConverter
。
11<mvc:annotation-driven />
则可以控制器方法如下:
61"req2") (
2public String reqConvert2( Account account) {
3 System.out.println(account);
4 return "success";
5}
6
使用Postman发送一个POST请求如下,请求体格式为JSON字符串,形参为Account类型,实现自动绑定。
注意:如果请求体格式为
&
拼接的字符串,则消息转换器不能将其绑定到POJO类,需要使用前面所述的请求参数绑定方式。
扩展:使用Jquery来发送POST请求示例如下:
171<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
2<script type="text/javascript">
3$(function () {
4$("#testJson").click(function () {
5$.ajax({
6type: "post",
7url: "${pageContext.request.contextPath}/testResponseJson",
8contentType: "application/json;charset=utf-8",
9data: '{"id":1,"name":"test","money":999.0}',
10dataType: "json",
11success: function (data) {
12alert(data);
13}
14});
15});
16})
17</script>
RequestEntity<T>类可作为控制器方法的形参,封装请求头和转换后的请求体,方便后续使用。
191"req2") (
2public String reqConvert2(RequestEntity<String> req) {
3 System.out.println(req);
4 System.out.println(req.getMethod());
5 System.out.println(req.getUrl());
6 System.out.println(req.getHeaders());
7 System.out.println(req.getBody());
8 return "success";
9}
10
11"req4") (
12public String reqConvert4(RequestEntity<Account> req) {
13 System.out.println(req.getMethod());
14 System.out.println(req.getUrl());
15 System.out.println(req.getHeaders());
16 System.out.println(req.getBody());
17 return "success";
18}
19
前端通过POST方式请求http://localhost:8080/message/req2,打印信息如下:
41POST
2http://localhost:8080/message/req2
3[host:"localhost:8080", connection:"keep-alive", content-length:"18", cache-control:"max-age=0", sec-ch-ua:""Chromium";v="104", " Not A;Brand";v="99", "Microsoft Edge";v="104"", sec-ch-ua-mobile:"?0", sec-ch-ua-platform:""Windows"", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.81 Safari/537.36 Edg/104.0.1293.54", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", sec-fetch-site:"same-origin", sec-fetch-mode:"navigate", sec-fetch-user:"?1", sec-fetch-dest:"document", referer:"http://localhost:8080/message_test", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
4name=zhangsan&money=123
注意:使用RequestEntity作为形参时可以没有请求体,封装后的请求体数据为null。
如果POST请求参数出现乱码,可在web.xml
中配置SpringMVC提供的编码过滤器CharacterEncodingFilter
。
181<!--配置springMVC的编码过滤器,指定编码为UTF-8-->
2<filter>
3 <filter-name>CharacterEncodingFilter</filter-name>
4 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
5 <init-param>
6 <param-name>encoding</param-name>
7 <param-value>UTF-8</param-value>
8 </init-param>
9 <init-param>
10 <param-name>forceResponseEncoding</param-name>
11 <param-value>true</param-value>
12 </init-param>
13</filter>
14<filter-mapping>
15 <filter-name>CharacterEncodingFilter</filter-name>
16 <url-pattern>/*</url-pattern>
17</filter-mapping>
18
注意:CharacterEncodingFilter一定要配置到其他过滤器之前,否则可能无效!
Tomcat8.0之前GET请求也可能会出现乱码问题,需修改Tomcat的server.xml
配置文件useBodyEncodingForURI属性为true。
11<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" useBodyEncodingForURI="true" />
如果遇到Ajax请求仍然乱码,则把useBodyEncodingForURI="true"改为URIEncoding="UTF-8"。
若类路径存在Hibernate Validator或类似实现,则在全局注册LocalValidatorFactoryBean
,以支持@Vaild
注解和Validated
接口。
你可以通过如下方式来配置全局Validator实例:
101
2
3public class WebConfig implements WebMvcConfigurer {
4
5
6 public Validator getValidator(); {
7 // ...
8 }
9}
10
也可注册自己的Validator实例:
91
2public class MyController {
3
4
5 protected void initBinder(WebDataBinder binder) {
6 binder.addValidators(new FooValidator());
7 }
8
9}
类似的XML配置如下:
21<mvc:annotation-driven validator="globalValidator"/>
2
将HttpServletRequest
作为控制器方法的形参,框架在调用时会自动注入为当前请求对象,通过该对象即可在Request域共享数据等。
101
2"share") (
3public class DataShareController {
4 "api") (
5 public String testDataShareByServletAPI(HttpServletRequest request) {
6 request.setAttribute("username", "request_zhangsan");
7 return "success";
8 }
9}
10
61"model") (
2public String testDataShareByModel(Model model) {
3 model.addAttribute("username", "request_zhangsan");
4 return "success";
5}
6
121"map") (
2public String testMap(Map<String, Object> map) {
3 map.put("username", "request_zhangsan");
4 return "success";
5}
6
7"modelMap") (
8public String testMap(ModelMap modelMap) {
9 modelMap.put("username", "request_zhangsan");
10 return "success";
11}
12
ModelAndView有Model和View的功能,Model主要用于向请求域共享数据,View主要用于设置视图,实现页面跳转。
101"mv") (
2public ModelAndView testDataShareByMv() {
3 ModelAndView mv = new ModelAndView();
4 // 共享数据
5 mv.addObject("username", "request_zhangsan");
6 // 页面跳转(默认转发)
7 mv.setViewName("success");
8 return mv;
9}
10
@ModelAttribute注解用来进行Request域数据共享,可以作用于普通方法或控制器方法参数之上。
当作用于普通方法时,则该方法会在此Controller的每个方法执行前被执行,如果有返回值,则自动将该返回值加入到ModelMap中。
161
2public class Hello2ModelController {
3
4
5 public User populateModel() {
6 User user=new User();
7 user.setAccount("ray");
8 return user;
9 }
10
11 value = "/helloWorld2") (
12 public String helloWorld() {
13 return "helloWorld.jsp";
14 }
15}
16
当作用于控制器方法参数时,自动将该参数值添加到ModelMap中。
71"/testModelAttribute") (
2public String testModelAttribute( ("newUser")User user,Model model){
3 System.out.println("testModelAttribute User:"+user);
4 System.out.println("testModelAttribute model:"+model.toString());
5 return "success";
6}
7
@RequestAttribute属性可以从Request域获取数据并映射到控制器方法参数。
41"/") (
2public String handle( Client client) {
3 // ...
4}
将HttpSession
作为控制器方法的形参,框架在调用时会自动注入为当前会话对象,通过该对象即可在Session域共享数据等。
61"api2") (
2public String testDataShareByServletAPI(HttpSession session) {
3 session.setAttribute("username", "session_zhangsan");
4 return "success";
5}
6
@SessionAttributes注解可作用于控制器所在类上,其从Request域查找配置的属性,并共享到Session域。
111
2"sc") (
3"name") (
4public class SessionController {
5 "session") (
6 public String sessions(Model model,HttpSession session){
7 model.addAttribute("name", "winclpt");
8 session.setAttribute("myName", "chke");
9 return "session";
10}
11
@SessionAttribute注解可从Session域获取数据并映射到控制器方法参数。
41"/") (
2public String handle( User user) {
3 // ...
4}
获取会话对象(或请求对象)后,通过该对象可获取ServletContext
对象,即可在Application域共享数据等。
61"api3") (
2public String testDataShareByServletAPI3(HttpSession session) {
3 session.getServletContext().setAttribute("username", "servletContext_zhangsan");
4 return "success";
5}
6
控制器方法常用的返回值类型如下:
响应视图和模型:ResponseEntity<T>、HttpHeaders、String、View、Map、Model、ModelAndView、void
异步响应类型:DeferredResult
任何其他返回值:如果是String,将会被当作逻辑视图名称解析,其它类型将会被反序列化后进行响应。
返回值相关的注解如下:
@ResponseBody:将返回值通过HttpMessageConverter序列化到响应主体。
@ModelAttribute:将返回值共享到请求域。
将HttpServletRequest
或HttpServletResponse
作为控制器方法的形参,框架在调用时会自动注入为当前请求对象或响应对象,此时可以进行请求转发、重定向或直接发送响应数据等。
201// 请求转发 -> /WEB-INF/pages/success.jsp
2"api2") (
3public void testServletApi2(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
4 request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
5}
6
7// 重定向 -> http://baidu.com
8"api3") (
9public void testServletApi3(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
10 response.sendRedirect("http://baidu.com");
11}
12
13// 直接发送响应数据 -> "这是一个响应数据!"
14"api") (
15public void testServletApi(HttpServletResponse response) throws IOException {
16 response.setCharacterEncoding("utf-8");
17 response.setContentType("application/json;charset=utf-8");
18 response.getWriter().write("这是一个响应数据!");
19}
20
提示:可在控制器方法中注入的对象还有:HttpServletRequest、HttpServletResponse、HttpSession、java.security.Principal、Locale、InputStream、OutputStream、Reader、Writer等。
当控制器方法设置的视图名称以forward:
为前缀时,创建InternalResourceView
视图,然后直接通过:
后面的路径进行转发,而不经过视图解析器拼接视图前后缀等过程。
61// 请求转发 -> /WEB-INF/pages/success.jsp
2"/forward") (
3public String forward() {
4 return "forward:/WEB-INF/pages/success.jsp";
5}
6
注意:
使用Forward进行转发时,写转发路径而非视图名称,相当于 request.getRequestDispatcher("url").forward(request,response)。
转发路径以
/
开头时,则是基于Servlet上下文的绝对路径,否则是相对于当前请求的相对路径。可以转发到其它的控制器方法或JSP视图页面,但不能够转发到其它服务器。
当工程引入了JSTL模板库的依赖时,转发视图会自动转换为
JstlView
。
当控制器方法设置的视图名称以redirect:
为前缀时,创建RedirectView
视图,然后直接通过:
后面的路径进行重定向,也不经过视图解析器拼接视图前后缀等过程。
61// 重定向 -> http://baidu.com
2"/redirect") (
3public String redirect() {
4 return "redirect:http://baidu.com";
5}
6
注意:
使用Redirect进行重定向时,写新路径而非视图名称,相当于response.sendRedirect(url)。
同样的,路径以
/
开头时,则是基于Servlet上下文的绝对路径,否则是相对于当前请求的相对路径。可以重定向到任何可访问URL资源,但WEB-INF目录下的资源除外。
重定向时默认传递所有的Model属性,可以通过RequestMappingHandlerAdapter的ignoreDefaultModelOnRedirect标志来控制。
如果属性为String类型,则直接拼接在URL之后。如果非String类型,则通过RedirectAttributes的实现类RedirectAttributesModelMap
进行传递。RedirectAttributesModelMap有一个flashAttributes属性,在重定向之前(通常在会话中)被临时保存,在重定向之后可供请求使用。
在每个请求上,都有一个具有从上一个请求(如果有)传递的属性的输入FlashMap,和一个具有为后续请求保存的属性的输出FlashMap,通过RequestContextUtils
中的getInputFlashMap/getOutputFlashMap方法,可以从Spring MVC中的任何位置访问这两个FlashMap实例。
当控制器方法设置的视图名称没有任何前缀时,则由配置的视图解析器按优先级(order属性配置)进行解析,它会将视图名称拼接前后缀,然后以转发的方式实现跳转。
在控制器方法结束时,可通过返回String
或ModelAndView
的形式设置视图名称。
131// String
2"/view") (
3public String view() {
4 return "success";
5}
6
7// ModelAndView
8"/mv") (
9public ModelAndView mv() {
10 ModelAndView mv = new ModelAndView();
11 mv.setViewName("success");
12 return mv;
13}
这里也可以直接返回视图的绝对路径,如/WEB-INF/pages/success.jsp,但一般会结合视图解析器使用,只需返回视图名称即可。
SpringMVC提供了一个默认视图解析器InternalResourceViewResolver
可用于解析JSP视图。
111<!-- 默认视图解析器,可用于处理jsp等视图 -->
2<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
3 <property name="order" value="1"/>
4 <property name="prefix" value="/WEB-INF/pages/"/>
5 <property name="suffix" value=".jsp"/>
6</bean>
7
8<!-- 简化方式配置(了解) -->
9<mvc:view-resolvers>
10 <mvc:jsp/>
11</mvc:view-resolvers>
JSP视图的简单示例如下,文件路径为src\main\webapp\WEB-INF\pages\success.jsp。
161<%page contentType="text/html;charset=UTF-8" language="java" %>
2
3<html lang="en">
4<head>
5 <meta charset="UTF-8">
6 <title>Success</title>
7</head>
8<body>
9<h1>Success!</h1>
10
11request.username: <%=request.getAttribute("username")%>
12session.username: <%=session.getAttribute("username")%>
13application.username: <%=application.getAttribute("username")%>
14
15</body>
16</html>
首先需要引入Thymeleaf视图的相关依赖。
61<!-- Spring5和Thymeleaf整合包 -->
2<dependency>
3 <groupId>org.thymeleaf</groupId>
4 <artifactId>thymeleaf-spring5</artifactId>
5 <version>3.0.12.RELEASE</version>
6</dependency>
然后在spring-mvc-config.xml
文件中配置Thymeleaf视图解析器,并设置视图前缀、视图后缀和优先级等关键属性。
211<!-- 配置Thymeleaf视图解析器 -->
2<bean id="thymeleafViewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
3 <!-- 解析优先级 -->
4 <property name="order" value="0"/>
5 <property name="characterEncoding" value="UTF-8"/>
6 <property name="templateEngine">
7 <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
8 <property name="templateResolver">
9 <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
10 <!-- 视图前缀 -->
11 <property name="prefix" value="/WEB-INF/templates/"/>
12 <!-- 视图后缀 -->
13 <property name="suffix" value=".html"/>
14 <property name="templateMode" value="HTML5"/>
15 <property name="characterEncoding" value="UTF-8"/>
16 </bean>
17 </property>
18 </bean>
19 </property>
20</bean>
21
Thymeleaf视图的简单示例如下,文件路径为src\main\webapp\WEB-INF\templates\success.html。
181
2<html lang="en" xmlns:th="http://www.thymeleaf.org">
3<head>
4 <meta charset="UTF-8">
5 <title>Success</title>
6</head>
7<body>
8
9<a th:href="@{/}"><h1>首页</h1></a>
10
11request.username: <span th:text="${#httpServletRequest.getAttribute('username')}"></span>
12<br>
13session.username: <span th:text="${session.username}"></span>
14<br>
15application.username: <span th:text="${application.username}"></span>
16
17</body>
18</html>
首先也是引入FreeMarker视图的相关依赖。
111<!-- Spring5和freemarker整合包 -->
2<dependency>
3 <groupId>org.freemarker</groupId>
4 <artifactId>freemarker</artifactId>
5 <version>2.3.31</version>
6</dependency>
7<dependency>
8 <groupId>org.springframework</groupId>
9 <artifactId>spring-context-support</artifactId>
10 <version>5.3.15</version>
11</dependency>
然后在spring-mvc-config.xml
文件中配置FreeMarker视图解析器和相关配置类,并设置视图前缀、视图后缀和优先级等关键属性。
521<!-- 注册freemarker视图解析器 -->
2<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
3 <!-- 视图解析顺序,排在其他视图解析器之后 数字越大优先级越低 -->
4 <property name="order" value="2"/>
5 <!-- 开启模版缓存 -->
6 <property name="cache" value="true"/>
7 <!-- freeMarkerConfigurer已经配了,这里就不用配啦 -->
8 <property name="prefix" value=""/>
9 <!-- 配置文件后缀 -->
10 <property name="suffix" value=".ftl"/>
11 <property name="contentType" value="text/html;charset=UTF-8"/>
12 <!-- 是否允许session属性覆盖模型数据,默认false -->
13 <property name="allowSessionOverride" value="false"/>
14 <!-- 是否允许request属性覆盖模型数据,默认false -->
15 <property name="allowRequestOverride" value="false"/>
16 <!-- 开启spring提供的宏帮助(macro) -->
17 <property name="exposeSpringMacroHelpers" value="true"/>
18 <!-- 添加request attributes属性到ModelAndView中 -->
19 <property name="exposeRequestAttributes" value="true"/>
20 <!-- 添加session attributes属性到ModelAndView中 -->
21 <property name="exposeSessionAttributes" value="true"/>
22</bean>
23
24<!-- 注册freemarker配置类 -->
25<bean id="freeMarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
26 <!-- ftl模版文件路径 -->
27 <property name="templateLoaderPath" value="/WEB-INF/templates/"/>
28 <!-- 页面编码 -->
29 <property name="defaultEncoding" value="utf-8"/>
30 <property name="freemarkerSettings">
31 <props>
32 <!-- 模版缓存刷新时间,不写单位默认为秒 -->
33 <prop key="template_update_delay">0</prop>
34 <!-- 时区 和 时间格式化 -->
35 <prop key="locale">zh_CN</prop>
36 <prop key="datetime_format">yyyy-MM-dd</prop>
37 <prop key="date_format">yyyy-MM-dd</prop>
38 <!-- 数字使用.来分隔 -->
39 <prop key="number_format">#.##</prop>
40 </props>
41 </property>
42</bean>
43
44
45<!-- 简化配置方式(了解) -->
46<mvc:view-resolvers>
47 <mvc:freemarker cache="false"/>
48</mvc:view-resolvers>
49<mvc:freemarker-configurer>
50 <mvc:template-loader-path location="/WEB-INF/freemarker"/>
51</mvc:freemarker-configurer>
52
FreeMarker视图的简单示例如下,文件路径为src\main\webapp\WEB-INF\templates\success.ftl。
151
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Success</title>
6</head>
7<body>
8
9<a href="/"><h1>首页</h1></a>
10
11<!--注意:如果域中不存在username属性会报错!-->
12username: ${username}
13
14</body>
15</html>
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller
标签进行表示。
61<!--
2 path:设置处理的请求地址
3 view-name:设置请求地址所对应的视图名称
4-->
5<mvc:view-controller path="/testView1" view-name="myView1"></mvc:view-controller>
6<mvc:view-controller path="/testView2" view-name="myView2"></mvc:view-controller>
注意:当存在view-controller时,控制器方法中的请求映射将全部失效,此时需开启MVC注解驱动:
<mvc:annotation-driven/>
。
@ResponseBody注解用来将返回值作为为响应体返回,但由于响应体一般为字符串类型,因此默认只能转换String类型的返回值。
61"res") (
2
3public String resConvert() {
4 return "this is string.";
5}
6
提示:
@ResponseBody注解也可以配置在类上,等效于类中的所有控制器方法都加上该注解。
SpringMVC提供了一个@ResponseBody和@Controller的组合注解
@RestController
,简化注解使用。
为了能够转换其它类型的返回值作为响应体,需要引入JSON/XML依赖,并配置相关的HttpMessageConverter。
引入Jackson的依赖如下:
61<dependency>
2 <groupId>com.fasterxml.jackson.core</groupId>
3 <artifactId>jackson-databind</artifactId>
4 <version>2.12.1</version>
5</dependency>
6
并通过开启MVC注解驱动来自动配置MappingJackson2HttpMessageConverter
。
11<mvc:annotation-driven />
然后就可以响应不同类型的实体数据了。
191// ==> http://localhost:8080/message/res2
2// <== 666
3"res2") (
4
5public Integer resConvert2() {
6 return 666;
7}
8
9// ==> http://localhost:8080/message/res3
10// <== {"name":"zhangsan","money":123.4}
11"res3") (
12
13public Account resConvert3() {
14 Account account = new Account();
15 account.setName("zhangsan");
16 account.setMoney(123.4F);
17 return account;
18}
19
ResponseEntity可作为控制器方法的返回值类型,它可以设置响应头和响应体信息,并经消息转换器转换后响应给浏览器。
文件上传要求FROM表单的请求方式必须为POST,并且添加属性enctype="multipart/form-data"
。
161
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>文件上传测试</title>
6</head>
7<body>
8
9<form action="/file/up" method="post" enctype="multipart/form-data">
10 <input type="file" name="multipartFile"/>
11 <input type="submit" value="上传">
12</form>
13
14</body>
15</html>
16
注意:文件输入框的name属性值要求和控制器方法中MultipartFile形参名一致。
首先添加commons-fileupload依赖如下:
61<dependency>
2 <groupId>commons-fileupload</groupId>
3 <artifactId>commons-fileupload</artifactId>
4 <version>1.3.1</version>
5</dependency>
6
然后在SpringMVC的配置文件中添加multipartResolver
配置。
81<!-- 配置文件解析器:用于解析文件为MultipartFile对象(注意:BeanId不能修改) -->
2<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
3 <!-- 设置上传文件的最大尺寸为5MB -->
4 <property name="maxUploadSize">
5 <value>5242880</value>
6 </property>
7</bean>
8
编写控制器代码如下,SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息。
361
2"file") (
3public class FileController {
4
5 "up") (
6 public String up(MultipartFile multipartFile, HttpSession session) throws IOException {
7 // 获取上传的文件名
8 String fileName = multipartFile.getOriginalFilename();
9 if (fileName == null) {
10 return "error";
11 }
12
13 // 生成UUID作为文件名
14 int suffixIndex = fileName.lastIndexOf(".");
15 String uuidFileName = UUID.randomUUID().toString() + (suffixIndex != -1 ? fileName.substring(suffixIndex) : "");
16
17 // 获取服务器中upload目录的路径
18 ServletContext servletContext = session.getServletContext();
19 String path = servletContext.getRealPath("upload");
20 File file = new File(path);
21 if (!file.exists()) {
22 file.mkdirs();
23 }
24
25 // 组成最终的文件名
26 String finalFilename = path + File.separator + uuidFileName;
27 System.out.println("file-up: " + finalFilename);
28
29 //上传
30 multipartFile.transferTo(new File(finalFilename));
31
32 return "success";
33 }
34
35}
36
注意:
可以使用
List<MultipartFile>
接收多个同名文件,或使用Map<String, MultipartFile>
或MultiValueMap<String, MultipartFile>
映射多个文件。MultipartFile可以不直接作为方法的参数,放在POJO类参数的某个属性。
文件下载关键是设置一个Content-Disposition
请求头,以附件的形式进行下载。
271"/down") (
2public ResponseEntity<byte[]> down( String fileName, HttpSession session) throws IOException {
3 ResponseEntity<byte[]> responseEntity;
4
5 //获取真实路径
6 // ServletContext servletContext = session.getServletContext();
7 // String realPath = servletContext.getRealPath("down" + File.separator + fileName);
8 String realPath = "D:/Download" + File.separator + fileName;
9
10 // 读文件
11 byte[] bytes;
12 try (InputStream is = new FileInputStream(realPath)) {
13 bytes = new byte[is.available()];
14 is.read(bytes);
15 }
16
17 // 设置响应头
18 MultiValueMap<String, String> headers = new HttpHeaders();
19 headers.add("Content-Disposition", "attachment;filename=" + fileName); // 以附件形式下载
20 HttpStatus statusCode = HttpStatus.OK;
21
22 // 封装ResponseEntity
23 responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
24
25 return responseEntity;
26}
27
SpringMVC通过组件化配置来支持不同的功能,主要的一些组件如下:
DispatcherServlet:前端控制器,统一接收客户端(浏览器)的请求,并根据请求URL转发给 Controller 中的方法。
Handler:控制器处理器,即标注了 @RequestMapping 注解的方法,负责处理客户端发送的请求,并响应视图/JSON 数据。
HandlerMapping:处理器映射器,匹配查找能处理请求的Handler,并组合拦截器,返回一个HandlerExecutionchain对象。
RequestMappingHandlerMapping:根据@RequestMapping注解的配置信息进行映射。
SimpleUrlHandlerMapping:根据URI进行简单映射,不常用。
HandlerAdapter:处理器适配器,它的作用是执行上面封装好的 HandlerExecutionchain。
HandlerExceptionResolver:解决异常的策略,可以将异常映射到到控制器方法、错误视图或其他目标。
ViewResolver:视图解析器,根据 ModelAndview 中存放的视图名称进行实际的视图渲染。
LocaleResolver和LocaleContextResolver:解析Client端正在使用的Locale以及可能的时区,以便能够提供国际化的视图。
ThemeResolve:解决Web应用程序可以使用的主题,例如提供个性化的布局。
MultipartResolver:借助其它解析库来解析Multipart请求,如实现文件上传。
FlashMapManager:存储和检索输入和输出FlashMap,实现将属性从一个请求传递到另一个请求,通常跨重定向。
下面是MVC组件的一些内置实现,详情可以参考DispatcherServlet.properties:
271# Default implementation classes for DispatcherServlet's strategy interfaces.
2# Used as fallback when no matching beans are found in the DispatcherServlet context.
3# Not meant to be customized by application developers.
4
5org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
6
7org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
8
9org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
10 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
11 org.springframework.web.servlet.function.support.RouterFunctionMapping
12
13org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
14 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
15 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
16 org.springframework.web.servlet.function.support.HandlerFunctionAdapter
17
18
19org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
20 org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
21 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
22
23org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
24
25org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
26
27org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
SpringMVC定义了View
和ViewResolver
接口,用于解绑控制器与特定的视图技术。
View:解决了在移交给特定视图技术之前的数据准备问题。
ViewResolver:提供了视图名称和实际视图之间的映射。
SpringMVC对一些场景内置了ViewResolver,如下:
ContentNegotiatingViewResolver:本身不会解析视图,只根据Accept请求头或"format=pdf"请求参数等信息委派给其他视图解析器。
InternalResourceView:
RedirectView:
91
2
3public class WebConfig implements WebMvcConfigurer {
4
5
6 public void configureViewResolvers(ViewResolverRegistry registry) {
7 registry.enableContentNegotiation(new MappingJackson2JsonView());
8 }
9}
类似的XML配置如下:
71<mvc:view-resolvers>
2 <mvc:content-negotiation>
3 <mvc:default-views>
4 <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
5 </mvc:default-views>
6 </mvc:content-negotiation>
7</mvc:view-resolvers>
请参考“响应视图页面”章节!
拦截器用于拦截Controller方法的执行,需要实现HandlerInterceptor
接口,并根据情况来实现其抽象方法。
2810) (
2public class LogInterceptor implements HandlerInterceptor {
3 /**
4 * 在控制器方法执行之前执行,返回值表示是否放行。
5 */
6
7 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
8 System.out.println("LogInterceptor->preHandle(): " + request.getServletPath());
9 return true;
10 }
11
12 /**
13 * 控制器方法执行之后执行。
14 */
15
16 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
17 System.out.println("LogInterceptor->postHandle(): " + request.getServletPath());
18 }
19
20 /**
21 * 处理完视图和模型数据,渲染视图完毕之后执行。
22 */
23
24 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
25 System.out.println("LogInterceptor->afterCompletion(): " + request.getServletPath());
26 }
27}
28
注意:
拦截器只能拦截控制器方法,不能拦截其它请求,如静态资源请求。
特别的,如果使用@ResponseBody和ResponseEntity,则在控制器方法返回前已将响应写入和提交,因此不能再通过postHandle来处理响应,如"添加额外的响应头"等操作。(不过,你可以通过ResponseBodyAdvice或RequestMappingHandlerAdapter解决!)
151<mvc:interceptors>
2 <!--配置拦截器(/**所有控制器请求 /*表示只有一层路径的控制器请求)-->
3 <mvc:interceptor>
4 <mvc:mapping path="/**"/>
5 <mvc:exclude-mapping path="/"/>
6 <bean class="com.huangyuanxin.notes.springmvc.interceptor.LogInterceptor"></bean>
7 </mvc:interceptor>
8
9 <!--简写方式一:直接创建Bean,默认拦截所有控制器请求-->
10 <!--<bean id="logInterceptor" class="com.huangyuanxin.notes.springmvc.interceptor.LogInterceptor"></bean>-->
11
12 <!--简写方式二:直接引用外部Bean,也是默认拦截所有控制器请求-->
13 <!--<ref bean="logInterceptor"></ref>-->
14</mvc:interceptors>
15
如果配置了多个拦截器,则按如下原则来执行拦截器方法:
按配置顺序执行preHandle(),反顺序执行postHandle()和afterComplation(),配置顺序可通过@Order
注解或Order
接口调整。
如果中途某个拦截器的preHandle()返回了false,则直接跳转到上一个拦截器的afterComplation(),其它未执行的拦截器,包括控制器方法都将会被跳过。
如果在请求映射或处理期间引发了异常,则交给HandlerExceptionResolver
链来处理,通常是返回一个友好的错误响应。
SpringMVC对一些场景内置了HandlerExceptionResolver,如下:
SimpleMappingExceptionResolver:用于声明异常类名称与错误页面之间的映射。
111<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
2 <property name="exceptionMappings">
3 <props>
4 <!-- properties的键表示处理器方法执行过程中出现的异常
5 properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面 -->
6 <prop key="java.lang.ArithmeticException">error</prop>
7 </props>
8 </property>
9 <!-- exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享 -->
10 <property name="exceptionAttribute" value="ex"></property>
11</bean>
DefaultHandlerExceptionResolver:用于处理SpringMVC引发的异常,并将异常映射到HTTP状态码。
ResponseStatusExceptionResolver:在使用@ResponseStatus注解处理异常的场景,根据注解中的值将其映射到 HTTP 状态码。
ExceptionHandlerExceptionResolver: 通过调用@Controller或@ControllerAdvice类中的@ExceptionHandler方法来解决异常。
如果当前HandlerExceptionResolver无法处理引发的异常,请返回null值,交给下一个HandlerExceptionResolver进行处理。
如果所有的HandlerExceptionResolver都无法处理异常,则映射到Servlet容器的默认错误页面。错误页面可通过下面方式配置。
31<error-page>
2 <location>/error</location>
3</error-page>
自定义CustomRuntimeException
异常和CustomExceptionResolver
如下。
311
2public class CustomRuntimeException extends RuntimeException {
3 public CustomRuntimeException(String s) {
4 super(s);
5 }
6}
7
8public class CustomExceptionResolver implements HandlerExceptionResolver {
9
10
11 public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
12 // 打印异常信息
13 ex.printStackTrace();
14
15 // 转换为自定义异常
16 CustomRuntimeException exception = null;
17 if (ex instanceof CustomRuntimeException) {
18 exception = (CustomRuntimeException) ex;
19 } else {
20 exception = new CustomRuntimeException(ex.getMessage());
21 }
22
23 // 构建ModelAndView
24 ModelAndView modelAndView = new ModelAndView();
25 modelAndView.addObject("ex", exception);
26 modelAndView.setViewName("error");
27
28 return modelAndView;
29 }
30}
31
31<!-- 配置自定义异常处理器 -->
2<bean id="handlerExceptionResolver" class="com.huangyuanxin.notes.springmvc.resolver.CustomExceptionResolver"/>
3
如果控制器方法的参数既不是HttpServletRquest等特殊对象,也不是String类型,则在映射请求参数时,一般需要进行相应的类型转换。
如将前端传过来的String类型值映射到Date类型的形参。
51"date") (
2public String deleteAccount(Date date) {
3 System.out.println(date);
4 return "success";
5}
org.springframework.format.Formatter
用于处理String类型到任意类型的转换。首先定义一个Formatter如下:
151public class MyFormatter implements Formatter<Date> {
2
3 public Date parse(String text, Locale locale) throws ParseException {
4 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
5 Date date = simpleDateFormat.parse(text);
6 System.out.println("转换了:"+date);
7 return date;
8 }
9
10
11 public String print(Date object, Locale locale) {
12 return null;
13 }
14}
15
然后在spring-mvc-config.xml
文件中进行配置。
261<!-- 配置类型转换服务 -->
2<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
3 <!-- 配置Formatter -->
4 <property name="formatters">
5 <set>
6 <bean class="org.example.MyFormatter"/>
7 <bean class="org.example.MyAnnotationFormatterFactory"/>
8 </set>
9 </property>
10 <!-- 配置FormatterRegistrar -->
11 <property name="formatterRegistrars">
12 <set>
13 <bean class="org.example.MyFormatterRegistrar"/>
14 </set>
15 </property>
16 <!-- 配置Converter -->
17 <property name="converters">
18 <set>
19 <bean class="org.example.MyConverter"/>
20 </set>
21 </property>
22</bean>
23
24<!--开启注解驱动支持-->
25<mvc:annotation-driven conversion-service="conversionService"/>
26
注意:
FormattingConversionServiceFactoryBean也可以配置其它类型的转换器。
默认情况下,安装了Number和Date类型的格式化程序,包括对@NumberFormat和@DateTimeFormat注解的支持。如果 Classpath 中存在Joda-Time,则还将安装对Joda-Time格式库的完全支持。
org.springframework.core.convert.converter.Converter
用来处理任意类型之间的映射。首先定义一个Converter如下:
181public class StringToDateConverter implements Converter<String, Date> {
2
3 private DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
4
5
6 public Date convert(String source) {
7 if (source != null && source.trim().length() > 0) {
8 try {
9 return format.parse(source);
10 } catch (Exception e) {
11 throw new RuntimeException("日期转换错误");
12 }
13 }
14
15 return null;
16 }
17}
18
然后在spring-mvc-config.xml
文件中进行配置:
151<!-- 配置类型转换服务 -->
2<bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
3 <!-- 给工厂注入一个新的类型转换器 -->
4 <property name="converters">
5 <array>
6 <!-- 配置自定义类型转换器 -->
7 <bean class="com.huangyuanxin.notes.springmvc.converter.StringToDateConverter"></bean>
8 </array>
9 </property>
10</bean>
11
12<!--开启注解驱动支持-->
13<mvc:annotation-driven conversion-service="converterService"></mvc:annotation-driven>
14
15
java.beans.PropertyEditor
也可以用来进行属性转换,使用方式请参考“@InitBinder”相关章节。
MultipartResolver是一种用于解析Multipart请求(如文件上传)的策略,当收到Content Type为multipart/form-data的POST请求时,将会把HttpServletRequest包装为MultipartHttpServletRequest
。
SpringMVC提供了两种实现方式,一种是基于Commons FileUpload的实现,另一种基于Servlet 3.0 Multipart请求解析。
首先导入commons-fileupload
依赖,然后配置CommonsMultipartResolver
类,Bean名称固定为multipartResolver。
如何使用Multipart解析器,请参考“文件上传”章节。
默认情况下,Servlet 3.0 Multipart功能为关闭状态,可通过web.xml的<multipart-config>
标签或如下Java配置进行开启。
111public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
2
3
4 protected void customizeRegistration(ServletRegistration.Dynamic registration) {
5
6 // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
7 registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
8 }
9
10}
11
同样的,需要配置Bean名称为multipartResolver的StandardServletMultipartResolver
。
在Servlet3.0环境中,容器会查找javax.servlet.ServletContainerInitializer
接口的实现类,并用它来初始化Servlet容器。而Spring的SpringServletContainerInitializer
类实现了该接口,将初始化逻辑委托给WebApplicationInitializer
的实现类。
171public class MyWebApplicationInitializer implements WebApplicationInitializer {
2
3
4 public void onStartup(ServletContext servletCxt) {
5
6 // Load Spring web application configuration
7 AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
8 ac.register(AppConfig.class);
9 ac.refresh();
10
11 // Create and register the DispatcherServlet
12 DispatcherServlet servlet = new DispatcherServlet(ac);
13 ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
14 registration.setLoadOnStartup(1);
15 registration.addMapping("/app/*");
16 }
17}
为了继续简化Servlet容器的初始化过程,Spring又定义了AbstractAnnotationConfigDispatcherServletInitializer
抽象类。
461public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
2 /**
3 * 指定SpringMVC的配置类
4 *
5 * @return
6 */
7
8 protected Class<?>[] getServletConfigClasses() {
9 return new Class[]{WebConfig.class};
10 }
11
12 /**
13 * 指定Spring的配置类
14 *
15 * @return
16 */
17
18 protected Class<?>[] getRootConfigClasses() {
19 return new Class[]{SpringConfig.class};
20 }
21
22 /**
23 * 指定DispatcherServlet的映射规则,即url-pattern
24 *
25 * @return
26 */
27
28 protected String[] getServletMappings() {
29 return new String[]{"/"};
30 }
31
32 /**
33 * 添加过滤器
34 *
35 * @return
36 */
37
38 protected Filter[] getServletFilters() {
39 CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
40 encodingFilter.setEncoding("UTF-8");
41 encodingFilter.setForceRequestEncoding(true);
42 HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
43 return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
44 }
45}
46
提示:如果您需要进一步自定义DispatcherServlet本身,则可以覆盖createDispatcherServlet方法。
1101
2"com.huangyuanxin.notes.springmvc") //扫描组件 (
3//开启MVC配置(等效于 <mvc:annotation-driven/> )
4public class WebConfig implements WebMvcConfigurer {
5
6 // 开启DefaultServletHandler支持(可用于处理静态资源等)
7
8 public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
9 // 开启DefaultServletHandler支持(参数可自定义默认Servlet名称)
10 configurer.enable();
11 }
12
13 /* 配置静态资源映射
14 @Override
15 public void addResourceHandlers(ResourceHandlerRegistry registry) {
16 registry.addResourceHandler("/resources/**")
17 .addResourceLocations("/public", "classpath:/static/")
18 .setCachePeriod(31556926);
19 }
20 */
21
22 //配置文件上传解析器
23
24 public CommonsMultipartResolver multipartResolver() {
25 return new CommonsMultipartResolver();
26 }
27
28 //配置属性转换器
29
30 public void addFormatters(FormatterRegistry registry) {
31 registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
32 }
33
34 //配置拦截器
35
36 public void addInterceptors(InterceptorRegistry registry) {
37 LogInterceptor logInterceptor = new LogInterceptor();
38 registry.addInterceptor(logInterceptor).addPathPatterns("/**");
39 }
40
41 // 配置视图控制
42
43 public void addViewControllers(ViewControllerRegistry registry) {
44 registry.addViewController("/").setViewName("index");
45 }
46
47 // 配置异常映射
48
49 public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
50 SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
51 Properties prop = new Properties();
52 prop.setProperty("java.lang.ArithmeticException", "error");
53 //设置异常映射
54 exceptionResolver.setExceptionMappings(prop);
55 //设置共享异常信息的键
56 exceptionResolver.setExceptionAttribute("ex");
57 resolvers.add(exceptionResolver);
58 }
59
60 // 配置Thymeleaf模板解析器
61
62 public ITemplateResolver templateResolver() {
63 WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
64 // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
65 ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
66 webApplicationContext.getServletContext());
67 templateResolver.setPrefix("/WEB-INF/templates/");
68 templateResolver.setSuffix(".html");
69 templateResolver.setCharacterEncoding("UTF-8");
70 templateResolver.setTemplateMode(TemplateMode.HTML);
71 templateResolver.setOrder(0);
72 return templateResolver;
73 }
74
75 // 配置Thymeleaf模板引擎
76
77 public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
78 SpringTemplateEngine templateEngine = new SpringTemplateEngine();
79 templateEngine.setTemplateResolver(templateResolver);
80 return templateEngine;
81 }
82
83 // 配置Thymeleaf视图解析器
84
85 public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
86 ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
87 viewResolver.setCharacterEncoding("UTF-8");
88 viewResolver.setTemplateEngine(templateEngine);
89 return viewResolver;
90 }
91
92 // 配置其它视图解析器
93
94 public void configureViewResolvers(ViewResolverRegistry registry) {
95 // 配置FreeMarker视图解析器
96 registry.freeMarker().cache(false);
97 // 配置JSP视图解析器
98 registry.jsp();
99 }
100
101 // FreeMarker视图解析器属性配置
102
103 public FreeMarkerConfigurer freeMarkerConfigurer() {
104 FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
105 configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
106 return configurer;
107 }
108
109}
110
51
2public class SpringConfig {
3 //ssm整合之后,spring的配置信息写在此类中
4}
5
一切配置就绪后,就可以使用之前的方式来编写控制器了。
171
2public class TestController {
3
4 "test") (
5 public ModelAndView test(HttpSession session) {
6 ModelAndView mav = new ModelAndView();
7
8 mav.addObject("username", "zhangsan(request)");
9 session.setAttribute("username", "zhangsan(session)");
10 session.getServletContext().setAttribute("username", "zhangsan(application)");
11
12 mav.setViewName("success");
13
14 return mav;
15 }
16}
17
@ControllerAdvice(@RestControllerAdvice)是针对多个控制器的切面,一般用于配置类型转换器和异常处理器等。
@InitBinder标注的方法用来初始化WebDataBinder
实例,该类实例用于多种场景下的属性转换。
181
2public class FormController{
3
4 // 注册PropertyEditor
5
6 public void initBinder01(WebDataBinder binder) {
7 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
8 dateFormat.setLenient(false);
9 binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
10 }
11
12 // 添加Formatter
13
14 protected void initBinder02(WebDataBinder binder) {
15 binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
16 }
17}
18
注意:
如果@InitBinder方法在@ControllerAdvice中定义,默认对所有控制器生效。若在@Controller中定义,则只对当前控制器生效。
@Controller中定义的@InitBinder和@ModelAttribute方法将会在@ControllerAdvice中定义的相应方法之后被调用。
@Controller中定义的@ExceptionHandler方法将会在@ControllerAdvice中定义的相应方法之前被调用。
@ControllerAdvice是控制器方法的切面,配合@ExceptionHandler
注解可以定义异常处理通知。
151
2public class ExceptionController {
3 /**
4 * 异常拦截通知
5 *
6 * @ExceptionHandler 用于设置所拦截的异常
7 * ex 表示当前请求的异常对象
8 */
9 ArithmeticException.class) (
10 public String handleArithmeticException(Exception ex, Model model) {
11 model.addAttribute("ex", ex);
12 return "error";
13 }
14}
15
提示:
尽量通过注解的value属性或方法参数指定更精确的异常类型。
异常处理方法参数支持HandlerMethod、ServletRequest、ServletResponse、HttpSession等大多数在控制器所支持的参数。
返回值也可以是@ResponseBody、ResponseEntity<B>、String、void、ModelAndView等大多数在控制器所支持的返回值。
该注解不常用,使用方式请参考“域数据共享”章节。
HttpMessageConverter用于处理@RequestBody和@ResponseBody注解,对HTTP消息进行序列化和反序列化。
你可以通过覆盖configureMessageConverters()或extendMessageConverters()来自定义HttpMessageConverter。
141
2
3public class WebConfiguration implements WebMvcConfigurer {
4
5
6 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
7 Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
8 .indentOutput(true)
9 .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
10 .modulesToInstall(new ParameterNamesModule());
11 converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
12 converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
13 }
14}
类似的XML配置如下:
181<mvc:annotation-driven>
2 <mvc:message-converters>
3 <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
4 <property name="objectMapper" ref="objectMapper"/>
5 </bean>
6 <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
7 <property name="objectMapper" ref="xmlMapper"/>
8 </bean>
9 </mvc:message-converters>
10</mvc:annotation-driven>
11
12<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
13 p:indentOutput="true"
14 p:simpleDateFormat="yyyy-MM-dd"
15 p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
16
17<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
18
SpringMVC根据如下流程来确定请求的Midea类型:
首先检查请求路径的扩展名,如.json、.xml、.do等,根据扩展名去配置中查找Content Type。
查询参数format?
如果未找到,则读取Accept请求头中的Content Type(推荐)。
扩展名和Content Type的对应关系可通过configureContentNegotiation来配置:
101
2
3public class WebConfig implements WebMvcConfigurer {
4
5
6 public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
7 configurer.mediaType("json", MediaType.APPLICATION_JSON);
8 configurer.mediaType("xml", MediaType.APPLICATION_XML);
9 }
10}
类似的XML配置如下:
101<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
2
3<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
4 <property name="mediaTypes">
5 <value>
6 json=application/json
7 xml=application/xml
8 </value>
9 </property>
10</bean>
您可以自定义路径匹配和URL处理有关的选项。
281
2
3public class WebConfig implements WebMvcConfigurer {
4
5
6 public void configurePathMatch(PathMatchConfigurer configurer) {
7 configurer
8 .setUseSuffixPatternMatch(true)
9 .setUseTrailingSlashMatch(false)
10 .setUseRegisteredSuffixPatternMatch(true)
11 .setPathMatcher(antPathMatcher())
12 .setUrlPathHelper(urlPathHelper())
13 .addPathPrefix("/api",
14 HandlerTypePredicate.forAnnotation(RestController.class));
15 }
16
17
18 public UrlPathHelper urlPathHelper() {
19 //...
20 }
21
22
23 public PathMatcher antPathMatcher() {
24 //...
25 }
26
27}
28
类似的XML配置如下:
111<mvc:annotation-driven>
2 <mvc:path-matching
3 suffix-pattern="true"
4 trailing-slash="false"
5 registered-suffixes-only="true"
6 path-helper="pathHelper"
7 path-matcher="pathMatcher"/>
8</mvc:annotation-driven>
9
10<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
11<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
跨域资源共享(CORS)可以解决Ajax只能同源(协议+域名+端口)使用的限制,使用SpringMVC可以非常方便的进行配置。
@CrossOrigin注解用于开启单个控制器的跨域请求支持,如以下示例所示:
181maxAge = 3600) (
2
3"/account") (
4public class AccountController {
5
6 "http://domain2.com") (
7 "/{id}") (
8 public Account retrieve( Long id) {
9 // ...
10 }
11
12 "/{id}") (
13 public void remove( Long id) {
14 // ...
15 }
16}
17
18
默认情况下,@CrossOrigin允许所有的来源、所有的请求头和所有HTTP请求方式,不启用allowedCredentials,并设置maxAge为30分钟。
默认情况下,全局配置允许所有的来源、所有的请求头和GET、HEAD、POST请求方式,不启用allowedCredentials,maxAge为30分钟。
181
2
3public class WebConfig implements WebMvcConfigurer {
4
5
6 public void addCorsMappings(CorsRegistry registry) {
7
8 registry.addMapping("/api/**")
9 .allowedOrigins("http://domain2.com")
10 .allowedMethods("PUT", "DELETE")
11 .allowedHeaders("header1", "header2", "header3")
12 .exposedHeaders("header1", "header2")
13 .allowCredentials(true).maxAge(3600);
14
15 // Add more mappings...
16 }
17}
18
类似的XML配置如下:
141<mvc:cors>
2
3 <mvc:mapping path="/api/**"
4 allowed-origins="http://domain1.com, http://domain2.com"
5 allowed-methods="GET, PUT"
6 allowed-headers="header1, header2, header3"
7 exposed-headers="header1, header2" allow-credentials="true"
8 max-age="123" />
9
10 <mvc:mapping path="/resources/**"
11 allowed-origins="http://domain1.com" />
12
13</mvc:cors>
14
151CorsConfiguration config = new CorsConfiguration();
2
3// Possibly...
4// config.applyPermitDefaultValues()
5
6config.setAllowCredentials(true);
7config.addAllowedOrigin("http://domain1.com");
8config.addAllowedHeader("*");
9config.addAllowedMethod("*");
10
11UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
12source.registerCorsConfiguration("/**", config);
13
14CorsFilter filter = new CorsFilter(source);
15
DelegatingWebMvcConfiguration为SpringMVC提供了一些默认配置,你可以删除@EnableWebMvc并直接从DelegatingWebMvcConfiguration扩展,而不是实现WebMvcConfigurer。
41
2public class WebConfig extends DelegatingWebMvcConfiguration {
3 // ...
4}
UriBuilderFactory和UriBuilder一起提供了一种可插入的机制,基于共享配置(基本URL、编码首选项和其他详细信息)从URI 模板构建URI。
71String baseUrl = "http://example.com";
2DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
3
4URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
5 .queryParam("q", "{q}")
6 .build("Westin", "123");
7
UriComponentsBuilder是UriBuilder的实现类,用于构建复杂的URL请求路径。
71URI uri = UriComponentsBuilder
2 .fromUriString("http://example.com/hotels/{hotel}")
3 .queryParam("q", "{q}")
4 .encode()
5 .buildAndExpand("Westin", "123")
6 .toUri();
7
ServletUriComponentsBuilder也是UriBuilder的实现类,用于构建相对于当前请求的URI。
161HttpServletRequest request = ...
2
3// Re-uses host, scheme, port, path and query string...
4ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
5 .replaceQueryParam("accountId", "{id}").build()
6 .expand("123")
7 .encode();
8
9// Re-uses host, port and context path...
10ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
11 .path("/accounts").build()
12
13// Re-uses host, port, context path, and Servlet prefix...
14ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
15 .path("/accounts").build()
16
MvcUriComponentsBuilder可以按控制器方法名称来构建URI连接。
51UriComponents uriComponents = MvcUriComponentsBuilder
2 .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
3
4URI uri = uriComponents.encode().toUri();
5
SpringMVC内置了一些过滤器,用来处理一些特殊场景:
FormContentFilter:拦截Content Type为application/x-www-form-urlencoded的PUT、PATCH、DELETE 请求,从请求的正文中读取表单数据,并包装ServletRequest以通过ServletRequest.getParameter()*系列方法使表单数据可用。
ForwardedHeaderFilter:当请求通过代理(例如负载平衡器)进行处理时,主机,端口和方案可能会更改,该过滤器根据Forwarded请求头修改请求的主机,端口和方案,然后删除这些请求头。出于安全考虑,你可以通过removeOnly=true仅删除请求头而不使用它。
ShallowEtagHeaderFilter:用于支持ETag(如果响应内容的哈希值与请求发过来的哈希值一致,则响应304,可节省带宽)。
CorsFilter:用于支持CROS,并适用于Spring Security。
对控制器进行动态代理时推荐使用基于子类的动态代理。
主要注意的是,如果继承了非Spring上下文感知接口(例如InitializingBean,*Aware等)的其它接口,则需要手动指定代理方式。
1<!--配置事务通知通过基于子类的代理方式-->
2<tx:annotation-driven proxy-target-class="true"/>
DEBUG和TRACE日志记录可能会记录敏感信息,因此默认情况下屏蔽请求参数和请求头,开启方式如下:
x1public class MyInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
2
3
4 protected void customizeRegistration(Dynamic registration) {
5 registration.setInitParameter("enableLoggingRequestDetails", "true");
6 }
7
8}
9
SpringMVC与Servlet 3.0异步请求处理进行了广泛的集成:
控制器方法中的DeferredResult和Callable返回值,并为单个异步返回值提供基本支持。
控制器可以stream多个值,包括SSE和raw data。
控制器可以使用反应式Client 端,并返回reactive类型进行响应处理。
更多信息请参考官方文档!
HTTP缓存可显著提高Web应用程序的性能。 HTTP缓存围绕Cache-Control响应头以及随后的条件请求头(例如Last-Modified和ETag)展开。
Cache-Control为私有(例如浏览器)和公共(例如代理)缓存提供有关如何缓存和重用响应的建议。
ETag请求头用于发出条件请求,如果内容未更改,则可能导致没有主体的304(NOT_MODIFIED)。 ETag可以看作是Last-ModifiedHeaders 的更复杂的后继者。