Controller控制器

SpringMVC是基于Servlet规范封装的框架。我们使用SpringMVC框架时,不需要再编写Servlet相关的代码,只需要定义Controller并注册到Spring容器即可。

一个Controller对应一组请求,每一个Controller方法对应一个具体的HTTP请求。当用户的浏览器对某一资源发起请求后,DispatcherServlet首先收到请求,然后就会从Spring容器中获取并调用Controller组件,最后匹配对应的方法进行具体处理。流程如下图所示:

图片来自网络。

Controller配置

SpringMVC中,我们一般都是配置包扫描(参考前一篇笔记配置),并用注解标注在类上,来定义Controller:

  • @Controller:定义一个控制器
  • @RestController:定义一个控制器,和上面区别是它相当于给每个请求处理方法标注了@ResponseBody,返回的内容会自动以JSON序列化并输出

例子:

@Controller
public class IndexController {
    // ... 具体Controller类逻辑
}

另外一点要注意的是,SpringMVC的Controller默认为单例模式。

除此之外,SpringMVC也支持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值为参数名,requiredtrue

除了URL参数,SpringMVC也支持路径参数,例子如下:

@GetMapping(value = "/demo/{id}")
public String demo(@PathVariable Integer id) {
    // ... 具体业务逻辑
}

路径参数需要在@RequestMapping注解中用{}标出,并与方法参数名对应,方法参数需要使用@PathVariable注解标注。

接收表单请求

POST请求的表单格式常用的有application/x-www-form-urlencodedmultipart/form-data两种,在SpringMVC中处理方式没有区别。我们只需要指定一个JavaBean接收:

@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或XML请求体

AJAX请求一般使用JSON或XML格式请求体,和接收表单请求类似,JSON请求体也可以直接使用一个JavaBean来接收,SpringMVC能够集成Jackson库解析请求,但要注意的是参数上需要标注@RequestBody

如下配置集成JSON自动解析到SpringMVC:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="mappingJacksonHttpMessageConverter"/>
        </list>
    </property>
</bean>
<bean id="mappingJacksonHttpMessageConverter"
        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <property name="supportedMediaTypes">
        <list>
            <value>application/json;charset=UTF-8</value>
        </list>
    </property>
</bean>

具体代码中,我们直接使用JavaBean接收请求对象即可:

@PostMapping(value = "/demo")
public String demo(@RequestBody @Valid LoginFormBean loginFormBean, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        // ... 表单参数错误处理逻辑
    }
    // ... 具体业务逻辑
}

同样的,参数也支持@Valid验证注解。

除了JSON格式,SpringMVC配合JAXB注解也支持对XML请求体的自动解析,用法与上面类似,这里就不多介绍了。

注:该功能需要引入对应Jackson或JAXB依赖。

接收文件上传请求

文件上传比较常用的方式为multipart/form-data表单格式,文件对应的表单字段则是一个较大的二进制数据,一般都通过数据流读取。SpringMVC内置了Apache的commons-fileupload组件,能够很方便的处理上传请求。

这里要注意,我们需要为文件上传配置multipartResolver

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

处理文件上传的具体代码比较简单,直接通过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对象获取,这个对象我们都比较熟悉了,它就是ServletAPI中的请求对象:

@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或XML数据

之前解析请求JSON时我们介绍过Jackson库相关的配置,配置好后同样支持响应JSON的生成。

如果响应JSON数据,我们需要在Controller类上标注@RestController或在方法上标注@ResponseBody,比较推荐的用法是前者。

@RestController
@RequestMapping(value = "index")
public class IndexController {

    @GetMapping(value = "/demo")
    public ApiResult demo() {
        return ApiResult.success();
    }
}

XML也是类似的,这里就不多介绍了。

响应二进制数据

响应二进制数据一般用于文件下载的场景,文件下载通常要和Content-DipositionContent-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相关章节。

响应模板视图

SpringMVC支持多种模板引擎,比如常用的JSP、Thymeleaf、Velocity等。响应模板视图需要我们配置ViewResolver,下面是Thymeleaf的例子:

<!--thymeleaf template-->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine"/>
    <property name="characterEncoding" value="UTF-8"/>
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver"/>
</bean>
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/template/"/>
    <property name="suffix" value=".html"/>
    <property name="templateMode" value="HTML"/>
    <property name="characterEncoding" value="UTF-8"/>
    <!--<property name="cacheable" value="false" />-->
</bean>

返回模板视图非常简单,控制器方法直接返回模板路径即可:

@Controller
@RequestMapping(value = "index")
public class IndexController {

    @GetMapping(value = "/demo")
    public String demo(Model model) {
        // 向模板传递数据
        model.addAttribute("username", "Alice");
        // 响应模板webapp/template/index.html
        return "index";
    }
}

注意代码中,我们依赖注入了一个Model,它的用法类似一个Map,用来向模板引擎传递数据。

模板中直接取用该数据即可:

<!doctype html>
<html lang="zh-cmn-Hans" 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.controller;

import com.gacfox.demo.bean.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切面也支持全局数据预处理、全局数据绑定处理,不过正常使用基本不会用到,这里就不多介绍了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap