这一章作者还挺哲学:如果一个API从来没有人访问,它还是一个API吗。又说一个API就像薛定谔的猫,只有你访问的时候,才知道是死是活。
之前访问API都是通过Postman或者是浏览器,这也是消费数据的一种,如果配上前端页面,就是前后端分离。
这一章主要讲的是如何创建能够消费REST API的Spring应用程序,主要通过三个手段:
RestTemplate
,Spring Framework提供的简单的同步的访问REST API的客户端
Traverson
,Spring HATEOAS提供的可以访问HATEOAS形式JSON的客户端
WebClient
,Spring 5新提供的反射式,异步的REST客户端
WebClient的内容将在Spring 反射式编程再学,先看前两种。
RestTemplate
回想一下如果要创建一个客户端,肯定要写一堆样板代码,访问部分API,然后返回结果。像JDBCTemplate一样,RestTemplate也可以节省大量样板代码。
RestTemplate提供了41种方法来访问REST资源,可以将其分类成12种操作,对应41种具体方法:
方法 |
说明 |
delete(…) |
执行一个HTTP DELETE请求 |
exchange(…) |
向一个特定URL发请求,返回一个ResponseEntity对象,包含从请求体JSON中生成的一个对象 |
execute(…) |
向一个特定URL发请求,返回一个请求体JSON生成的对象 |
getForEntity(…) |
发送一个GET请求,返回ResponseEntity对象,包含请求体JSON生成的对象 |
getForObject(…) |
发送一个GET请求,返回请求体JSON生成的对象 |
headForHeaders(…) |
发送HEAD请求,返回特定URL返回的响应头部 |
optionsForAllow(…) |
发送Option请求,返回对方允许接受的头部 |
patchForObject(…) |
发送PATCH请求,返回更新后的对象 |
postForEntity(…) |
发送POST请求,返回一个ResponseEntity对象 |
postForLocation(…) |
发送POST请求,返回新创建资源的URL |
postForObject(…) |
发送POST请求,返回响应体对应的对象 |
put(…) |
向指定URL发送PUT请求 |
除了TRACE请求之外,RestTemplate对于基础的HTTP请求类型都至少提供了一个方法。这其中execute()
和exchange()
体提供了相对底层但是通用的方法。
表格里的大部分方法都有三种重载:
- 字符串形式的URL+参数
- 字符串形式的URL和参数,使用
Map<String,String>
格式封装
java.net.URI
对象,不支持URL参数
使用RestTemplate的方法就是先new一个对象出来,如果在Spring中,可以弄一个Bean或者注入到一个变量中都可以。
这里首先关闭我们自己的方法的HATEOAS,就是只返回原始的JSON。HATEOAS可以用Spring Data Rest测试。
GET 请求
在项目中创建一个控制器类,通过URL访问即可测试:
package cc.conyli.restlearn.controller;
import cc.conyli.restlearn.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Controller
@RequestMapping("/consume")
public class ConsumeController {
private RestTemplate restTemplate = new RestTemplate();
@GetMapping("/gettest")
public String getTest() {
Student student = getStudentById("3");
System.out.println(student);
return "home";
}
private Student getStudentById(String id) {
return restTemplate.getForObject("http://localhost:8080/myapi/students/{id}", Student.class, id);
}
}
这里先编写了一个将REST返回结果转换成单个学生的方法。三个参数分别是URL,要转换成的类和参数。其中URL可以支持花括号{}的参数。
访问之后发现控制台里可以打印出获取的学生对象。
如果想支持多个参数的查询,可以使用Map参数:
private Student getStudentByIdMap(String studentId) {
Map<String, String> urlVariables = new HashMap<>();
urlVariables.put("id", studentId);
return restTemplate.getForObject("http://localhost:8080/myapi/students/{id}", Student.class, urlVariables);
}
这样在占位符里的id,就会被换成Map对象中同名的键名对应的值。
还有一种重载方法是传入java.net.URI
对象,这需要使用一个URI构建器创建一个URI,与上边的方法有些类似:
private Student getStudentByIdUrl(String studentId) {
Map<String,String> urlVariables = new HashMap<>();
urlVariables.put("id", studentId);
URI url = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/myapi/students/{id}").build(urlVariables);
return restTemplate.getForObject(url, Student.class);
}
使用了URI之后,就无需传入最后的参数了。可见还是使用Map或者URI比较方便,第一种只能传一个参数。
getForObject直接获取对象,如果想获取整个响应相关的信息,可以使用getForEntity()来获取一个ResponseEntity对象,里边除了包装了需要的对象,还可以获取响应的其他信息。
private Student getStudentByIdFromResponseEntity(String studentId) {
ResponseEntity<Student> responseEntity = restTemplate.getForEntity("http://localhost:8080/myapi/students/{id}", Student.class, studentId);
log.info("Fetched time" + responseEntity.getStatusCode() + responseEntity.getStatusCodeValue() + responseEntity.getHeaders().getDate());
return responseEntity.getBody();
}
这里获取了响应对象之后,可以获取状态码,Header信息等一系列数据。里边的泛型传入想取得的对象,就可以使用.getBody()获取了。
PUT 请求
PUT请求是更新,所以需要传入一个数据对象,然后使用put()方法将其发送到指定的URL即可
private void updateStudent(Student student) {
restTemplate.put("http://localhost:8080/myapi/students/{id}", student, student.getId());
}
这里还是使用id,然后传入到指定的URL中,更新指定id的对象。
DELETE 请求
DELETE方法无需传入要删除的类型,只需要传入URL的参数。
private void deleteStudent(Student student) {
restTemplate.delete("http://localhost:8080/myapi/students/{id}",student.getId());
}
这里发现报错,不支持的媒体类型,后来发现@DeleteMapping(path = "/{id}", consumes = "application/json")
里由于delete方法没有请求体,所以去掉consumes属性即可。
POST 请求
POST请求无需传URL参数,直接传入对象类即可。
private void postStudent(Student student) {
restTemplate.postForObject("http://localhost:8080/myapi/students", student, Student.class);
}
这是使用了postForObject,就是传入对象和对象的类用于生成JSON,然后POST到指定的地址。这里还可以传入第四个参数,可以是Map对象的URL参数键值对。
如果需要新创建的对象的URL地址,可以使用另外一个方法,这个方法从响应头部的Location获取地址,由于我们自己的响应头没有设置,可能会得到null:
private URI postStudentAndGetAddress(Student student) {
return restTemplate.postForLocation("http://localhost:8080/myapi/students", student);
}
如果也需要详细的响应对象,也可以使用返回ResponseEntity的方法:
private Student postStudentByEntity(Student student) {
ResponseEntity<Student> responseEntity = restTemplate.postForEntity("http://localhost:8080/myapi/students", student, Student.class);
log.info(responseEntity.getHeaders().getLocation()+" ");
log.info(responseEntity.getStatusCode() + " 响应码");
return responseEntity.getBody();
}
这样可以获取后设置好的对象,由于我们的REST API对于新建对象会返回创建后的对象,这个就可以获取创建后的对象了。
使用Traverson来遍历HATEOAS REST API
先需要添加依赖库:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
Traverson是一个简单可用的遍历Spring Data HATEOAS API的链接。这个Traverson来自于一个同名的JS库。
要使用Tranverson,就给其网站的HATEOAS REST的根路径,以及解析路径的方法两个参数来构造一个对象:
Traverson traverson = new Traverson(URI.create("http://localhost:8080/rest"), MediaTypes.HAL_JSON_UTF8);
有了这个对象后,实际上Traverson就会解析最初返回的根JSON内容,并且可以依照链接去获取不同的内容。在获取内容的时候,用Resource<T>来接受每一个JSON对象。
@GetMapping("/tranverson")
public String trans() {
Traverson traverson = new Traverson(URI.create("http://localhost:8080/rest"), MediaTypes.HAL_JSON);
ParameterizedTypeReference<Resources<Student>> parameterizedTypeReference = new ParameterizedTypeReference<Resources<Student>>(){};
Resources<Student> resources = traverson.follow("students").toObject(parameterizedTypeReference);
Collection<Student> students = resources.getContent();
log.info(students.toString());
return "home";
}
这个有点绕,只要理解Resources就是一批JSON对象就可以了,然后让traverson去follow返回的JSON中的students对应的链接(实际是students对象中的href属性),然后将结果转换成一批JSON,再从其中组成集合就可以了。
这个拿出来的JSON是不展示ID的,与之前学习的规则很匹配,不在数据中展示id。
可以把Traverson想象成是自动按照JSON内的链接去访问的浏览器,所以可以连续去访问,例子就省略了。还可以获取访问的地址,然后交给RestTemplate去操作:
String url = traverson.follow("students").asLink().getHref();
System.out.println(url);
Student student = new Student();
student.setCourseId(3);
student.setLastName("Jenny");
student.setFirstName("Huang");
restTemplate.postForLocation(url, student);
使用这两个东西可以快速来从REST API中获取数据,无论是针对仅使用Jackson的原始数据还是使用HATEOAS的数据都可以。