重学springboot系列番外篇之RestTemplate

x33g5p2x  于2021-12-14 转载在 Spring  
字(36.8k)|赞(0)|评价(0)|浏览(1582)

基本介绍及配置使用

什么是 RestTemplate?

RestTemplate是执行HTTP请求的同步阻塞式的客户端,它在HTTP客户端库(例如JDK HttpURLConnection,Apache HttpComponents,okHttp等)基础封装了更加简单易用的模板方法API。也就是说RestTemplate是一个封装,底层的实现还是java应用开发中常用的一些HTTP客户端。但是相对于直接使用底层的HTTP客户端库,它的操作更加方便、快捷,能很大程度上提升我们的开发效率。

RestTemplate作为spring-web项目的一部分,在Spring 3.0版本开始被引入。RestTemplate类通过为HTTP方法(例如GET,POST,PUT,DELETE等)提供重载的方法,提供了一种非常方便的方法访问基于HTTPWeb服务。如果你的Web服务API基于标准的RESTful风格设计,使用效果将更加的完美

非Spring环境下使用RestTemplate

RestTemplate是spring的一个rest客户端,在spring-web这个包下。这个包虽然叫做spring-web,但是它的RestTemplate可以脱离Spring 环境使用。

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-web</artifactId>
  4. <version>5.2.6.RELEASE</version>
  5. </dependency>

测试一下Hello world,使用RestTemplate发送一个GET请求,并把请求得到的JSON数据结果打印出来。

  1. @Test
  2. public void simpleTest()
  3. {
  4. RestTemplate restTemplate = new RestTemplate();
  5. String url = "http://jsonplaceholder.typicode.com/posts/1";
  6. String str = restTemplate.getForObject(url, String.class);
  7. System.out.println(str);
  8. }

服务端是JSONPlaceholder网站,帮我们提供的服务端API。需要注意的是:"http://jsonplaceholder.typicode.com/posts/1"服务URL,虽然URL里面有posts这个单词,但是它的英文含义是:帖子或者公告,而不是我们的HTTP Post协议。

所以说"http://jsonplaceholder.typicode.com/posts/1",请求的数据是:id为1的Post公告资源。打印结果如下:

Spring环境下使用RestTemplate

将maven坐标从spring-web换成spring-boot-starter-web

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

将RestTemplate配置初始化为一个Bean。这种初始化方法,是使用了JDK 自带的HttpURLConnection作为底层HTTP客户端实现。我们还可以把底层实现切换为Apache HttpComponents,okHttp等,我们后续章节会为大家介绍。

  1. @Configuration
  2. public class ContextConfig {
  3. //默认使用JDK 自带的HttpURLConnection作为底层实现
  4. @Bean
  5. public RestTemplate restTemplate(){
  6. RestTemplate restTemplate = new RestTemplate();
  7. return restTemplate;
  8. }
  9. }

在需要使用RestTemplate 的位置,注入并使用即可。

  1. @Resource //@AutoWired
  2. private RestTemplate restTemplate;

底层HTTP客户端库的切换

RestTemplate只是对其他的HTTP客户端的封装,其本身并没有实现HTTP相关的基础功能。其底层实现是可以配置切换的,我们本小节就带着大家来看一下RestTemplate底层实现,及如何实现底层基础HTTP库的切换。

源码分析

RestTemplate有一个非常重要的类叫做HttpAccessor,可以理解为用于HTTP接触访问的基础类。下图为源码:

从源码中我们可以分析出以下几点信息

  • RestTemplate 支持至少三种HTTP客户端库。

  • java JDK自带的HttpURLConnection是默认的底层HTTP实现客户端

  • SimpleClientHttpRequestFactory,即java JDK自带的HttpURLConnection不支持HTTP协议的Patch方法,如果希望使用Patch方法,需要将底层HTTP客户端实现切换为Apache HttpComponents 或 OkHttp

  • 可以通过设置setRequestFactory方法,来切换RestTemplate的底层HTTP客户端实现类库。

底层实现切换方法

从开发人员的反馈,和网上的各种HTTP客户端性能以及易用程度评测来看,OkHttp 优于 Apache HttpComponents、Apache HttpComponents优于HttpURLConnection。所以我个人更建议大家将底层HTTP实现切换为okHTTP。

切换为okHTTP

首先通过maven坐标将okHTTP的包引入到项目中来

  1. <dependency>
  2. <groupId>com.squareup.okhttp3</groupId>
  3. <artifactId>okhttp</artifactId>
  4. <version>4.7.2</version>
  5. </dependency>

如果是spring 环境下通过如下方式使用OkHttp3ClientHttpRequestFactory初始化RestTemplate bean对象。

  1. @Configuration
  2. public class ContextConfig {
  3. @Bean("OKHttp3")
  4. public RestTemplate OKHttp3RestTemplate(){
  5. RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
  6. return restTemplate;
  7. }
  8. }

