第 11 章 构建REST风格网站
在HTTP协议发展的过程中,提出了很多的规则,但是这些规则有些烦琐,于是又提出了一种风格约定,它便是REST风格。实际上严格地说它不是一种标准,而是一种风格。在现今互联网的世界中这种风格己经被广泛使用起来了。尤其是现今流行的微服务中,这样的风格甚至被推荐为各个微服务系统之间用于交互的方式。
首先在REST风格中,每一个资源都只是对应着一个网址,而一个代表资源的网址应该是一个名词,且不存在动词,这代表对一个资源的操作。在这样的风格下对于简易参数则尽量通过网址进行传递。例如,要获取id为l的用户的URL可能就设计http//localhost:8080/user/1
11.1 REST简述
11.1.1 REST名词解释
REST按其英文名称(Representational State Transfer)可翻译为表现层状态转换。首先需要有资源才能表现,所以第一个名词是"资源"。有了资源也要根据需要以合适的形式表现资源,这就是第二个名词是"表现层"。最后是资源可以被新增、修改、删除等,也就是第三个名词“状态转换”
- 资源: 它可以是系统权限用户、角色和菜单等,也可以是一些媒体类型,如文本、图片、歌曲,总之它就是一个具体存在的对象。每个资源对应一个独一无二的
URI。在REST中,URI也可以称为端点(EndPoint)。 - 表现层: 有了资源还需要确定如何表现这个资源。例如,一个用户可以使用
JSON、XML或者其他的形式表现出来 - 状态转换: 现实中资源并不是一成不变的, 它是一个变化的过程,一个资源可以经历创建(
create)、访问)(visit) 、修改(update)和删除(delete)的过程
综上我们可以总结REST风格架构的特点
- 服务器存在一系列的资源,每一个资源通过单独唯一的
URI进行标识 - 客户端和服务器之间可以相互传递资源,而资源会以某种表现层得以展示
- 客户端通过
HTTP协议所定义的动作对资源进行操作,以实现资源的状态转换
11.1.2 HTTP的动作
可以用HTTP请求的类型,来代表对资源的CRUD的行为
GET:READ,访问服务器资源POST:CREATE,在服务器创建新的资源PUT:UPDATE,修改服务器已经存在的资源,使用PUT时需要把资源的全部属性一并提交PATCH:UPDATE,修改服务器已经存在的资源,使用PATCH时只需要将部分资源属性提交DELETE:DELETE,从服务器将资源删除
下面举几个REST风格的例子
1 | # 获取用户信息,1代表用户编号 |
在URI中并没有出现动词,而对于参数主要通过URI设计去获取。对于参数数量超过5个的可以考虑使用传递JSON的方式来传递参数
11.1.3 REST风格的一些误区
-
REST风格的URI中不存在动词,例如
GET /user/get/1,应改为GET /user/1 -
URI中不应该加入版本号,例如下面
GET /v1/user/1,如果存在版本号应设置在请求头中 -
类似这种
PUT users?userName=user_name¬e=note是不推荐使用的,应改为PUT users/{userName}/{note}
11.2 使用SpringMVC开发REST风格端点
Spring对REST风格的支持是基于SpringMVC设计基础上的,在Spring 4.3之后则有更多的注解引入使得REST风格的开发更为便捷。
11.2.1 SpringMVC整合REST
只要把URI设计为符合REST风格规范,那么显然就己经满足REST风格了。不过为了更为便捷地支持REST风格的开发,Spring 4.3之后除了@RequestMapping外,还可以使用以下5个注解,这5个注解主要是针对HTTP的动作而言的,通过它们就能够有效地支持REST风格的规范
@GetMapping: 对应HTTP的GET请求,获取资源@PostMapping: 对应HTTP的POST请求,创建资源@PutMapping: 对应HTTP的PUT请求,提交所有资源属性以修改资源@PatchMapping: 对应HTTP的PATCH请求,提交资源部分修改的属性@DeleteMapping: 对应HTTP的DELETE请求,删除服务器端的资源
在REST风格的设计中,如果是简单的参数,往往会通过URL直接传递,在SpringMVC可以使用注解@PathVariable进行获取,对于那些复杂的参数,可以考虑使用请求体JSON的方式提交给服务器,这样就可以使用注解@RequestBody将JSON数据集转换为Java对象。
在现今的开发中,数据转化为JSON是最常见的方式,这个时候可以考虑使用注解@ResponseBody,这样SpringMVC就会通过MappingJackson2HttpMessageConverter最终将数据转换为JSON数据集,而在SpringMVC对REST风格的设计中,甚至可以使用注解@RestController让整个控制器都默认转换为JSON数据集。
11.2.2 使用Spring开发REST风格端点
我们可以使用POST动作来创建资源
1 | ("/user") |
@PostMapping表示采用POST动作提交用户信息@ReguestBody代表接收的是一个JSON数据集参数@ResponseBody代表会将函数的返回值转化为JSON格式传递给前端
接下来就是使用GET动作来获取对象了
1 | (value="/user/{id}") |
- 采用注解
@GetMapping声明HTTP的GET请求,并且把参数编号(id)以URI的形式传递,这符合了REST风格的要求 - 在
getUser方法中使用了注解@PathVariable从URI中获取参数 @ResponseBody代表会将函数的返回值转化为JSON格式传递给前端
更新和删除操作也与上面的例子类似,这里略
11.2.3 使用@RestController
因为现在前后端分离,所以使用JSON作为前后端交互已经十分普遍。如果每一个方法都加入@ResponseBody才能将数据模型转换为JSON,显然有些冗余。SpringMVC还存在一个注解@RestController,它可以修饰控制器类,将类中的方法返回值转化为JSON数据
1 |
|
11.2.4 渲染结果
在@RequestMapping、GetMapping等注解中还存在consumes和produces两个属性。其中consumes
代表的是限制该方法接收什么类型的请求体(body), produces代表的是限定返回的媒体类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回。
假如我们想要某个方法返回一个字符串,而不是JSON的话,可以采用如下方法
1 | (value="/user/name/{id}", produces=MediaType.TEXT_PLAIN_VALUE) |
- 对于
getUserName方法,因为@GetMapping的属性produces声明为普通文本类型,也就是修改了原有@RestController默认的JSON类型,同样结果也会被SpringMVC自身注册好的StringHttpMessageConverter拦截, 这样就可以转变为一个简单的字符串。
11.2.5 处理HTTP状态码、异常和响应头
当发生资源找不到或者处理逻辑发生异常时, 需要考虑的是返回给客户端的HTTP状态码和错误消息的问题。为了简化这些开发,Spring提供了实体封装类ResponseEntity和注解@ResponseStatus。ResponseEnti ty可以有效封装错误消息和状态码,通过@ResponseStatus可以配置指定的响应码给客户端。
下面我们修改插入用户的方法,将状态码修改为201,并且插入响应头的属性来标识这次请求的结果
1 | (value = "/user2/entity") |
insertUserEntity方法中定义返回为一个ResponseEntity<User>的对象,这里还生成了响应头(HttpHeaders对象),并且添加了属性success来表示请求是否成功,在最后返回的时刻生成了一个ResponseEntity<User>对象,然后将查询到的用户对象和响应头捆绑上,并且指定状态码为201(创建资源成功)- 在
insertUserAnnotation方法上则使用了@ResponseStatus注解将HTTP的响应码标注为201(创建资源成功),所以在方法正常返回时Spring就会将响应码设置为20l
但是有时候会出现一些异常,例如,按照id查找用户,可能查找不到数据,这个时候就不能以正常返回去处理了,又或者在执行的过程中产生了异常,这也是需要我们进行处理的。通过@ControllerAdvice和@ExceptionHandler注解可以通过控制器通知的方式对异常情况进行处理
首先我们自定义一个查找失败异常
1 | public class NotFoundException extends RuntimeException { |
然后自定义一个控制器通知,来自定义异常的处理
1 | //控制器通知 |
- 这里使用了
@ControllerAdvice来标注类,说明在定义一个控制器通知。 basePackages配置了它所拦截的包,annotations限定了拦截的那些被标注为注解@Controller和@RestController的控制器,- 这里的
@ExceptionHandler定义了拦截NotFoundException的异常 @ResponseBody定义了响应的信息以JSON格式表达@ResponseStatus定义了状态码为500(服务器内部错误),这样就会把这个状态码传达给请求者
最后我们让前文定义的getUser方法抛出异常,这样完成了当发生资源找不到或者处理逻辑发生异常这种情况下的处理逻辑
1 | (value="/user/{id}") |
这样当发生异常时,就可以返回500状态码和异常信息字符串了