Spring in Action 5 Self-learning notes 6 - 消费REST数据

Spring in Action 5 Self-learning notes 6 - 消费REST数据

这一章作者还挺哲学:如果一个API从来没有人访问,它还是一个API吗。又说一个API就像薛定谔的猫,只有你访问的时候,才知道是死是活。 之前访问API都是通过Postman或者是浏览器,这也是消费数据的一种,如果配上前端页面,就是前后端分离。 这一章主要讲的是如何创建能够消费REST API的Spr

这一章作者还挺哲学:如果一个API从来没有人访问,它还是一个API吗。又说一个API就像薛定谔的猫,只有你访问的时候,才知道是死是活。 之前访问API都是通过Postman或者是浏览器,这也是消费数据的一种,如果配上前端页面,就是前后端分离。 这一章主要讲的是如何创建能够消费REST API的Spring应用程序,主要通过三个手段:
  1. RestTemplate,Spring Framework提供的简单的同步的访问REST API的客户端
  2. Traverson,Spring HATEOAS提供的可以访问HATEOAS形式JSON的客户端
  3. 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()体提供了相对底层但是通用的方法。 表格里的大部分方法都有三种重载:
  1. 字符串形式的URL+参数
  2. 字符串形式的URL和参数,使用Map<String,String>格式封装
  3. 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的数据都可以。
LICENSED UNDER CC BY-NC-SA 4.0
Comment