如果是非Spring环境,直接new RestTemplate(new OkHttp3ClientHttpRequestFactory()之后使用就可以了。

切换为Apache HttpComponents

与切换为okHTTP方法类似、不再赘述。

  1. <dependency>
  2. <groupId>org.apache.httpcomponents</groupId>
  3. <artifactId>httpclient</artifactId>
  4. <version>4.5.12</version>
  5. </dependency>

使用HttpComponentsClientHttpRequestFactory初始化RestTemplate bean对象

  1. @Bean("httpClient")
  2. public RestTemplate httpClientRestTemplate(){
  3. RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
  4. return restTemplate;
  5. }

设置超时时间

引入依赖之后,就来开始使用吧,任何一个HttpApi我们都可以设置请求的连接超时时间,请求超时时间,如果不设置的话,就可能会导致连接得不到释放,造成内存溢出。这个是我们需要重点注意的点,下面就来看看RestTemplate如何来设置超时时间呢?我们可以在SimpleClientHttpRequestFactory类中设置这两个时间,然后将factory传给RestTemplate实例,设置如下:

  1. @Configuration
  2. public class RestTemplateConfig {
  3. /** * 服务器返回数据(response)的时间 */
  4. private static final Integer READ_TIME_OUT = 6000;
  5. /** * 连接上服务器(握手成功)的时间 */
  6. private static final Integer CONNECT_TIME_OUT = 6000;
  7. @Bean
  8. public RestTemplate restTemplate(){
  9. ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
  10. return new RestTemplate(requestFactory);
  11. }
  12. @Bean
  13. public HttpClient httpClient(){
  14. //默认证书有效
  15. SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
  16. SSLContext sslContext = null;
  17. try {
  18. //信任所有的SSL证书
  19. sslContext = SSLContextBuilder.create().setProtocol(SSLConnectionSocketFactory.SSL)
  20. .loadTrustMaterial((x, y) -> true).build();
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. if (sslContext != null) {
  25. sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
  26. }
  27. // 支持HTTP、HTTPS
  28. Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
  29. .register("http", PlainConnectionSocketFactory.getSocketFactory())
  30. .register("https", sslConnectionSocketFactory)
  31. .build();
  32. PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
  33. connectionManager.setMaxTotal(200);
  34. connectionManager.setDefaultMaxPerRoute(100);
  35. connectionManager.setValidateAfterInactivity(2000);
  36. RequestConfig requestConfig = RequestConfig.custom()
  37. // 服务器返回数据(response)的时间,超时抛出read timeout
  38. .setSocketTimeout(READ_TIME_OUT)
  39. // 连接上服务器(握手成功)的时间,超时抛出connect timeout
  40. .setConnectTimeout(CONNECT_TIME_OUT)
  41. // 从连接池中获取连接的超时时间,超时抛出ConnectionPoolTimeoutException
  42. .setConnectionRequestTimeout(1000)
  43. .build();
  44. return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager).build();
  45. }
  46. }

GET请求使用详解

RestTemplate可以发送HTTP GET请求,经常使用到的方法有两个:

  • getForObject()
  • getForEntity()

二者的主要区别在于,getForObject()返回值是HTTP协议的响应体。getForEntity()返回的是ResponseEntity,ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息。

getForObject() 方法

以String的方式接受请求结果数据

在Spring环境下写一个单元测试用例,以String类型接收响应结果信息

  1. @SpringBootTest
  2. class ResttemplateWithSpringApplicationTests {
  3. @Resource
  4. private RestTemplate restTemplate;
  5. @Test
  6. void testSimple() {
  7. String url = "http://jsonplaceholder.typicode.com/posts/1";
  8. String str = restTemplate.getForObject(url, String.class);
  9. System.out.println(str);
  10. }
  11. }

getForObject第二个参数为返回值的类型,String.class以字符串的形式接受getForObject响应结果,

以POJO对象的方式接受结果数据

在Spring环境下写一个单元测试用例,以java POJO对象接收响应结果信息

  1. @Test
  2. public void testPoJO() {
  3. String url = "http://jsonplaceholder.typicode.com/posts/1";
  4. PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class);
  5. System.out.println(postDTO.toString());
  6. }

输出打印结果如下:

POJO的定义如下,根据JSON String的数据格式定义。

  1. @Data
  2. public class PostDTO {
  3. private int userId;
  4. private int id;
  5. private String title;
  6. private String body;
  7. }

以数组的方式接收请求结果

访问http://jsonplaceholder.typicode.com/posts可以获得JSON数组方式的请求结果

下一步就是我们该如何接收,使用方法也很简单

  1. @Test
  2. public void testArrays() {
  3. String url = "http://jsonplaceholder.typicode.com/posts";
  4. PostDTO[] postDTOs = restTemplate.getForObject(url, PostDTO[].class);
  5. System.out.println("数组长度:" + postDTOs.length);
  6. }

请求的结果被以数组的方式正确接收,输出如下:

  1. 数组长度:100

使用占位符号传参的几种方式

以下的几个请求都是在访问"http://jsonplaceholder.typicode.com/posts/1",只是使用了占位符语法,这样在业务使用上更加灵活。

  • 传参替换使用{?}来表示坑位,根据实际的传参顺序来填充,如下:
  1. url = baseUrl+"?userName={?}&userId={?}";
  2. resultData = restTemplate.getForObject(url, ResultData.class, "张三2",2);
  • 使用占位符的形式传递参数:
  1. String url = "http://jsonplaceholder.typicode.com/{1}/{2}";
  2. PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class, "posts", 1);
  • 另一种使用占位符的形式:
  1. String url = "http://jsonplaceholder.typicode.com/{type}/{id}";
  2. String type = "posts";
  3. int id = 1;
  4. PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class, type, id);
  • 我们也可以使用 map 装载参数:

使用{xx}来传递参数时,这个xx对应的就是map中的key

  1. String url = "http://jsonplaceholder.typicode.com/{type}/{id}";
  2. Map<String,Object> map = new HashMap<>();
  3. map.put("type", "posts");
  4. map.put("id", 1);
  5. PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class, map);

getForObject()方法小结

  1. /** 方法一,直接将参数添加到url上面。 * Retrieve a representation by doing a GET on the specified URL. * The response (if any) is converted and returned. * <p>URI Template variables are expanded using the given URI variables, if any. * @param url the URL 请求地址 * @param responseType the type of the return value 响应体的类型 * @param uriVariables the variables to expand the template 传入的参数 * @return the converted object */
  2. @Nullable
  3. <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
  4. /** 方法二,通过Map来提交参数。 * Retrieve a representation by doing a GET on the URI template. * The response (if any) is converted and returned. * <p>URI Template variables are expanded using the given map. * @param url the URL * @param responseType the type of the return value * @param uriVariables the map containing variables for the URI template * @return the converted object */
  5. @Nullable
  6. <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
  7. /** 方法三,用URI来请求。 * Retrieve a representation by doing a GET on the URL . * The response (if any) is converted and returned. * @param url the URL * @param responseType the type of the return value * @return the converted object */
  8. @Nullable
  9. <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
  1. @Test
  2. public void getForObjectTest() {
  3. String baseUrl = "http://localhost:8081/testRestTemplateApp/getUser.do";
  4. //方法一: 直接拼接参数,推荐使用
  5. String url =baseUrl+"?userName=张三1&userId=1";
  6. ResultData resultData = restTemplate.getForObject(url, ResultData.class);
  7. System.out.println("*****GET直接拼接参数查询返回结果={}" + JSON.toJSONString(resultData));
  8. //方法一:传参替换,推荐使用
  9. url = baseUrl+"?userName={?}&userId={?}";
  10. resultData = restTemplate.getForObject(url, ResultData.class, "张三2",2);
  11. System.out.println("*****GET传参替换查询返回结果={}" + JSON.toJSONString(resultData));
  12. //方法一:传参替换,使用String.format,推荐使用
  13. url = baseUrl + String.format("?userName=%s&userId=%s", "张三2",2);
  14. resultData = restTemplate.getForObject(url, ResultData.class);
  15. System.out.println("******GET使用String.format查询返回结果={}" + JSON.toJSONString(resultData));
  16. //方法二:使用Map,不推荐使用
  17. url = baseUrl + "?userName={userName}&userId={userId}";
  18. Map<String, Object> paramMap = new HashMap<>();
  19. paramMap.put("userName", "张三1");
  20. paramMap.put("userId",1);
  21. resultData = restTemplate.getForObject(url, ResultData.class, paramMap);
  22. System.out.println("******GET使用Map查询返回结果={}" + JSON.toJSONString(resultData));
  23. //方法三:使用URI,不推荐使用
  24. URI uri = URI.create(baseUrl+"?userName=%E5%BC%A0%E4%B8%891&userId=1");
  25. ResultData resultData1 = restTemplate.getForObject(uri, ResultData.class);
  26. System.out.println("******GET使用URI查询返回结果={}" + JSON.toJSONString(resultData1));
  27. }
  • 当响应头是application/json;charset=UTF-8格式的时候,返回的数据类型可以直接写String.class,如下
  1. String url ="http://localhost:8081/testRestTemplateApp/getUser.do?userName=张三1&userId=1";
  2. String resultData = restTemplate.getForObject(url, String.class);
  • 不推荐直接使用方法三传入URI,原因主要有如下两点: 1. 传入的参数包含中文时必须要转码,直接传中文会报400的错误,2. 响应的结果必须要跟接口的返回值保持一致,不然回报406的错误
  1. //userName不能直接传入张三1,不然会报400的错误
  2. URI uri = URI.create(baseUrl+"?userName=%E5%BC%A0%E4%B8%891&userId=1");
  3. //responseType不能传入String.class,不然会报406的错误
  4. ResultData resultData1 = restTemplate.getForObject(uri, ResultData.class);

