### 13.2.3 REST Client 上一章讲到如何在SpringBoot中提供RESTfull服务,本章将介绍系统之间,如何发起REST请求。 SpringBoot提供了RestTemplate来辅助发起一个REST请求,默认通过JDK自带的HttpURLConnection来作为底层HTTP消息的发送,使用JackSon 来序列化服务器响应的JSON数据 #### 13.2.3.1 RestTemplate RestTemplate是核心类,提供了所有访问Rest服务的接口,尽管实际上可以使用HTTP Client类或者java.net.URL 来完成,但RestTemplate提供了Restful风格的的API。 RestTemplate有6个主要得方法对应于Restful的6个主要得Http Method。 | HTTP Method | Java API | | ----------- | ----------------------------- | | DELETE | delete | | GET | getForObject,getForEntity | | HEAD | headForHeaders | | OPTIONS | optionsForAllow | | POST | postForObject,postForLocation | | PUT | put | | 其他 | exchange(通用) | SpringBoot 提供了RestTemplateBuilder来创建一个RestTemplat。应用可以通过如下代码来创建一个RestTemplate实例 ~~~java @Autowired RestTemplateBuilder restTemplateBuilder; public void foo(){ RestTemplate client = restTemplateBuilder.build(); } ~~~ 下面列举一下使用RestTemplate来查询订单和创建订单 ~~~java /*一个测试类*/ @Controller @RequestMapping("/test") public class RestClientTestCrontroller { @Value(value = "${api.order}") String base ; @Autowired RestTemplateBuilder restTemplateBuilder; @GetMapping("/get/{orderId}") public @ResponseBody Order testGetOrder(@PathVariable String orderId) throws Exception{ RestTemplate client = restTemplateBuilder.build(); String uri = base+"/order/{orderId}"; //核心代码 Order order = client.getForObject(uri, Order.class,orderId); return order; } } ~~~ base是在配置文件application.properties中配置的订单API地址 ~~~properties api.order=http://127.0.0.1:8080/api/v1 ~~~ 代码首先构造RestTemplate,然后调用getForObject,此方法接受三个参数,第一个是URI 模板,第二个参数是期望返回的对象,后面是URI模板对应的参数列表。参数列表既可以是数组,也可以是个Map,如上有可以写成 ~~~java Map map = new HashMap(); map.put("orderId",orderId); Order order = client.getForObject(uri, Order.class,map); ~~~ 如果还想获取响应的HTTP头相关信息,可以调用client.getForEntity,此方法返回ResponseEntity,包含了头信息 ~~~java ResponseEntity<Order> responseEntity = client.getForEntity(uri, Order.class, orderId); Order order = responseEntity.getBody(); HttpHeaders headers = responseEntity.getHeaders(); ~~~ 添加订单可以使用postForObject方法,此方法接受三个参数,第一个是URI,第二个是Post参数,可以是HttpEntity,或者是某个POJO对象,POJO对象在这种情况下回自动转成HTTPEntity,第三个参数是期望返回的类型,这个例子期望返回类型是String ~~~java @GetMapping("/addorder") public @ResponseBody String testAddOrder() throws Exception{ RestTemplate client = restTemplateBuilder.build(); String uri = base+"/order"; Order order = new Order(); order.setName("test"); String ret = client.postForObject(uri, order, String.class); //{success:true,message:"添加成功"} return ret; } ~~~ 或者使用HttpEntity ~~~java HttpEntity<Order> body = new HttpEntity<Order>(order); String ret = client.postForObject(uri, body, String.class); ~~~ 使用HttpEnity的好处是可以提供额外的HTTP头信息。 如果期望返回的类型是一个列表,如List<Order>,不能简单调用xxxForObject,因为存在泛型的类型擦除,RestTemplate在反序列化的时候并不知道实际反序列化的类型,因此可以使用ParameterizedTypeReference来包含泛型类型,代码如下 ```java RestTemplate client = restTemplateBuilder.build(); //根据条件查询一组订单 String uri = base+"/orders?offset={offset}"; Integer offset = 1; //无参数 HttpEntity body = null; ParameterizedTypeReference<List<Order>> typeRef = new ParameterizedTypeReference<List<Order>>() {}; ResponseEntity<List<Order>> rs = client.exchange(uri, HttpMethod.GET, body, typeRef, offset); List<Order> order = rs.getBody(); ``` 注意到typeRef实际定义是用{}结束,这里实际上创建了一个ParameterizedTypeReference子类,依据在类定义中的泛型信息是保留的原则,typeRef保留了期望返回的泛型List<Order> exchange 是一个基础的Rest调用接口,除了需要指明HTTP Method外,调用跟其他方法都类似。 > 除了使用ParameterizedTypeReference来保留泛型信息外,也可以通过getForObject方法先映射成String,然后通过ObjectMapper来转为指定类型,可以参考第三章Jackson来了解。 #### 13.2.3.2 定制 RestTemplate 创建一个配置类实现RestTemplateCustomizer接口的customize方法,此方法会 ~~~java @Configuration public class RestConf implements RestTemplateCustomizer { public void customize(RestTemplate restTemplate) { SimpleClientHttpRequestFactory jdkHttp = (SimpleClientHttpRequestFactory)restTemplate.getRequestFactory(); jdkHttp.setConnectTimeout(1000); } } ~~~ 如上代码,customize方法会定制RestTemplate,上面的代码设置链接超时时间为1000毫秒。Spring Boot因为默认使用了JDK的URLConnection作为底层的HTTP工具,如果想使用了OkHttp。需要添加如下依赖 ~~~xml <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.0.1</version> </dependency> ~~~ 那上面的代码应该是 ~~~java public void customize(RestTemplate restTemplate) { OkHttp3ClientHttpRequestFactory okHttp = (OkHttp3ClientHttpRequestFactory)restTemplate.getRequestFactory(); okHttp.setReadTimeout(5000); okHttp.setWriteTimeout(3000); } ~~~