Controller控制器
SpringMVC是基于JavaEE Servlet规范封装的Web表现层框架。我们使用SpringMVC框架时,不需要再编写Servlet相关的代码,只需要定义Controller(控制器)并注册到Spring容器即可。实际开发中,通常每个Controller对应一组业务功能相关的请求,每个Controller方法对应一个具体的HTTP请求。当用户的浏览器对资源发起请求后,DispatcherServlet首先收到请求,然后就会从Spring容器中获取并调用Controller组件,最后匹配对应的方法进行具体处理,整个流程如下图所示。

定义Controller
SpringMVC中,我们需要使用注解标注来声明一个类是控制器。
@Controller:定义一个控制器,常用于传统MVC开发模式
@RestController:定义一个控制器,和上面区别是它相当于给每个请求处理方法标注了@ResponseBody,返回的内容会自动以JSON序列化并输出,常用于现代前后端分离开发模式,服务端只提供JSON接口的情况
默认情况下,SpringMVC的控制器是单例的,因此你不能用类成员变量记录请求或会话作用域的信息。
下面是控制器类写法的例子。
@Controller
public class IndexController {
// ... 具体Controller类逻辑
}
除了使用注解定义Controller,SpringMVC也支持在Spring配置文件中使用XML方式声明控制器类,但这种方式太过麻烦早已经不再使用,后文将不再涉及此种方式。
请求处理
请求路径匹配
Controller中,通过@RequestMapping注解匹配请求对应的控制器和方法。该注解可以标注在控制器类和方法上,下面是一个例子。
@Controller
@RequestMapping(value = "/index")
public class IndexController {
@RequestMapping(value = "/demo", method = RequestMethod.GET)
public String demo() {
// ... 具体业务逻辑
}
}
value:匹配的路径,支持数组匹配多个路径method:HTTP请求方法,如果不指定,那么代表GET、POST均支持,建议写明
如上述写法,用户请求/index/demo路径时,SpringMVC就会自动匹配并获取IndexController这个Bean,并调用demo方法。
除了@RequestMapping,SpringMVC还提供了@GetMapping、@PostMapping等注解,相当于已经指定好了method,实际开发中推荐使用后者。
接收URL参数
Controller接收基本GET请求参数非常简单,例如请求路径为GET /demo?id=1,直接在控制器方法上声明该参数,SpringMVC会自动进行参数注入。
@GetMapping(value = "/demo")
public String demo(@RequestParam(value = "id", required = false) Integer id) {
// ... 具体业务逻辑
}
value:请求参数名required:是否为必须参数,如果必须参数未传,SpringMVC会返回HTTP 400错误
代码中@RequestParam注解不是必须的,默认value值为参数名,required为true。
除了URL参数,SpringMVC也支持路径参数,例子如下。
@GetMapping(value = "/demo/{id}")
public String demo(@PathVariable Integer id) {
// ... 具体业务逻辑
}
路径参数需要在@RequestMapping注解中用{}标出,并与方法参数名对应,方法参数需要使用@PathVariable注解标注。
接收表单请求
POST请求的表单格式常用的有application/x-www-form-urlencoded、multipart/form-data两种,在SpringMVC中处理方式没有区别。我们只需要指定一个DTO类来接收。
@PostMapping(value = "/demo")
public String demo(LoginFormBean loginFormBean) {
// ... 具体业务逻辑
}
SpringMVC也对JSR303验证注解进行了集成,我们如果需要验证请求表单的参数,直接使用相关注解即可。
@PostMapping(value = "/demo")
public String demo(@Valid LoginFormBean loginFormBean, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// ... 表单参数错误处理逻辑
}
// ... 具体业务逻辑
}
代码中,LoginFormBean内部标注了相关验证注解,方法参数上标注了@Valid,这样请求调用控制器方法前,SpringMVC会对参数进行验证,如果有错误则自动注入bindingResult参数,我们对其判断即可。
注:验证功能需要引入相关Maven依赖。
接收JSON请求体
AJAX请求一般使用JSON格式请求体,和接收表单请求类似,JSON请求体也可以直接使用一个DTO类来接收。对于SpringBoot工程,实际上已默认集成了Jackson库解析JSON请求,但要注意的是参数上需要标注@RequestBody,下面是一个例子。
@PostMapping(value = "/demo")
public String demo(@RequestBody @Valid LoginFormBean loginFormBean, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// ... 表单参数错误处理逻辑
}
// ... 具体业务逻辑
}
同样的,参数也支持@Valid验证注解。
接收文件上传请求
文件上传比较常用的方式为multipart/form-data表单格式,文件对应的表单字段则是一个较大的二进制数据,一般都通过数据流读取。SpringMVC内置了Apache的commons-fileupload组件,能够很方便的处理上传请求。处理文件上传的具体代码比较简单,直接通过MultipartFile类型的参数接收即可。
@PostMapping(value = "/demo")
public String demo(@RequestParam(value = "file") MultipartFile file) {
// ... 具体业务逻辑
}
获取HTTP请求头
SpringMVC中获取请求头有两种方式,比较简洁的方法是使用@RequestHeader注解,另一种方式是从自动注入的HttpServletRequest对象中获取。
@RequestHeader注解能够直接注入请求头作为参数,下面例子代码中,我们从请求头中获取了名为token的参数。
@GetMapping(value = "/demo")
public String demo(@RequestHeader(value = "token") String token) {
// ... 具体业务逻辑
}
@RequestHeader也具有required属性,默认为true,如果不满足会返回HTTP 400错误。
另一种方式是通过注入HttpServletRequest对象获取,这个对象我们都比较熟悉了,它就是Servlet API中的请求对象。
@GetMapping(value = "/demo")
public String demo(HttpServletRequest request) {
// ... 具体业务逻辑
String token = request.getHeader("token");
}
获取Cookie
Cookie和请求头处理类似,也支持两种方式,通过@CookieValue注解获取,或从注入的HttpServletRequest对象中获取。
@GetMapping(value = "/demo")
public String demo(@CookieValue(value = "token") String token) {
// ... 具体业务逻辑
}
代码比较简单,这里就不赘述了。
响应处理
响应JSON数据
对于响应JSON数据,我们需要在Controller类上标注@RestController或在方法上标注@ResponseBody,比较推荐的用法是前者。
@RestController
@RequestMapping(value = "index")
public class IndexController {
@GetMapping(value = "/demo")
public ApiResult demo() {
return ApiResult.success();
}
}
响应二进制数据
响应二进制数据一般用于文件下载的场景,文件下载通常要和Content-Diposition、Content-Type响应头配合使用,下面是一个例子。
@RestController
@RequestMapping(value = "index")
public class IndexController {
@GetMapping(value = "/demo")
public ResponseEntity<byte[]> demo() {
// 模拟响应的二进制数据
String str = "Hello, world!";
byte[] data = str.getBytes();
// 响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", "1.txt");
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 返回响应
return new ResponseEntity<>(data, headers, HttpStatus.OK);
}
}
SpringMVC对响应体进行了一个封装,即ResponseEntity对象,我们对其指定输出的二进制数据、响应头和相应状态码,就能实现任意数据的响应了。
除了使用ResponseEntity对象,我们也可以直接使用依赖注入的HttpServletResponse对象,获取其输出流并写入数据。这种方式在老版本SpringMVC工程中使用也比较多,具体可以参考Servlet相关章节。
响应模板视图
对于SpringBoot工程,模板引擎一般使用Thymeleaf,这需要引入相关的起步依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
返回模板视图非常简单,控制器方法直接返回模板路径即可。
@Controller
@RequestMapping(value = "index")
public class IndexController {
@GetMapping(value = "/demo")
public String demo(Model model) {
// 向模板传递数据
model.addAttribute("username", "Alice");
// 响应模板在resources/templates/index.html
return "index";
}
}
注意代码中,我们在方法参数中注入了一个Model对象,它的用法类似一个Map,用来向模板引擎传递数据。
模板中直接取用该数据即可。
<!doctype html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Demo</title>
</head>
<body>
<div th:text="${username}"></div>
</body>
</html>
有关模板引擎的详细使用方法将在其他章节详细介绍。
注:使用后端模板引擎输出HTML页面已经属于过时的技术了(尤其大型项目常用的Java领域),现在主流的前后端分离开发中,前端工程会独立出去并由静态文件服务器单独提供,而后端只负责输出接口数据。
ControllerAdvice控制器切面
在Spring核心功能相关章节中我们学习过AOP的概念,实际上SpringMVC有一个地方迫切需要AOP切面功能,就是统一异常处理。
考虑这样的场景:我们的代码抛出运行时异常,但此时我们不希望SpringMVC展示出五花八门的错误页面,我们希望返回一个全局通用的错误信息,此时就可以用AOP切面统一拦截控制器方法的执行。下面代码我们实现返回一个通用错误JSON信息。
package com.gacfox.demo.config;
import com.gacfox.demo.model.ApiResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public ApiResult<?> handleException() {
return ApiResult.failure();
}
}
如果按照正常访问流程走到的控制器方法抛出异常,那么该切面就会生效,并返回通用错误JSON信息。
除了全局错误信息拦截,@ControllerAdvice切面也支持全局数据预处理、全局数据绑定处理,不过正常使用基本不会用到,这里就不多介绍了。