getForEntity()方法

上面的所有的getForObject请求传参方法,getForEntity都可以使用,使用方法上也几乎是一致的,只是在返回结果接收的时候略有差别。使用ResponseEntity<T> responseEntity来接收响应结果。用responseEntity.getBody()获取响应体。响应体内容同getForObject方法返回结果一致。剩下的这些响应信息就是getForEntity比getForObject多出来的内容。

  • HttpStatus statusCode =responseEntity.getStatusCode();获取整体的响应状态信息
  • int statusCodeValue = responseEntity.getStatusCodeValue(); 获取响应码值
  • HttpHeaders headers = responseEntity.getHeaders();获取响应头

getForEntity()的三个重载方法:

  1. <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
  2. throws RestClientException;
  3. <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
  4. throws RestClientException;
  5. <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;

使用演示:

  1. @Test
  2. public void testEntityPoJo() {
  3. String url = "http://jsonplaceholder.typicode.com/posts/5";
  4. ResponseEntity<PostDTO> responseEntity
  5. = restTemplate.getForEntity(url, PostDTO.class);
  6. PostDTO postDTO = responseEntity.getBody(); // 获取响应体
  7. System.out.println("HTTP 响应body:" + postDTO.toString());
  8. //以下是getForEntity比getForObject多出来的内容
  9. HttpStatus statusCode = responseEntity.getStatusCode(); // 获取响应码
  10. int statusCodeValue = responseEntity.getStatusCodeValue(); // 获取响应码值
  11. HttpHeaders headers = responseEntity.getHeaders(); // 获取响应头
  12. System.out.println("HTTP 响应状态:" + statusCode);
  13. System.out.println("HTTP 响应状态码:" + statusCodeValue);
  14. System.out.println("HTTP Headers信息:" + headers);
  15. }

输出打印结果

POST请求使用详解

其实POST请求方法和GET请求方法上大同小异,RestTemplate的POST请求也包含两个主要方法:

  • postForObject()
  • postForEntity()

二者的主要区别在于,postForObject()返回值是HTTP协议的响应体。postForEntity()返回的是ResponseEntity,ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息。

postForObject发送JSON格式请求

三个重载方法

  1. /** * @param url the URL 请求地址 * @param request the Object to be POSTed (may be {@code null}) 请求体,可以传入一个Bean对象,也可以传入HttpEntity对象,包装请求头 * @param responseType the type of the return value 响应对象的类型 * @param uriVariables the variables to expand the template 传入的参数 * @return the converted object * @see HttpEntity */
  2. @Nullable
  3. <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
  4. Object... uriVariables) throws RestClientException;
  5. /** * @param url the URL 请求地址 * @param request the Object to be POSTed (may be {@code null}) 请求体,可以传入一个Bean对象,也可以传入HttpEntity对象,包装请求头 * @param responseType the type of the return value 响应对象的类型 * @param uriVariables the variables to expand the template 传入的map * @return the converted object * @see HttpEntity */
  6. @Nullable
  7. <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
  8. Map<String, ?> uriVariables) throws RestClientException;
  9. /** * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param responseType the type of the return value * @return the converted object * @see HttpEntity */
  10. @Nullable
  11. <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException;

写一个单元测试用例,测试用例的内容是向指定的URL提交一个Post(帖子).

  1. @SpringBootTest
  2. class PostTests {
  3. @Resource
  4. private RestTemplate restTemplate;
  5. @Test
  6. void testSimple() {
  7. // 请求地址
  8. String url = "http://jsonplaceholder.typicode.com/posts";
  9. // 要发送的数据对象
  10. PostDTO postDTO = new PostDTO();
  11. postDTO.setUserId(110);
  12. postDTO.setTitle("dhy 发布文章");
  13. postDTO.setBody("dhy 发布文章 测试内容");
  14. // 发送post请求,并输出结果
  15. PostDTO result = restTemplate.postForObject(url, postDTO, PostDTO.class);
  16. System.out.println(result);
  17. }
  18. }
  • jsonplaceholder.typicode.com是一个可以提供在线免费RESTful测试服务的一个网站
  • ”/posts"服务接收PostDTO参数对象,并将请求结果以JSON字符串的形式进行响应。响应结果就是请求参数对象对应的JSON字符串。
  • 所以postForObject方法第二个参数是请求数据对象,第三个参数是返回值类型

url支持占位符语法

如果url地址上面需要传递一些动态参数,可以使用占位符的方式:

  1. String url = "http://jsonplaceholder.typicode.com/{1}/{2}";
  2. String url = "http://jsonplaceholder.typicode.com/{type}/{id}";

具体的用法和使用GET方法请求是一致的

注意

  1. @Test
  2. public void testPostForObjectForForm() {
  3. String baseUrl = "http://localhost:8081/testRestTemplateApp/getUser.do";
  4. //方法一:表单提交
  5. MultiValueMap<String, Object> request = new LinkedMultiValueMap<>();
  6. request.set("userName","张三1");
  7. request.set("userId",1);
  8. ResultData resultData = restTemplate.postForObject(baseUrl,request, ResultData.class);
  9. System.out.println("*****POST表单提交使用URI查询返回结果={}" + JSON.toJSONString(resultData));
  10. //方法二:使用URI
  11. URI uri = URI.create(baseUrl);
  12. resultData = restTemplate.postForObject(uri,request, ResultData.class);
  13. System.out.println("******POST使用URI查询返回结果={}" + JSON.toJSONString(resultData));
  14. }

