Spring Boot:(二)MVC
Contents
Spring Boot 可以使用 Spring MVC 处理 HTTP 请求。
配置文件
Spring 的 @Value
值注入方式不再赘述,参考Spring 环境抽象 。这里注重介绍的是 Spring boot 的多环境配置。
都知道通过 Spring 的 @Profile("development/production")
可以选择根据 spring.profiles.active/default
配置来是否决定加载该 bean而在 Spring boot 中可以通过这个配置来加载差异化的后补配置,后补配置文件名格式 application-{profile}.properties
其中 {profile}
对应着配置项的名称,常用的如:
application-dev.properties
:开发环境application-test.properties
:测试环境application-prod.properties
:生产环境
例如需要启用 application-dev.properties
里的配置,在 appliction.properties
文件里可以这样设置:
|
|
注意的是附加的配置项如有和 appliction.properties
里的冲突,会覆盖。
Handler Methods
Spring boot 的 Web 这一部分可以使用 Spring MVC 或者 WebFlux,以下着重讲解 Spring MVC 在 Spring 下的使用。
首先构建一个 POJO model User 用于后续的使用。
|
|
Request Mapping Handler Methods
通过 Spring MVC Request Mapping 的方式可以将请求的 URL 和处理该请求的 handler 进行匹配,通常会通过注解的方式来实现。
@RequestMapping
用于创建 URL 和 handler 的映射,可以同时用于 class 和 method 上,这时候实际的 method handler 的 URL 就需要加上 class 上的 RequestMapping value 作为 前缀。
@RequestMapping
的默认 value 为 URL 可以,处理固定的字符串还可以使用占位符;method 值为 HTTP 方法,如实现 user 的 RESTful 接口:
|
|
RequestMapping 的 value 默认为
/
method 默认所有方法,支持**
多级、*
单级、及?
单字符的 Ant 风格通配符。除了 RequestMapping method 指定方法,还可以使用
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
等快捷方式。除了 path 参数,还可以使用
consumes
指定请求中的 Media Types 来进一步的细分如@PostMapping(path = "/pets", consumes = "application/json")
,这里对应的是 HTTP 协议 request 中的Accept
头。范围更广的,可以进一步使用
headers
进一步根据请求头来细分 Mapping,如GetMapping(path = "/pets", headers = "myHeader=myValue")
。RequestMapping 还可以通过 params 参数进行细分(其实 method 也是个细分的项)如:
1 2 3 4 5
@RequestMapping(value="/login", params="flag") // 请求中必须含有有名为 flag 的键 @RequestMapping(value="/login", params="!flag") // 请求中不能含有要有名为 flag 的键 @RequestMapping(value="/login", params="flag=hello") // 请求中名为 flag 的键的值必须为 hello @RequestMapping(value="/login", params="flag!=hello") // 请求中名为 flag 的键的值不能为 hello @RequestMapping(value="/login", params={"flag1","flag2=hello"}) // 请求中必须含有 flag1 的键,同时名为 flag2 键的值必须是 hello
可以对一个方法指定多个 url 匹配方式,这个情况下需要考虑值没有注入的的情况,如果需要考虑值的合法性需要:
|
|
上述的 @Nullable
也可以转换为 @PathVariable(required = false)
获取 Request 中的数据
所有获取 request 中的数据方式大体可以参考 Method Arguments 表格。
URI Path 中的数据
Mapping 值里可以使用路径占位符,使用
@PathVariable
会根据名字来着注入到方法的对应参数里,可以包含多个路径占位符,方法参数里也是可以获取到类注解里声明的路径占位符的。Mapping 里还可以使用
${}
的方式使用配置值Mapping 值里还可以使用下面的通配符,即 ANT 风格
?
一个字符*
零个字符或者一段路径(指的是 / / 之间的)**
零个字符或者多个路径段
Mapping 还可以使用
{varName:regx}
的方式使用正则来匹配值PathVariable 可以加入 name(即 value) 参数这样方法参数名就不用和占位符一致
|
|
键值数据
@RequestParam
可以通过注解函数参数来获取 Get 方法的 QueryString 键值对,该注解也可解析 Request Body 中的 form-data 和 x-www-form-urlencoded 的 Content-type 键值数据,Http中数据键名默认是和注解的参数形成对应,也可以通过 @RequestParam("paramName")
指定名称。
@RequestParam
能转换的数据有限(一些简单的类型),复杂数据参见 数据转换和验证章节。另外值得注意的是有个特殊用法,如果注解的是 Map<String, String>
或者 MultiValueMap<String, String>
类型的参数,且不指定 name 参数的话,所有的键值参数将会被放入 Map,如:
|
|
特别的对应 multipart/form-data
形式的含有文件的请求可使用 MultipartFile 来处理:
|
|
如果在请求中含有多个同名的键,可以使用 (@RequestParam List<String> list)
的形式来获取而不丢失。如果想将请求中的数据递解析为数组可以利用这种形式的,不过值之间需要使用 ,
进行分割
@RequestParam
有默认参数值选项,如 @RequestParam(name="name", required=false, defaultValue="World")
。
请求头信息
@RequestHeader
, 通过这个注解可以获取到 request 里的请求头的键值对数据,其 value 指定请求头名。和 RequestParam 类似,可以 Map 形式获取所以信息。@CookieValue
, 特别的,这个注解可以获取请求头中的 cookie 值中的键值对,默认参数类型只能是 String。
|
|
包装数据
有时会为了方便处理表单,或者传输的键值过多,需要使用值对象来装载,这时候就可以用 @ModelAttribute
来实现。
可以从,URL、QueryString、Request Body 中方便的获取每一部分的键值,最后组装在一起,如下的代码:
|
|
对应 curl 命令可以有如下的请求示例
|
|
对应没有获取到的值,会使用其类型的空值,其值的获取范围:
@SessionAttributes
。- 同一个控制器中的
@ModelAttribute
方法。 - URI 模板变量和类型转换器中获取的。
- 使用默认构造器初始化的。
@ModelAttribute
除了修饰参数外,可以直接注解处理方法,用于包装数据,。
获取 Request Body 中的数据
获取 Request Body 中的数据的一大需求就是获取其中的 json 数据。使用 @RequestBody
注解方法参数可以直接完成反序列化:
|
|
可以使用下面的命令测试
|
|
body 中的 json 数据允许与 VO 中的不对应,该注解不能解析 application/x-www-form-urlencoded
类型的 body,这一点没有 django rest framework 里统一解析方便。
获取文件
对于 body 中的文件可以通过注入的 ServletRequest 然后进行比较存储,也可以使用 Multipartfile 或者 Part 类型的参数,通过 Spring 注入进来(适用于 form-data):
|
|
获取其他数据
如果想要在处理方法里使用到进一步的数据,如获取请求方法、获取原生 ServletRequest、HttpSession 等可以很方便的根据类型来注入,所以可以注入到方法的参数可以参考文档 Method Arguments
|
|
其实 @RequestParam
对参数不用注解也是可以同名请求参数注入到方法的参数里的,而且是默认运行为空的。
@SessionAttribute
注解控制器的参数的话,可用获取到预先存在于 HttpSession 中被 setAttribute 的属性。
@RequestAttribute
注解控制器的参数的话,可以被用于访问由过滤器或拦截器创建的、预先存在的请求属性,如在自定义的 filter 里面使用 request.setAttribute
设置的属性。
数据转换和验证
@RequestParam
, @RequestHeader
, @PathVariable
, @MatrixVariable
, @CookieValue
等注解都是基于 http 协议的 string 进行类型转换到注入类型的,像 int, long, Date 目标参数类型是可以简单的直接转换的,如果需要稍复杂的数据就需要 WebDataBinder 或者注册 Formatters 来完成了
Formatters
日期和数值注解可以方便的使用 @DateTimeFormat
@NumberFormat
注解来解析,如下
|
|
上面的日期也可以使用 @DateTimeFormat(pattern = "yyyy-MM-dd")
形式,注意是上面的没有使用 @RequestParam
也是可以非显示的注入的。
WebDataBinder?
与 Formatters 转换简单值不同,使用 WebDataBinder 可以进行复杂的复合数据的转换:
|
|
对应的处理方法:
|
|
相应 id-name-address
的格式可以使用 http://localhost:8080/json/?user=1-Tom-Beijing
访问测试,这里的能够实现的转换的条件是:
- 实现
Converter<String, User>
接口,前者是源参数类型,后面是目标参数类型。 - 将自定义的 Converter 使用
@Component
加入到 spring 上下文中。 - 在 handler 里存在一个参数类型是 User,参数名为 user,同样的在请求中存在键为 user 的 String 值,于是就使用了 StringToUserConverter 进行了转换。
数组形式的参数,也是可以直接调用自定义的 Converter 来完成的(这里的 @RequestParam
好像是必要的),使用 http://localhost:8080/json/?user=1-Tom-Beijing,2-Jack-Wuhan
访问下面的 handler
|
|
数据验证
在 VO 或者 POJO 上的属性上加上 JSR-303 注解可以实现字段的验证信息的标注,再在处理方法的注入参数上加上 @Valid
即可实现参数检查,加上参数类型为 Errors 之后,spring 会将错误信息注入。
|
|
|
|
处理 @Min
注解外,还可参考 javax.validation.constraints 下的注解,还可以使用如 Hibernate 提供的 Range 注解。Errors 也可为 BindingResult,下列示例返回了错误信息,最好处理数据验证错误的方式是在切面里进行统一的处理,参见 Custom Error Message Handling for REST API。
|
|
也可以自己实现 Validator 接口之后在控制类里使用 @InitBinder
注解一个用于绑定验证器的方法
控制 Response Body
JSON 序列化
通过前面的例子可以知道,在 ResponseBody 或者 RestContoller 注解下的 handler 直接返回 POJO 之后,Jackson JSON 可直接帮你序列化返回(根据 get 方法来获取),如果有不同模式下序列化不同 POJO 字段的需求可以使用 @JsonView
和在 POJO 内定义的 interface 来实现,如:
|
|
上面的视图方法里指定 @JsonView(User.WithoutPasswordView.class)
注解,那么返回的只会解析有 WithoutPasswordView.class
的 getter,不包括没有使用 @JsonView
的。除了直接修饰 handler 也可以下面的方式动态添加到视图 Model 里:
|
|
@JsonView
也是支持数组参数的,所以可以 @JsonView({WithoutPasswordView.class, WithPasswordView.class})
还有更多的注解用于指定序列化时候的细节控制:
@JsonAnyGetter
用于注解 Map 类型的 getter。@JsonGetter
注解 getter 方法指定字段名,而不是根据 getter 名。@JsonProperty
注解属性,显式指定序列化的多种属性,参数如 name,index,access等。@JsonPropertyOrder
注解 POJO 类指定顺序。@JsonRawValue
用于 String 类型 Json 字符串的嵌套序列化。@JsonRootName
外层包裹的对象。@JsonIgnoreProperties
@JsonIgnoreType
注解 POJO 类,忽略某些属性和类型。@JsonIgnore
注解需要忽略的属性。
如对密码的序列化的处理,不允许反序列化即对 json 来说只允许写:
|
|
详细用法和其他注解参考更多。
Thymeleaf
除了返回 JSON 之外,还可返回视图模板的路径,这里以 Thymeleaf 前端模板语言为例。首先添加其依赖:
|
|
编写控制器
|
|
这里的控制器就不能使用 RestContoller 或者 ResponseBody ,返回值为字符串就是目标文件的路径,该路径一般为 classpath:/resources/templates/
下,可以通过配置 spring.thymeleaf.prefix
来指定自定义的路径。model 参数为渲染视图时候的上下文对象,将需要在视图中用到的数据使用 addAttribute 添加,即可在模板里使用。
编写目标文件 resources/templates/index.html
|
|
这里的 th:
并不是 HTML 里的表格头标签,而是命名域 thymeleaf 的缩写,该命名域由 <html>
标签里的 xmlns:th="http://www.w3.org/1999/xhtml"
指定。th:text="${name}"
就将传递过来的上下文中名为 name 的数据渲染到该标签的 text 中,其他 thymeleaf 使用方法参见 thymeleaf。
除此之外还支持 JSP, JSTL, Thymeleaf, FreeMarker, Velocity, Groovy, Mustache,模板语言对比。
控制 Response Header
- Mapping 中的
consumes
可以精细化 Media Type,相应的可以使用produces
指定返回的 Media type ,如@GetMapping(path = "/pets/{petId}"
,produces = "application/json;charset=UTF-8")
,即可指定 Response HTTP 协议中的Content-Type
头。
更好的方式是在 controller 里返回一个 ResponseEntity 类型
|
|
这里示范了两种方式,一个是链式调用,一个是自己构造。上面的 ResponseEntity<?>
返回值也可以改为固定的类型。
通常来说,返回的状态码一般分为成功和失败两种,成功相对失败更加的唯一,所以有时候可以使用 @ResponseStatus
来简化 handler 的响应:
|
|
默认的 handler 成功之后是返回 SUCCESS 的,上面的代码修改了其默认返回状态码,而错误时候的返回码可以通过抛出异常来统一的处理。
控制其他
页面跳转
|
|
静态资源
Spring Boot 默认提供静态资源目录位置需置于 classpath 下,目录名需符合如下规则:
- /static
- /public
- /resources
- /META-INF/resources
如果想要自定义如 404 错误的视图页面直接在如 src/main/resources/templates/error/
路径下创建如 404.html 等页面即可。