一、前言
什么是rest?什么是restful?
我相信很多人区分不开来,Rest的英文全称为Representational State Transfer,即表述性状态转移,就是将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端(或者反过来)。Rest是一种软件架构风格而不是标准,提供了设计原则和约束,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。而Restful是Rest的一种实现,是基于Rest风格构建的API。
二、Restful API优点
1.Rest面向资源,一目了然,具有自解释性。
2.Restful接口直接基于HTTP协议,轻量,不在需要任何别的诸如消息协议。
3.数据格式默认以Json为主体,数据简单易读,前端不用单独解析,可以直接使用。
4.Rest是无状态的,无状态约束使服务器的变化对客户端是不可见的,因为在两次连续的请求中,客户端并不依赖于同一台服务器。就是说Service端是不用保存Client端的状态信息,比如登陆信息等。Client发送的请求必须包含有能够让服务器理解请求的全部信息,包括自己的状态信息。这点在分布式系统上显的比较重要。
5.不用理会客户端的类型,不论你是Web端请求,还是移动端请求,对后台接口而言,我只需要返回相应约定好格式的数据即可。
三、Restful中的概念重要
1、资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念,它可以是一段文本、一张图片、一首歌曲、一种服务。资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI(统一资源定位符)来标识,URI既是资源的名称,也是资源在Web上的地址。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。
2、表现层(Representation)
我们把"资源"具体呈现出来的形式,叫做它的Representation,是一段对于资源在某个特定时刻的状态的描述。可以有多种格式,例如HTML/XML/JSON/纯文本/二进制格式/图片/视频/音频等等。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段是对Representation的描述。
3、状态转移(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程——在客户端和服务器端之间转移(transfer)代表资源状态的表述。而互联网通信协议HTTP协议,是一个无状态协议,所以所有的状态都需保存在服务器端。
四、SpringMVC实现Restful API
了解了Restful之后,对于一个Java程序员,特别是主攻后端的Java程序员,如何编写一个标准的Restful API才是关键。
在Rest中定位资源是通过URL进行识别的;而Rest中的行为是通过HTTP方法来定义的,所以我们还需要了解一下HTTP方法的定义:
POST:创建资源
READ:获取资源
PUT/PATCH:更新资源
DELETE:删除资源
注意:PUT和PATCH的区别在与PUT是对整个对象进行更新,而PATCH是局部更新,是非幂等的,PATCH在Spring3.2以上版本支持;POST具有非幂等的特点,所以一般情况下用作创建资源,当然还可以进行其他的操作。
1.创建一个基本的Restful API
下面这个例子就是最简单的一个控制器,通过在UserController类上添加@Controller注解表明该类是控制器。而@RequestMapping注解则用来处理请求地址映射,可以同时用在类或者方法上。@RequestMapping注解常用的两个属性就是method和value,method代表接受的HTTP请求方法;value代表URL路径的一部分。最后返回的时候需要加上@ResponseBody注解,该注解的作用是将Controller的方法返回的对象通过适当的转换器转换为指定的格式,并写入返回对象的body区,通常用来返回JSON数据或者是XML数据。
@Controller
public class UserController {
@Autowired
private UserSerivce userSerivce;
@RequestMapping(method=RequestMethod.GET, value="/users")
@ResponseBody
public List<User> getUsers() {
return userSerivce.getUsers();
}
}
在Spring4.0中提供了很多新的注解,其中@RestController注解就是其中之一。从下面的源码可以看到,@RestController其实就是集合了@Controller和@ResponseBody注解。这样我们就不用在每一个API上都使用@ResponseBody注解了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
当然一个Restful API更有可能我们需要提供查询的参数,所以我们就需要引入@PathVariable注解。该注解可以将URL中占位符参数绑定到控制器处理方法的入参中:URL中的{xxx}占位符可以通过@PathVariable("xxx") 绑定到操作方法的入参中。使用方式如下所示:
// 访问方式 GET http://localhost:8080/user/1
@RequestMapping(method=RequestMethod.GET, value="/user/{id}")
public User getUser(@PathVariable("id") Integer id) {
return userSerivce.getUser(id);
}
但是有时候这样的方式,如果参数非常多的话,就会显得URL特别的冗长,不易读。所以我们需要引入@RequestBody来解决该问题,通过该注解可以将请求体中的JSON字符串绑定到相应的bean中。以新增一个User为例,使用方式如下所示:
@RequestMapping(method=RequestMethod.POST, value="/user")
public User addUser(@RequestBody User user) {
return userSerivce.addUser(user);
}
2.返回错误信息
在Spring3中添加了一个ResponseEntity用于在返回时可以定义返回的HttpStatus和HttpHeaders,使用方式如下所示。
@RequestMapping(method = RequestMethod.GET, value = "/user/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") int id) {
User user = userService.getUser(id);
HttpStatus httpStatus = HttpStatus.OK;
HttpHeaders httpHeaders = new HttpHeaders();
try {
httpHeaders.setLocation(new URI("localhost:8080/user/" + id));
} catch (URISyntaxException e) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
e.printStackTrace();
}
if(user == null) {
httpStatus = HttpStatus.NOT_FOUND;
}
return new ResponseEntity<User>(user, httpStatus);
}
但是我个人觉得这种方式并不能解决我们平常业务中的问题。在大多数情况下,其实我们的请求都是Ajax请求,我们任然需要和前端沟通返回的格式,比如如下的格式。通过code将后端执行后的结果告诉前端,我们任然需要自己写公用的返回工具类。
{
"code": 200,
"msg": "成功",
"_csrf": "6BerChUe",
"data": {
// 数据存放
},
"login": false
}
3.资源操作
下面是对资源进行增删查改操作。
@RestController
public class UserController {
@Autowired
private UserService userService;
// GET localhost:8080/user/{id}
@RequestMapping(method = RequestMethod.GET, value = "/user/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") int id) {
User user = userService.getUser(id);
HttpStatus httpStatus = HttpStatus.OK;
if(user == null) {
httpStatus = HttpStatus.NOT_FOUND;
}
return new ResponseEntity<User>(user, httpStatus);
}
// POST localhost:8080/user
@RequestMapping(method = RequestMethod.POST, value = "/user")
public ResponseEntity<User> addUser(@RequestBody User user) {
User result = userService.addUser(user);
HttpStatus httpStatus = HttpStatus.OK;
if(result == null) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return new ResponseEntity<User>(result, httpStatus);
}
// DELETE localhost:8080/user/1
@RequestMapping(method = RequestMethod.DELETE, value = "/user")
public ResponseEntity<User> deleteUser(@PathVariable("id") int id) {
User result = userService.deleteUser(id);
HttpStatus httpStatus = HttpStatus.OK;
if(result == null) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return new ResponseEntity<User>(result, httpStatus);
}
// PUT localhost:8080/user
@RequestMapping(method = RequestMethod.PUT, value = "/user")
public ResponseEntity<User> updateUserByPUT(@RequestBody User user) {
User result = userService.updateUser(user);
HttpStatus httpStatus = HttpStatus.OK;
if(result == null) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return new ResponseEntity<User>(result, httpStatus);
}
// PATCH localhost:8080/user
@RequestMapping(method = RequestMethod.PATCH, value = "/user")
public ResponseEntity<User> updateUserByPATCH(@RequestBody User user) {
User result = userService.updateUser(user);
HttpStatus httpStatus = HttpStatus.OK;
if(result == null) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return new ResponseEntity<User>(result, httpStatus);
}
}
同样对接口的测试我们可以用专门的工具进行测试,我这里使用的是Postman。
五、参考文档
《Spring In Action》第四版
https://www.cnblogs.com/wzbinStu/p/8566367.html
https://blog.csdn.net/maxiao124/article/details/79897229
https://baike.baidu.com/item/rest/6330506?fr=aladdin