运行结果如下:

从运行结果我们可以看出,如果传入的参数是MultiValueMap类型的对象是,Spring会通过AllEncompassingFormHttpMessageConverter转换器来将参数通过表单提交。

如果直接传入一个Map对象,则会通过MappingJackson2HttpMessageConverter转换器对参数进行转换。

说完了表单提交,下面我们看看另外一种场景,如下,这个接口是一个保存用户数据的接口,参数需要格式化后放在请求体中。

  1. @ResponseBody
  2. @PostMapping("/addUserJSON.do")
  3. public ResultData<Boolean> addUserJSON(@RequestBody User user) {
  4. if (user == null) {
  5. return new ResultData<>(HttpStatus.BAD_REQUEST.value(), null, "参数不能为空");
  6. }
  7. return new ResultData<>(HttpStatus.OK.value(),true,"保存成功");
  8. }

当我们需要调用接口是通过@RequestBody来接受参数时,也就是需要传入一个JSON对象,我们该如何请求呢?

我们调用可以postForObject可以直接传入User对象, 也可以将请求头设置成application/json,然后将User对象序列化,代码如下所示:

  1. @Test
  2. public void testPostForObject() {
  3. String baseUrl = "http://localhost:8081/testRestTemplateApp/addUserJSON.do";
  4. User user = new User();
  5. user.setUserName("李四");
  6. user.setAge(23);
  7. //第一种方式:不传入JSON的参数,不设置请求头
  8. ResultData resultData = restTemplate.postForObject(baseUrl, user, ResultData.class);
  9. System.out.println("*********不序列化传入参数请求结果={}" + JSON.toJSONString(resultData));
  10. //第二种方式:传入JSON类型的参数,设置请求头
  11. HttpHeaders headers = new HttpHeaders();
  12. headers.setContentType(MediaType.APPLICATION_JSON);
  13. HttpEntity httpEntity = new HttpEntity(JSON.toJSONString(user),headers);
  14. resultData = restTemplate.postForObject(baseUrl, httpEntity, ResultData.class);
  15. System.out.println("*********序列化参数请求结果={}" + JSON.toJSONString(resultData));
  16. }

第一种方式是由于Spring内部的MappingJackson2HttpMessageConverter会将参数进行序列化并请求接口

第二种方式是直接设置好请求头为application/json,并将参数序列化。所以就不需要通过MappingJackson2HttpMessageConverter进行转换。比较推荐

运行结果如下:

postForEntity()方法

上面的所有的postForObject请求传参方法,postForEntity都可以使用,使用方法上也几乎是一致的,只是在返回结果接收的时候略有差别。使用ResponseEntity<T> responseEntity来接收响应结果。用responseEntity.getBody()获取响应体。响应体内容同postForObject方法返回结果一致。剩下的这些响应信息就是postForEntity比postForObject多出来的内容。

  • HttpStatus statusCode =responseEntity.getStatusCode();获取整体的响应状态信息
  • int statusCodeValue = responseEntity.getStatusCodeValue(); 获取响应码值
  • HttpHeaders headers = responseEntity.getHeaders();获取响应头
  1. @Test
  2. public void testEntityPoJo() {
  3. // 请求地址
  4. String url = "http://jsonplaceholder.typicode.com/posts";
  5. // 要发送的数据对象
  6. PostDTO postDTO = new PostDTO();
  7. postDTO.setUserId(110);
  8. postDTO.setTitle("dhy 发布文章");
  9. postDTO.setBody("dhy 发布文章 测试内容");
  10. // 发送post请求,并输出结果
  11. ResponseEntity<String> responseEntity
  12. = restTemplate.postForEntity(url, postDTO, String.class);
  13. String body = responseEntity.getBody(); // 获取响应体
  14. System.out.println("HTTP 响应body:" + postDTO.toString());
  15. //以下是postForEntity比postForObject多出来的内容
  16. HttpStatus statusCode = responseEntity.getStatusCode(); // 获取响应码
  17. int statusCodeValue = responseEntity.getStatusCodeValue(); // 获取响应码值
  18. HttpHeaders headers = responseEntity.getHeaders(); // 获取响应头
  19. System.out.println("HTTP 响应状态:" + statusCode);
  20. System.out.println("HTTP 响应状态码:" + statusCodeValue);
  21. System.out.println("HTTP Headers信息:" + headers);
  22. }

输出打印结果

postForLocation() 方法的使用

postForLocation的定义是POST 数据到一个URL返回新创建资源的URL,就是重定向或者页面跳转。

同样提供了三个方法,分别如下,需要注意的是返回结果为URI对象,即网络资源

  1. public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)
  2. throws RestClientException ;
  3. public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)
  4. throws RestClientException ;
  5. public URI postForLocation(URI url, @Nullable Object request) throws RestClientException ;

这类接口主要应用在需要跳转页面的请求,比如,登录,注册,支付等post请求,请求成功之后需要跳转到成功的页面。这种场景下我们可以使用postForLocation了,提交数据,并获取放回的URI,一个测试如下:

首先mock一个接口

  1. @ResponseBody
  2. @RequestMapping(path = "loginSuccess")
  3. public String loginSuccess(String userName, String password) {
  4. return "welcome " + userName;
  5. }
  6. /** * @param userName * @param password * @return */
  7. @RequestMapping(path = "login", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST}
  8. ,produces = "charset/utf8")
  9. public String login(@RequestParam(value = "userName", required = false) String userName,
  10. @RequestParam(value = "password", required = false) String password) {
  11. return "redirect:/loginSuccess?userName=" + userName + "&password=" + password + "&status=success";
  12. }

测试请求是:

  1. @Test
  2. public void testPostLocation() {
  3. String url = "http://localhost:8081/testRestTemplateApp/login";
  4. MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
  5. paramMap.add("userName", "bob");
  6. paramMap.add("password", "1212");
  7. URI location = restTemplate.postForLocation(url, paramMap);
  8. System.out.println("*******返回的数据=" + location);
  9. }

运行结果如下:

HTTP method使用方法详解

RESTful风格与HTTP method

熟悉RESTful风格的朋友,应该了解RESTful风格API使用HTTP method表达对资源的操作。

常用HTTP方法RESTful风格语义(操作)
GET查询、获取数据
POST新增、提交数据
DELETE删除数据
PUT更新、修改数据
HEAD获取HTTP请求头数据
OPTIONS判断URL提供的当前API支持哪些HTTP method方法

在前面的章节,我已经为大家详细的介绍了RestTemplate的GET和POST的相关的使用方法,本节来为大家介绍DELETE、PUT、HEAD、OPTIONS。

使用 DELETE方法去删除资源

