SpringBoot系列: RestTemplate 快速入门

====================================
相关的文章
====================================
SpringBoot系列: 与Spring Rest服务交互数据
https://www.cnblogs.com/harrychinese/p/Springboot_SpringRest.html

SpringBoot系列: Spring MVC视图方法的补充
https://www.cnblogs.com/harrychinese/p/springboot_mvc_view_function2.html

[转载]SpringBoot系列: SpringMVC 参数绑定注解解析
https://www.cnblogs.com/harrychinese/p/springboot_mvc_view_function.html

SpringBoot系列: RestTemplate 快速入门
https://www.cnblogs.com/harrychinese/p/springboot_resttemplate.html

 

====================================
微服务进程间通信
====================================

微服务进程之间的通讯有 http 和 rpc 两种协议, 在 Spring Cloud 项目中一般都以 http 通信, 常用的访问框架有:
1. JdkHttpConnection 组件
2. Apache HttpClient 组件
3. RestTemplate (Spring Framework 提供的 webclient, 缺省是基于 JdkHttpConnection 实现的, 也可以基于 Apache HttpClient 、 OkHttp 实现)
4. Feign (spring-cloud-starter-feign 项目提供的 webclient)
5. OkHttp (Square 开源的 http 客户端)
6. AsyncHttpClient(基于 Netty 的 http 客户端)
7. Retrofit (Square 开源的 http 客户端, 对于 OkHttp 做了封装)

JdkHttpConnection/Apache HttpClient 等 web 客户端是底层客户端, 如果直接在微服务项目中使用, 需要处理很多工作. 其他几个客户端都针对 Rest 服务做了很多封装, 这包括:
1. 连接池
2. 超时设置
3. 请求和响应的编码/解码 (json <-> pojo)
4. 支持异步


因为我们开发的项目是基于 Spring Boot 的, 考虑到集成性和 Spring 官方的支持程度, 自然选择 RestTemplate 或 Feign 了.
有关 http 通信经常会看到 Robin 相关资料, 该技术是 Spring Cloud Netflix 的一个项目, 是一个基于 Http 和 Tcp 的客户端负载均衡器, 支持两种策略 Round robin 或 weigh based. Robin 可以和 RestTemplate/Feign 搭配使用, 为 web 请求提供负载均衡特性.

 


==========================
pom.xml
==========================
RestTemplate 默认使用 jackson 完成 json 序列化和反序列化.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

 

==========================
RestTemplate 实例化
==========================
RestTemplate 实例最好是由 Spring 容器管理, 而不是在用到时候 new RestTemplate() 一个实例.
可以在 @Controller/@Service/@Configuration 类中, 声明一个 restTemplate bean, 其他地方直接注入即可使用.

@RestController
class HelloController {
    //声明 bean
    @Bean
    @LoadBalanced   //增加 load balance 特性. 
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    //注入
    @Autowired
    private RestTemplate restTemplate;    
    
    private void someMethod(){
       //使用 restTemplate
    }
}


或者, 先注入 RestTemplateBuilder, 然后通过该 builder 来构建 RestTemplate 实例. 使用 builder 可以为 RestTemplate 定制化东西:
builder.additionalInterceptors() 方法: 可以通过增加拦截器为每次 request 记录 log,
builder.additionalMessageConverters() 方法: 比如增加 MessageConverter 实现特定的 json <-> pojo 的转换,

@RestController
class Hello2Controller {    
    //注入 RestTemplateBuilder
    @Autowired
    private void initRestTemplate(RestTemplateBuilder builder){
        this.restTemplate=builder.build();
    }
       
    private RestTemplate restTemplate;    
    
    private void someMethod(){
       //使用 restTemplate
    }
}

 


==========================
RestTemplate 使用
==========================
RestTemplate 主要方法

Http 方法 | RestTemplate 方法
DELETE | delete
GET | getForObject(), getForEntity()
HEAD | headForHeaders()
OPTIONS | OptionsForAllow()
PUT | put
any | exchange(), execute()