删除一个已经存在的资源,使用RestTemplate的delete(uri)方法。该方法会向URL代表的资源发送一个HTTP DELETE方法请求。

  1. @Test
  2. void testDelete() {
  3. String url = "http://jsonplaceholder.typicode.com/posts/1";
  4. restTemplate.delete(url);
  5. }

使用PUT方法去修改资源

修改一个已经存在的资源,使用RestTemplate的put()方法。该方法会向URL代表的资源发送一个HTTP PUT方法请求。

  1. @Test
  2. void testPut() {
  3. // 请求地址
  4. String url = "http://jsonplaceholder.typicode.com/posts/1";
  5. // 要发送的数据对象(修改数据)
  6. PostDTO postDTO = new PostDTO();
  7. postDTO.setUserId(110);
  8. postDTO.setTitle("zimug 发布文章");
  9. postDTO.setBody("zimug 发布文章 测试内容");
  10. // 发送PUT请求
  11. restTemplate.put(url, postDTO);
  12. }

通用请求方法exchange方法

exchange方法是一个通用的方法,它可以发送GET、POST、DELETE、PUT等等HTTP方法请求。

该方法以method方式的请求调用远程RESTFUL服务,其中httpEntity参数用于指定请求参数

  1. public <T> T toPostEntity(String url, HttpEntity httpEntity, Class<T> responseType) {
  2. ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, responseType);
  3. logger.info("请求地址是={},响应结果是={}", url, new Gson().toJson(responseEntity));
  4. //接受请求失败,抛出异常
  5. if (HttpStatus.OK.value() != responseEntity.getStatusCodeValue() || responseEntity.getStatusCode().isError()) {
  6. throw new BusinessException(ErrorCode.RESULT_CODE_ERROR);
  7. }
  8. //接受请求成功
  9. return responseEntity.getBody();
  10. }

下面的两种方式发送GET请求效果是一样的

  1. //使用getForEntity发送GET请求
  2. ResponseEntity<PostDTO> responseEntity
  3. = restTemplate.getForEntity(url, PostDTO.class);
  4. //使用exchange发送GET请求
  5. ResponseEntity<PostDTO> responseEntity = restTemplate.exchange(url, HttpMethod.GET,
  6. null, PostDTO.class);

下面的两种方式发送POST请求效果是一样的

  1. // 使用postForEntity发送POST请求
  2. ResponseEntity<String> responseEntity
  3. = restTemplate.postForEntity(url, postDTO, String.class);
  4. // 使用exchange发送POST请求
  5. ResponseEntity<String> responseEntity
  6. = restTemplate.exchange(url, HttpMethod.POST,null, String.class);

下面的两种方式发送DELETE请求效果是一样的,只是一个有返回值,一个返回值为void

  1. // 使用delete发送DELETE请求,返回值为void
  2. restTemplate.delete(url);
  3. // 使用exchange发送DELETE请求
  4. ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.DELETE,null,String.class);

上面为大家举了几个用exchange()发送请求的例子,exchange()还能针对很多的HTTP method类型发送请求,是通用方法!

使用HEAD方法获取HTTP请求头数据

使用headForHeaders()API 获取某个资源的URI的请求头信息,并且只专注于获取HTTP请求头信息。

  1. @Test
  2. public void testHEAD() {
  3. String url = "http://jsonplaceholder.typicode.com/posts/1";
  4. HttpHeaders httpHeaders = restTemplate.headForHeaders(url);
  5. //断言该资源接口数据为JSON类型
  6. assertTrue(httpHeaders.getContentType()
  7. .includes(MediaType.APPLICATION_JSON));
  8. System.out.println(httpHeaders);
  9. }

使用OPTIONS获取HTTP资源支持的method

下文代码使用optionsForAllow测试该URL资源是否支持GET、POST、PUT、DELETE,即增删改查。

  1. @Test
  2. public void testOPTIONS() {
  3. String url = "http://jsonplaceholder.typicode.com/posts/1";
  4. Set<HttpMethod> optionsForAllow = restTemplate.optionsForAllow(url);
  5. HttpMethod[] supportedMethods
  6. = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE};
  7. //测试该url资源是否支持GET、POST、PUT、DELETE,即增删改查
  8. assertTrue(optionsForAllow.containsAll(Arrays.asList(supportedMethods)));
  9. }

文件上传与下载

文件上传

写一个单元测试类,来完成RestTemplate文件上传功能,具体实现细节参考代码注释

  1. @SpringBootTest
  2. class UpDownLoadTests {
  3. @Resource
  4. private RestTemplate restTemplate;
  5. @Test
  6. void testUpload() {
  7. // 文件上传服务上传接口
  8. String url = "http://localhost:8888/upload";
  9. // 待上传的文件(存在客户端本地磁盘)
  10. String filePath = "D:\\data\\local\\splash.png";
  11. // 封装请求参数
  12. FileSystemResource resource = new FileSystemResource(new File(filePath));
  13. MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
  14. param.add("uploadFile", resource); //服务端MultipartFile uploadFile
  15. //param.add("param1", "test"); //服务端如果接受额外参数,可以传递
  16. // 发送请求并输出结果
  17. System.out.println("--- 开始上传文件 ---");
  18. String result = restTemplate.postForObject(url, param, String.class);
  19. System.out.println("--- 访问地址:" + result);
  20. }
  21. }

输出结果如下:

  1. --- 开始上传文件 ---
  2. --- 访问地址:http://localhost:8888/2020/08/12/028b38f1-3f9b-4088-9bea-1af8c18cd619.png

文件上传之后,可以通过上面的访问地址,在浏览器访问。或者通过RestTemplate客户端进行下载。

文件下载

执行下列代码之后,被下载文件url,会被正确的保存到本地磁盘目录targetPath。

  1. @Test
  2. void testDownLoad() throws IOException {
  3. // 待下载的文件地址
  4. String url = "http://localhost:8888/2020/08/12/028b38f1-3f9b-4088-9bea-1af8c18cd619.png";
  5. //用二进制数组来接收图片的二进制流
  6. ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class);
  7. System.out.println("文件下载请求结果状态码:" + rsp.getStatusCode());
  8. // 将下载下来的文件内容保存到本地
  9. String targetPath = "D:\\data\\local\\splash-down.png";
  10. //Files和Path是nio里面的工具类
  11. //Objects是jdk7以后新增的一个类,里面有很多静态方法有来简化操作
  12. Files.write(Paths.get(targetPath), Objects.requireNonNull(rsp.getBody(),
  13. "未获取到下载文件"));
  14. }

这种下载方法实际上是将下载文件一次性加载到客户端本地内存,然后从内存将文件写入磁盘。这种方式对于小文件的下载还比较适合,如果文件比较大或者文件下载并发量比较大,容易造成内存的大量占用,从而降低应用的运行效率。

大文件的下载

这种下载方式的区别在于

  • 设置了请求头APPLICATION_OCTET_STREAM,表示以流的形式进行数据加载
  • RequestCallback结合File.copy保证了接收到一部分文件内容,就向磁盘写入一部分内容。而不是全部加载到内存,最后再写入磁盘文件。
  1. @Test
  2. void testDownLoadBigFile() throws IOException {
  3. // 待下载的文件地址
  4. String url = "http://localhost:8888/2020/08/12/028b38f1-3f9b-4088-9bea-1af8c18cd619.png";
  5. // 文件保存的本地路径
  6. String targetPath = "D:\\data\\local\\splash-down-big.png";
  7. //定义请求头的接收类型
  8. RequestCallback requestCallback = request -> request.getHeaders()
  9. .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
  10. //对响应进行流式处理而不是将其全部加载到内存中
  11. restTemplate.execute(url, HttpMethod.GET, requestCallback, clientHttpResponse -> {
  12. //处理响应数据
  13. Files.copy(clientHttpResponse.getBody(), Paths.get(targetPath));
  14. return null;
  15. });
  16. }

execute方法是restTemplate的底层实现

注意:使用execute方法调用restFul服务的时候,HttpMessageConverter不会自动起作用,因此开发者需要直接才能够底层I/O级别来发送请求处理响应,因此如果使用excute方法,还想把响应的JSON字符串或者请求参数直接转换为一个pojo对象,会报下面这个错误,原因一开始就说了

请求失败异常处理

异常现象

在使用RestTemplate进行远程接口服务调用的时候,当请求的服务出现异常:超时、服务不存在等情况的时候(响应状态非200、而是400、500HTTP状态码),就会抛出如下异常:

该异常我是模拟出来的,将正确的请求服务地址由“/posts/1”改成“/postss/1”。服务不存在所以抛出404异常。

  1. @Test
  2. public void testEntity() {
  3. String url = "http://jsonplaceholder.typicode.com/postss/1";
  4. ResponseEntity<String> responseEntity
  5. = restTemplate.getForEntity(url, String.class); //这行抛出异常
  6. //下面两行代码执行不到
  7. HttpStatus statusCode = responseEntity.getStatusCode(); // 获取响应码
  8. System.out.println("HTTP 响应状态:" + statusCode);
  9. }

异常抛出之后,程序后面的代码就执行不到了,无法进行后面的代码执行。实际的业务开发中,有的时候我们更期望的结果是:不管你服务端是超时了还是服务不存在,我们都应该获得最终的请求结果(HTTP请求结果状态400、500),而不是获得一个抛出的异常。

源码解析-默认实现

首先我要说一个结论:RestTemplate请求结果异常是可以自定义处理的。在开始进行自定义的异常处理逻辑之前,我们有必要看一下异常处理的默认实现。也就是:为什么会产生上面小节提到的现象?

  • ResponseErrorHandler是RestTemplate请求结果的异常处理器接口

  • DefaultResponseErrorHandler是ResponseErrorHandler的默认实现

所以我们就来看看DefaultResponseErrorHandler是如何来处理异常响应的?

从HttpResponse解析出Http StatusCode,如果状态码StatusCode为null,就抛出UnknownHttpStatusCodeException异常。

如果StatusCode存在,则解析出StatusCode的series,也就是状态码段(除了200段,其他全是异常状态码),解析规则是StatusCode/100取整。

  1. public enum Series {
  2. INFORMATIONAL(1), // 1xx/100
  3. SUCCESSFUL(2), // 2xx/100
  4. REDIRECTION(3), // 3xx/100
  5. CLIENT_ERROR(4), // 4xx/100 ,客户端异常
  6. SERVER_ERROR(5); // 5xx/100 ,服务端异常
  7. }

进一步针对客户端异常和服务端异常进行处理,处理的方法是抛出HttpClientErrorException。也就是第一小节出现的异常的原因

RestTemplate自定义异常处理

所以我们要实现自定义异常,实现ResponseErrorHandler 接口就可以。

  1. public class MyRestErrorHandler implements ResponseErrorHandler {
  2. /** * 判断返回结果response是否是异常结果 * 主要是去检查response 的HTTP Status * 仿造DefaultResponseErrorHandler实现即可 */
  3. @Override
  4. public boolean hasError(ClientHttpResponse response) throws IOException {
  5. int rawStatusCode = response.getRawStatusCode();
  6. HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
  7. return (statusCode != null ? statusCode.isError(): hasError(rawStatusCode));
  8. }
  9. protected boolean hasError(int unknownStatusCode) {
  10. HttpStatus.Series series = HttpStatus.Series.resolve(unknownStatusCode);
  11. return (series == HttpStatus.Series.CLIENT_ERROR || series == HttpStatus.Series.SERVER_ERROR);
  12. }
  13. @Override
  14. public void handleError(ClientHttpResponse response) throws IOException {
  15. // 里面可以实现你自己遇到了Error进行合理的处理
  16. //TODO 将接口请求的异常信息持久化
  17. }
  18. }

将MyRestErrorHandler 在RestTemplate实例化的时候进行注册

这时再去执行第一小节中的示例代码,就不会抛出异常。而是得到一个HTTP Status 404的结果。我们可以根据这个结果,在程序中继续向下执行代码。

自动重试机制

在上一节我们为大家介绍了,当RestTemplate发起远程请求异常时的自定义处理方法,我们可以通过自定义的方式解析出HTTP Status Code状态码,然后根据状态码和业务需求决定程序下一步该如何处理。

本节为大家介绍另外一种通用的异常的处理机制:那就是自动重试。也就是说,在RestTemplate发送请求得到非200状态结果的时候,间隔一定的时间再次发送n次请求。n次请求都失败之后,最后抛出HttpClientErrorException。

在开始本节代码之前,将上一节的RestTemplate自定义异常处理的代码注释掉,否则自动重试机制不会生效。如下(参考上一节代码):

  1. //restTemplate.setErrorHandler(new MyRestErrorHandler());

Spring Retry配置生效

通过maven坐标引入spring-retry,spring-retry的实现依赖于面向切面编程,所以引入aspectjweaver。以下配置过程都是基于Spring Boot应用。

  1. <dependency>
  2. <groupId>org.springframework.retry</groupId>
  3. <artifactId>spring-retry</artifactId>
  4. <version>1.2.5.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.aspectj</groupId>
  8. <artifactId>aspectjweaver</artifactId>
  9. </dependency>

在Spring Boot 应用入口启动类,也就是配置类的上面加上@SpringRetry注解,表示让重试机制生效。