1. delete() 方法, 在 url 资源执行 http DELETE 操作.
2. exchange() 方法, 通用的 web 请求方法, 返回一个 ResponseEntity 对象, 这个对象是从响应体映射而来. 该方法支持多种 web method, 是其他 RestTemplate 方法的基础.
3. execute() 方法, 是 exchange() 方法的基础.
4. getForEntity() 方法, 发送一个 GET 请求, 返回一个通用的 ResponseEntity 对象, 使用该对象可以得到 Response 字符串.
5. getForObject() 方法, 发送一个 GET 请求, 返回一个 pojo 对象.
6. headForHeaders() 方法, 发送一个 HEAD 请求, 返回包含特定资源 url 的 http 头.
8. optionsForAllow() 方法, 发送一个 HTTP OPTIONS 请求, 返回对于特定 url 的 Allow 头信息.
9. PostForEntity() 方法, 发送一个 Post 请求, 返回一个 ResponseEntity 对象, 这个对象是从响应体映射而来.
10. PostForObject() 方法, 发送一个 POST 请求, 返回一个特定的对象, 该对象是从响应体映射而来.
11. PostForLocation() 方法, 发送一个 POST 请求, 返回新创建资源的 URL.
12. put() 方法, 发送 PUT 请求.


--------------------------
获取 plain json
--------------------------

ResponseEntity<String> response=restTemplate.getForEntity(url, String.class)
// 获取包含 plain text Body 的 response
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
// 获取 status code 
System.out.println("status code:" + response.getStatusCode());
// 使用 jackson 解析 json 字符串
// class: com.fasterxml.jackson.databind.ObjectMapper
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody());
JsonNode value = root.path("type");

 

--------------------------
获取 Pojo 对象
--------------------------
如果 Rest 服务返回下面的 json 格式:
{ "firstName":"John" , "lastName":"Doe" }

RestTemplate 很容易可以将 json 转成对象:
Employee foo = restTemplate.getForObject(url, Employee.class);

如果 Rest 服务返回下面的 json 格式, json 中有一个根节点 employees, 其包含了多个 Employee 信息.

{
    "employees": [
        { "firstName":"John" , "lastName":"Doe" },
        { "firstName":"Anna" , "lastName":"Smith" },
        { "firstName":"Peter" , "lastName":"Jones" }
    ]
}

对于这种格式的 json, 我们仍然可以使用 getForObject() 方法, 只要基于 Employee 类 做个 list wrapper 类即可. 

public class EmployeeList {
    private List<Employee> employees; 
    public EmployeeList() {
        employees = new ArrayList<>();
    }
     // standard constructor and getter/setter
}
EmployeeList response = restTemplate.getForObject(
  "http://localhost:8080/employees",
  EmployeeList.class);
List<Employee> employees = response.getEmployees();

--------------------------
获取 json 数组对象
--------------------------
虽然 restTemplate.getForObject() 能很方便地将 json 转成 pojo, 但仅仅适合于处理单个对象的情形. 下面的 json 直接返回了一个数组, 这时使用 getForObject() 就不管用了.

[
    { "firstName":"John" , "lastName":"Doe" },
    { "firstName":"Anna" , "lastName":"Smith" },
    { "firstName":"Peter" , "lastName":"Jones" }
]


我们可以使用 exchange() 方法, 最关键一点是将 List<Employee> 类型传进去, 这样 RestTemplate 就知道如何将 json 数组转成 object list 了.

ResponseEntity<List<Employee>> response = restTemplate.exchange(
  "http://localhost:8080/employees/",
  HttpMethod.GET,
  null,
  new ParameterizedTypeReference<List<Employee>>(){});
List<Employee> employees = response.getBody();

--------------------------
向 url 传参
--------------------------
在 POST 和 GET 等方法, 最后一个形参往往是 url 参数变量, 比如:
getForEntity(String url,Class responseType,Object...urlVariables)
getForEntity(String url,Class responseType,Map urlVariables)

处理方式 1:
如果要使用数组或可变参数方式传入 url param, url 的参数必须使用数字下标来占位.

String url = http://USER-SERVICE/user.do?name={1}&age={2};
String[] urlVariables=["jason",26];

 