使用案例

  1. @Service
  2. public class RetryService {
  3. @Resource
  4. private RestTemplate restTemplate;
  5. private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  6. @Retryable(value = RestClientException.class, maxAttempts = 3,
  7. backoff = @Backoff(delay = 5000L,multiplier = 2))
  8. public HttpStatus testEntity() {
  9. System.out.println("发起远程API请求:" + DATE_TIME_FORMATTER.format(LocalDateTime.now()));
  10. String url = "http://jsonplaceholder.typicode.com/postss/1";
  11. ResponseEntity<String> responseEntity
  12. = restTemplate.getForEntity(url, String.class);
  13. return responseEntity.getStatusCode(); // 获取响应码
  14. }
  15. }

@Retryable注解的方法在发生异常时会重试,参数说明:

  • value:当指定异常发生时会进行重试,HttpClientErrorException是RestClientException的子类。
  • include:和value一样,默认空。如果 exclude也为空时,所有异常都重试
  • exclude:指定异常不重试,默认空。如果 include也为空时,所有异常都重试
  • maxAttemps:最大重试次数,默认3
  • backoff:重试等待策略,默认空

@Backoff注解为重试等待的策略,参数说明:

  • delay:指定重试的延时时间,默认为1000毫秒
  • multiplier:指定延迟的倍数,比如设置delay=5000,multiplier=2时,第一次重试为5秒后,第二次为10(5x2)秒,第三次为20(10x2)秒。

写一个测试的RetryController 对RetryService 的testEntity方法进行调用

  1. @RestController
  2. public class RetryController {
  3. @Resource
  4. private RetryService retryService;
  5. @GetMapping("/retry")
  6. public HttpStatus test() {
  7. return retryService.testEntity();
  8. }
  9. }

测试结果

向http://localhost:8080/retry发起请求,结果如下:

从结果可以看出:

  • 第一次请求失败之后,延迟5秒后重试
  • 第二次请求失败之后,延迟10秒后重试
  • 第三次请求失败之后,抛出异常

通过BasicAuth认证

服务提供方通常会通过一定的授权、鉴权认证逻辑来保护API接口。其中比较简单、容易实现的方式就是使用HTTP 的Basic Auth来实现接口访问用户的认证。我们本节就来为大家介绍一下,在服务端加入Basic Auth认证的情况下,该如何使用RestTemplate访问服务端接口。

HttpBasic认证原理说明

  • 首先,HttpBasic模式要求传输的用户名密码使用Base64模式进行加密。如果用户名是 "admin" ,密码是“ admin”,则将字符串"admin:admin"使用Base64编码算法加密。加密结果可能是:YWtaW46YWRtaW4=
  • 然后,在Http请求中使用authorization作为一个HTTP请求头Header name“Basic YWtaW46YWRtaW4=“作为Header的值,发送给服务端。(注意这里使用Basic+空格+加密串
  • 服务器在收到这样的请求时,到达BasicAuthenticationFilter过滤器,将提取“authorization”的Header值,并使用用于验证用户身份的相同算法Base64进行解码
  • 解码结果与登录验证的用户名密码匹配,匹配成功则可以继续过滤器后续的访问。

HTTP Basic Auth服务端实现

如果你想自己搭建一个服务端,那么如何为Spring Boot 服务添加Basic Auth认证?

给大家介绍一个提供免费在线的RESTful接口服务的网站:http://httpbin.org/。这个网站为我们提供了Basic Auth认证测试服务接口。如果我们只是为了学习RestTemplate,直接用这个网站提供的服务就可以了。

浏览器访问地址:http://www.httpbin.org/#/Auth/get_basic_auth__user___passwd_ ,这个接口服务是通过OpenAPI(swagger)实现的,所以可以进行在线的访问测试。所以可以先通过页面操作测试一下,再开始下面学习使用RestTemplate访问服务端接口。

请求头方式携带认证信息

在HTTP请求头中携带Basic Auth认证的用户名和密码,具体实现参考下文代码注释:

  1. @SpringBootTest
  2. class BasicAuthTests {
  3. @Resource
  4. private RestTemplate restTemplate;
  5. @Test
  6. void testBasicAuth() {
  7. //该url上携带用户名密码是httpbin网站测试接口的要求,
  8. //真实的业务是不需要在url上体现basic auth用户名密码的
  9. String url = "http://www.httpbin.org/basic-auth/admin/adminpwd";
  10. //在请求头信息中携带Basic认证信息(这里才是实际Basic认证传递用户名密码的方式)
  11. HttpHeaders headers = new HttpHeaders();
  12. headers.set("authorization",
  13. "Basic " +
  14. Base64.getEncoder()
  15. .encodeToString("admin:adminpwd".getBytes()));
  16. //发送请求
  17. HttpEntity<String> ans = restTemplate
  18. .exchange(url,
  19. HttpMethod.GET, //GET请求
  20. new HttpEntity<>(null, headers), //加入headers
  21. String.class); //body响应数据接收类型
  22. System.out.println(ans);
  23. }
  24. }

测试用例执行成功,说明RestTemplate 正确的携带了Basic 认证信息,得到正常的响应结果:200。

拦截器方式携带认证信息

上面的代码虽然实现了功能,但是不够好。因为每一次发送HTTP请求,我们都需要去组装HttpHeaders 信息,这样不好,造成大量的代码冗余。那么有没有一种方式可以实现可以一次性的为所有RestTemplate请求API添加Http Basic认证信息呢?答案就是:在RestTemplate Bean初始化的时候加入拦截器,以拦截器的方式统一添加Basic认证信息。

  1. @Configuration
  2. public class ContextConfig {
  3. @Bean("OKHttp3")
  4. public RestTemplate OKHttp3RestTemplate(){
  5. RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
  6. //添加拦截器
  7. restTemplate.getInterceptors().add(getCustomInterceptor());
  8. return restTemplate;
  9. }
  10. //实现一个拦截器:使用拦截器为每一个HTTP请求添加Basic Auth认证用户名密码信息
  11. private ClientHttpRequestInterceptor getCustomInterceptor(){
  12. ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, execution) -> {
  13. httpRequest.getHeaders().set("authorization",
  14. "Basic " +
  15. Base64.getEncoder()
  16. .encodeToString("admin:adminpwd".getBytes()));
  17. return execution.execute(httpRequest, bytes);
  18. };
  19. return interceptor;
  20. }
  21. //这段代码是《第3节-底层HTTP客户端实现切换》的内容
  22. private ClientHttpRequestFactory getClientHttpRequestFactory() {
  23. int timeout = 100000;
  24. OkHttp3ClientHttpRequestFactory clientHttpRequestFactory
  25. = new OkHttp3ClientHttpRequestFactory();
  26. clientHttpRequestFactory.setConnectTimeout(timeout);
  27. return clientHttpRequestFactory;
  28. }
  29. }