处理方式 2:
如果要 Map 传入 url param, url 的参数必须使用 named 方式占位

String url = http://USER-SERVICE/user.do?name={name}&age={age};
Map<String, Object> urlVariables = new HashMap<String, Object>();
urlVariables.put("name",jason);
urlVariables.put("age",26);

 

--------------------------
设置 header, Post 一个 json 串
--------------------------

String url="url";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
String body="some json body";
HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
ResponseEntity<String> response= restTemplate.postForEntity(url, requestEntity, String.class);

HttpEntity 经常被用到, 它可以将 Headers 和要提交的数据合并成一个对象, 作为 request 对象传参给 POST/PUT/PATCH 等很多方法. 

--------------------------
Post 一个对象 list
--------------------------
Post 操作可以直接使用 restTemplate.postForObject() 方法, 该方法即可 Post 单个对象, 也可以 Post 对象的 List.

List<Employee> newEmployees = new ArrayList<>();
newEmployees.add(new Employee(3, "Intern"));
newEmployees.add(new Employee(4, "CEO"));
 
restTemplate.postForObject(
  "http://localhost:8080/employees/",
  newEmployees,
  ResponseEntity.class);


--------------------------
使用 HEAD 获取 headers
--------------------------

HttpHeaders httpHeaders = restTemplate.headForHeaders(fooResourceUrl);
assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));


--------------------------
文件上传下载
--------------------------
参考 https://www.jianshu.com/p/bbd9848c0cfc

@Test
    public void upload() throws Exception {
        Resource resource = new FileSystemResource("/home/lake/github/wopi/build.gradle");
        MultiValueMap multiValueMap = new LinkedMultiValueMap();
        multiValueMap.add("username","lake");
        multiValueMap.add("files",resource);
        ActResult result = testRestTemplate.postForObject("/test/upload",multiValueMap,ActResult.class);
        Assert.assertEquals(result.getCode(),0);
    }

@Test
    public void download() throws Exception {
        HttpHeaders headers = new HttpHeaders();
        headers.set("token","xxxxxx");
        HttpEntity formEntity = new HttpEntity(headers);
        String[] urlVariables = new String[]{"admin"};
        ResponseEntity<byte[]> response = testRestTemplate.exchange("/test/download?username={1}",HttpMethod.GET,formEntity,byte[].class,urlVariables);
        if (response.getStatusCode() == HttpStatus.OK) {
            Files.write(response.getBody(),new File("/home/lake/github/file/test.gradle"));
        }
    }
 


--------------------------
定制化 RestTemplate
--------------------------
增加一个自定义 ErrorHandler:
restTemplate.setErrorHandler(errorHandler);

设定 httpClient 的工厂类:
restTemplate.setRequestFactory(requestFactory);
可以为 RestTemplate 设定 httpClient 的工厂类, 主要有两个工厂类:
1. SimpleClientHttpRequestFactory 工厂类, 这是缺省的工厂类, 底层用的是 jdk 的 HttpConnection, 默认超时为-1.
2. HttpComponentsClientHttpRequestFactory 底层用的是 Apache HttpComponents HttpClient, 比 JDK 的 HttpConnection 强大, 可以配置连接池和证书等, 支持 https.
3. OkHttp3ClientHttpRequestFactory 底层使用的是 square 公司开源的 OkHttp, 该客户端支持 https 等高级特性,  pom.xml 需要增加依赖.
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.8.1</version>
        </dependency>

 

====================================
参考
====================================
https://www.jianshu.com/p/bbd9848c0cfc
http://www.cnblogs.com/okong/p/springcloud-four.html
https://my.oschina.net/lifany/blog/688889
http://www.cnblogs.com/okong/p/springcloud-four.html
https://blog.csdn.net/QiaoRui_/article/details/80453799

https://spring.io/guides/gs/consuming-rest/
https://www.tutorialspoint.com/spring_boot/spring_boot_rest_template.htm
https://www.baeldung.com/rest-template
https://www.baeldung.com/spring-rest-template-list

 

posted @ 2018-10-30 17:34  harrychinese  阅读(13185)  评论(0编辑  收藏  举报