在RestTemplate Bean初始化的时候加入拦截器之后,上面的代码就可以省略HttpHeaders Basic Auth请求头携带信息的组装过程。发送请求,结果和上面的效果是一样的。

进一步简化

上面的方式使用了拦截器,但仍然是我们自己来封装HTTP headers请求头信息。进一步的简化方法就是,Spring RestTemplate 已经为我们提供了封装好的Basic Auth拦截器,我们直接使用就可以了,不需要我们自己去实现拦截器。

下面的方法是在RestTemplate Bean实例化的时候使用RestTemplateBuilder,自带basicAuthentication。所以到这里拦截器也不需要了(实际底层代码实现仍然是拦截器,只是api层面不需要指定拦截器了)。

发送请求,结果和第三小节中的效果是一样的。

这里没有对RestTemplateBuilder和拦截器进行深入分析,大家可以自行查阅资料了解,包括还可以替换消息转换器等功能,由于篇幅原因,这里就不多讲了

总结

介绍完了restTemplate的常用方法,但是,我们或许会感觉到restTemplate的方法太多了,调用起来不太方便,为了使用方便,我们就对restTemplate做一个封装。代码如下所示:主要封装成了四个方法,一个是通过get请求的方法,一个是通过表单提交的post请求方法,一个是通过json提交的post请求方法,最后就是上传图片的方法。

  1. @Component
  2. public class RestTemplateProxy {
  3. @Autowired
  4. private RestTemplate restTemplate;
  5. /** * * @param url 请求地址 * 参数可以通过 http://localhost:8888/juheServer/juhe/info/queryCustomer.do?taxNo=92330424MA29G7GY5W * 或者 http://localhost:8888/juheServer/juhe/info/queryCustomer.do+String.format("?taxNo=%s&order=%s", "92330424MA29G7GY5W","1212121212"); * @param responseType 返回值的类型 * @return * @author xiagwei * @date 2020/3/5 5:28 PM * */
  6. public <T> T getForObject(String url, Class<T> responseType) {
  7. return restTemplate.getForObject(url, responseType);
  8. }
  9. /** * 通过json的方式请求服务,不需要将数据格式化,直接将请求对象传入即可 * 可以是map,可以是一个bean * @param url 请求接口 * @param requestParam 请求实体 * @param responseType 返回对象的clazz * @return * @author xiagwei * @date 2020/3/5 5:36 PM */
  10. public <T> T postForObjectJSON(String url, Object requestParam,Class<T> responseType) {
  11. HttpHeaders headers = new HttpHeaders();
  12. headers.setContentType(MediaType.APPLICATION_JSON);
  13. HttpEntity httpEntity = new HttpEntity(requestParam, headers);
  14. return restTemplate.postForObject(url, httpEntity, responseType);
  15. }
  16. /** * 通过Form表单的方式提交 * @param url 请求接口 * @param requestParam 请求实体,可以是一个实体,也可以一个map * @param responseType 返回对象的clazz * @return * @author xiagwei * @date 2020/3/5 5:42 PM */
  17. public <T> T postForObjectForm(String url, @NotNull Object requestParam, Class<T> responseType) {
  18. MultiValueMap<String, Object> valueRequestMap = createValueMap(requestParam);
  19. return restTemplate.postForObject(url, valueRequestMap, responseType);
  20. }
  21. /** * 最通用的请求方法 * * @param url 请求的URL * @param requestParam 请求参数 * @param headers 请求头 * @param response 响应结果的类型 * @return * @date 2021/3/10 14:21 */
  22. public <T> T postForEntityHeader(String url, Object requestParam, HttpHeaders headers, Class<T> response) {
  23. MultiValueMap<String, Object> requestEntity = createValueMap(requestParam);
  24. HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(requestEntity,headers);
  25. return restTemplate.postForObject(url, httpEntity, response);
  26. }
  27. /** * 图片上传 * * @param url 请求地址 * @param body 请求体 * MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("uploadFile", new FileSystemResource(ImageUtil.downloadImgByUrl(url))); * @param responseType 返回结果的clazz对象 * @return * @author xiagwei * @date 2020/3/5 6:05 PM */
  28. public <T> T uploadImg(@NotNull String url, @NotNull MultiValueMap<String, Object> body,Class<T> responseType) {
  29. HttpHeaders headers = new HttpHeaders();
  30. headers.setContentType(MediaType.MULTIPART_FORM_DATA);
  31. HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
  32. return restTemplate.postForObject(url,requestEntity,responseType);
  33. }
  34. /** * 基础的请求方法 * * @param url * @param httpEntity * @param responseType * @return * @Author weixiang * @date 2020/3/5 6:05 PM */
  35. public <T> T toPostEntity(String url, HttpEntity httpEntity, Class<T> responseType) {
  36. ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, responseType);
  37. logger.info("请求地址是={},响应结果是={}", url, new Gson().toJson(responseEntity));
  38. //接受请求失败,抛出异常
  39. if (HttpStatus.OK.value() != responseEntity.getStatusCodeValue() || responseEntity.getStatusCode().isError()) {
  40. throw new BusinessException(ErrorCode.RESULT_CODE_ERROR);
  41. }
  42. //接受请求成功
  43. return responseEntity.getBody();
  44. }
  45. private MultiValueMap createValueMap(Object requestParam) {
  46. MultiValueMap<String, Object> valueRequestMap = new LinkedMultiValueMap<>();
  47. Map<String, Object> param = null;
  48. if (requestParam instanceof Map) {
  49. param = (Map<String, Object>) requestParam;
  50. } else {
  51. param = BeanUtil.beanToMap(requestParam);
  52. }
  53. for (String key : param.keySet()) {
  54. valueRequestMap.add(key, param.get(key));
  55. }
  56. return valueRequestMap;
  57. }
  58. }

这里需要重点说下,图片上传的方法,上传图片的话,我们一定要把请求头设置成multipart/form-data,然后其余的参数通过MultiValueMap来设置。

  1. public <T> T uploadImg(@NotNull String url, @NotNull MultiValueMap<String, Object> body,Class<T> responseType) {
  2. HttpHeaders headers = new HttpHeaders();
  3. headers.setContentType(MediaType.MULTIPART_FORM_DATA);
  4. HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
  5. return restTemplate.postForObject(url,requestEntity,responseType);
  6. }

Spring RestTemplate为何必须搭配MultiValueMap?

Spring RestTemplate为何必须搭配MultiValueMap?

一言蔽之:MultiValueMap会以表单形式提交给服务器端,而HashMap会以json请求体形式提交过去

相关文章