Java教程

Spring RestTemplate

本文主要是介绍Spring RestTemplate,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在服务的调用过程中,使用到了一个组件,叫做 RestTemplate.

RestTemplate 是由 Spring 提供的一个 HTTP 请求工具。可以减少我们平时开发常使用的 HttpClient API 依赖。

其实开发者也可以不使用 RestTemplate ,使用 Java 自带的 HttpUrlConnection 或者经典的网络访问框架 HttpClient 也可以完成,只是在 Spring 项目中,使用 RestTemplate 显然更方便一些。

RestTemplate 简介

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供常见的REST请求方案的模版,

例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。

Synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries such as the JDK HttpURLConnection, Apache HttpComponents, and others. The RestTemplate offers templates for common scenarios by HTTP method, in addition to the generalized exchange and execute methods that support of less frequent cases.

RestTemplate 采用同步方式执行 HTTP 请求的类,底层使用 JDK 原生 HttpURLConnection API ,或者 HttpComponents等其他 HTTP 客户端请求类库。还有一处强调的就是 RestTemplate 提供模板化的方法让开发者能更简单地发送 HTTP 请求。

值得注意的是, RestTemplate 类是在 Spring Framework 3.0 开始引入的,这里我们使用的 Spring 版本为当前最新的 GA 版本 5.1.6。而在 5.0 以上,官方标注了更推荐使用非阻塞的响应式 HTTP 请求处理类 org.springframework.web.reactive.client.WebClient 来替代 RestTemplate,尤其是对应异步请求处理的场景上 。

RestTemplate API

方法名 描述
getForObject 通过 GET 请求获得响应结果
getForEntity 通过 GET 请求获取 ResponseEntity 对象,包容有状态码,响应头和响应数据
headForHeaders 以 HEAD 请求资源返回所有响应头信息
postForLocation 用 POST 请求创建资源,并返回响应数据中响应头的字段 Location 的数据
postForObject 通过 POST 请求创建资源,获得响应结果
put 通过 PUT 方式请求来创建或者更新资源
patchForObject 通过 PATH 方式请求来更新资源,并获得响应结果。(JDK HttpURLConnection 不支持 PATH 方式请求,其他 HTTP 客户端库支持)
delete 通过 DELETE 方式删除资源
optionsForAllow 通过 ALLOW 方式请求来获得资源所允许访问的所有 HTTP 方法,可用看某个请求支持哪些请求方式
exchange 更通用版本的请求处理方法,接受一个 RequestEntity 对象,可以设置路径,请求头,请求信息等,最后返回一个 ResponseEntity 实体
execute 最通用的执行 HTTP 请求的方法,上面所有方法都是基于 execute 的封装,全面控制请求信息,并通过回调接口获得响应数据

RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。

image-20211121225255598

使用方法

注入RestTemplate对象

@Bean注入RestTemplate的实际new对象

@Configuration
public class ConfigBean {
    //@Configuration -- spring  applicationContext.xml
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

然后在Controller类中进行@Autowired

@RestController
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    private String url = "http://localhost:8081/user/";
    @GetMapping("/order/{id}")
    public User getOrder(@PathVariable Long id){
        //访问提供者获取数据
        User user = restTemplate.getForObject(url + id,User.class);
        return user;
    }
}

此时就会注入成功得到restTemplate对象

Example

get

尝试用 RestTemplate 访问请求路径为 product/get_product1一个不带任何参数 的 GET 请求,

@Test
public void testGet_product1() {
   String url = "http://localhost:8080/product/get_product1";
   //方式一:GET 方式获取 JSON 串数据
   String result = restTemplate.getForObject(url, String.class);
   System.out.println("get_product1返回结果:" + result);
   Assert.hasText(result, "get_product1返回结果为空");
   //方式二:GET 方式获取 JSON 数据映射后的 Product 实体对象   
   //Product product = restTemplate.getForObject(url, Product.class);
   System.out.println("get_product1返回结果:" + product);
   Assert.notNull(product, "get_product1返回结果为空");
   //方式三:GET 方式获取包含 Product 实体对象 的响应实体 ResponseEntity 对象,用 getBody() 获取
   ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class);
   System.out.println("get_product1返回结果:" + responseEntity);
   Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product1响应不成功");
}

控制台的输出日志

...
get_product1返回结果:{"id":1,"name":"ProductA","price":6666.0}
...
get_product1返回结果:Product{id='1', name='ProductA', price='6666.0'}
...
get_product1返回结果:<200,Product{id='1', name='ProductA', price='6666.0'},[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Thu, 09 May 2019 15:37:25 GMT"]>
...

使用 RestTemplate API 中 exchangeexecute 方法发送 GET 请求,可以更加细粒度控制请求的行为,如 Header 信息,数据处理方式等,同样在 testGet_product1 方法里添加代码如下:

@Test
public void testGet_product1() {
    String url = "http://localhost:8080/product/get_product1";
    //....
    
    //方式一: 构建请求实体 HttpEntity 对象,用于配置 Header 信息和请求参数
    MultiValueMap header = new LinkedMultiValueMap();
    header.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    HttpEntity<Object> requestEntity = new HttpEntity<>(header);
  
    //方式二: 执行请求获取包含 Product 实体对象 的响应实体 ResponseEntity 对象,用 getBody() 获取
    ResponseEntity<Product> exchangeResult = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Product.class);
    System.out.println("get_product1返回结果:" + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "get_product1响应不成功");

    //方式三: 根据 RequestCallback 接口实现类设置Header信息,用 ResponseExtractor 接口实现类读取响应数据
    String executeResult = restTemplate.execute(url, HttpMethod.GET, request -> {
        request.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    }, (clientHttpResponse) -> {
        InputStream body = clientHttpResponse.getBody();
        byte[] bytes = new byte[body.available()];
        body.read(bytes);
        return new String(bytes);
    }); 
    // 备注:这里使用了 Java8 特性:Lambda 表达式语法,若未接触 Lambda 表达式后可以使用匿名内部类代替实现
    System.out.println("get_product1返回结果:" + executeResult);
    Assert.hasText(executeResult, "get_product1返回结果为空");
}

控制台的输出日志

...
get_product1返回结果:<200,Product{id='1', name='ProductA', price='6666.0'},[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Thu, 09 May 2019 16:00:22 GMT"]>
...
get_product1返回结果:{"id":1,"name":"ProductA","price":6666.0}
...

执行带有参数的 GET 请求

@Test
public void testGet_product2() {
    String url = "http://localhost:8080/product/get_product2/id={id}";
	
    //方式一:将参数的值存在可变长度参数里,按照顺序进行参数匹配
    ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class, 101);
    System.out.println(responseEntity);
    Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product2 请求不成功");
    Assert.notNull(responseEntity.getBody().getId(), "get_product2  传递参数不成功");

    //方式二:将请求参数以键值对形式存储到 Map 集合中,用于请求时URL上的拼接
    Map<String, Object> uriVariables = new HashMap<>();
    uriVariables.put("id", 101);
    Product result = restTemplate.getForObject(url, Product.class, uriVariables);
    System.out.println(result);
    Assert.notNull(result.getId(), "get_product2  传递参数不成功");
}

...
<200,Product{id='101', name='ProductC', price='6666.0'},[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Fri, 10 May 2019 14:53:41 GMT"]>
...
Product{id='101', name='ProductC', price='6666.0'}
...

POST 请求

由于 POST 请求数据的内容类型 Content-Type 不同,发送 POST 请求情况相对就多了,我们这里以常用的 application/x-www-form-urlencodedapplication/json 这两种内容类型为例子。

Content-Typeapplication/x-www-form-urlencoded

@Test
public void testPost_product1() {
    String url = "http://localhost:8080/product/post_product1";
	Product product = new Product(201, "Macbook", BigDecimal.valueOf(10000));
    // 设置请求的 Content-Type 为 application/x-www-form-urlencoded
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
    
    //方式二: 将请求参数值以 K=V 方式用 & 拼接,发送请求使用
    String productStr = "id=" + product.getId() + "&name=" + product.getName() + "&price=" + product.getPrice();
    HttpEntity<String> request = new HttpEntity<>(productStr, header);
    ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    System.out.println("post_product1: " + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 请求不成功");

    //方式一: 将请求参数以键值对形式存储在 MultiValueMap 集合,发送请求时使用
    MultiValueMap<String, Object> map = new LinkedMultiValueMap();
    map.add("id", (product.getId()));
    map.add("name", (product.getName()));
    map.add("price", (product.getPrice()));
    HttpEntity<MultiValueMap> request2 = new HttpEntity<>(map, header);
    ResponseEntity<String> exchangeResult2 = restTemplate.exchange(url, HttpMethod.POST, request2, String.class);
    System.out.println("post_product1: " + exchangeResult2);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 请求不成功");
}

对应的输出日志如下:

...
post_product1: <200,Product{id='201', name='Macbook', price='10000'},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"48", Date:"Fri, 10 May 2019 16:07:43 GMT"]>
...
post_product1: <200,Product{id='201', name='Macbook', price='10000'},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"48", Date:"Fri, 10 May 2019 16:07:43 GMT"]>

发送 Content-Typeapplication/json 的 POST 请求:

@Test
public void testPost_product2() {
    String url = "http://localhost:8080/product/post_product2";
    
   	// 设置请求的 Content-Type 为 application/json
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
  
    // 设置 Accept 向服务器表明客户端可处理的内容类型
    header.put(HttpHeaders.ACCEPT, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
  
    // 直接将实体 Product 作为请求参数传入,底层利用 Jackson 框架序列化成 JSON 串发送请求
    HttpEntity<Product> request = new HttpEntity<>(new Product(2, "Macbook", BigDecimal.valueOf(10000)), header);
    ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    System.out.println("post_product2: " + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product2 请求不成功");
}

对应的输出日志如下:

...
post_product1: <200,Product{id='201', name='Macbook', price='10000'},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"48", Date:"Fri, 10 May 2019 16:07:43 GMT"]>
...
post_product1: <200,Product{id='201', name='Macbook', price='10000'},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"48", Date:"Fri, 10 May 2019 16:07:43 GMT"]>
复制代码

Content-Typeapplication/json

@Test
public void testPost_product2() {
    String url = "http://localhost:8080/product/post_product2";
    
   	// 设置请求的 Content-Type 为 application/json
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
    
    // 设置 Accept 向服务器表明客户端可处理的内容类型
    header.put(HttpHeaders.ACCEPT, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
    
    // 直接将实体 Product 作为请求参数传入,底层利用 Jackson 框架序列化成 JSON 串发送请求
    HttpEntity<Product> request = new HttpEntity<>(new Product(2, "Macbook", BigDecimal.valueOf(10000)), header);
    ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    System.out.println("post_product2: " + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product2 请求不成功");
}

验证的输出日志如下:

···
post_product2: <200,Product{id='2', name='Macbook', price='10000'},[Content-Type:"application/json;charset=UTF-8", Content-Length:"46", Date:"Fri, 10 May 2019 16:09:11 GMT"]>
···
复制代码

DELETE 请求 和 PUT 请求

DELETE 请求和 PUT 请求属于 RESTful 请求方式的两种,但通常不会被使用到,这里也只是简单演示下,具体代码如下:

// DELETE 方法请求
@Test
public void testDelete() {
   String url = "http://localhost:8080/product/delete/{id}";
   restTemplate.delete(url, 101);
}

// PUT 方法请求
@Test
public void testPut() {
    String url = "http://localhost:8080/product/update";
    Map<String, ?> variables = new HashMap<>();
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
    Product product = new Product(101, "iWatch", BigDecimal.valueOf(2333));
    String productStr = "id=" + product.getId() + "&name=" + product.getName() + "&price=" + product.getPrice();
    HttpEntity<String> request = new HttpEntity<>(productStr, header);
    restTemplate.put(url, request);
}

上传文件

@Test
public void testUploadFile() {
    String url = "http://localhost:8080/product/upload";
  
  	//设置body
    MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
    FileSystemResource file = new FileSystemResource(new File("/Users/One/Desktop/b.txt"));
    body.add("file", file);
  
  	//设置请求方式
    MultiValueMap<String, String> header = new LinkedMultiValueMap();
    header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.MULTIPART_FORM_DATA_VALUE));
  
  	//发送请求
    HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, header);
    ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
    System.out.println("upload: " + responseEntity);
    Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "upload 请求不成功");
}

如果需要上传文件类型数据,就只能使用 POST 请求,并且内容类型为 multipart/form-data,需要手动给 Header 指定这个 Content-Type。而需要上传的文件可以用 FileSystemResource 对象封装,表示了一个文件资源,同时服务端需要用 MultipartRequest 对象来获取文件数据。结合已运行的 Web 服务,运行上述测试方法即可得到下面日志输出:

...
upload: <200,upload success filename: b.txt,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"30", Date:"Fri, 10 May 2019 17:00:45 GMT"]>
...

进阶 RestTemplate

The default constructor uses java.net.HttpURLConnection to perform requests. You can switch to a different HTTP library with an implementation of ClientHttpRequestFactory. There is built-in support for the following:

  • Apache HttpComponents
  • Netty
  • OkHttp

RestTemplate 默认使用 JDK 原生的 java.net.HttpURLConnection 执行请求。而除此之外,Spring 还封装了 Apache HttpComponents, Netty, OkHttp 三种请求库,第一个就是我们平常用的 HttpClient API 相关的库,而 Netty 则是一个性能高的NIO 请求处理网络库,OkHttp 为功能丰富且高效的网络框架,多用于 Android 程序。

上文采用默认的构造器方法创建的 RestTemplate 实例,即采用了 JDK 原生的网络 API。

想要切换构造器,只需要在构造方法中传入特定 ClientHttpRequestFactory 实现类即可

@Configuration
public class ConfigBean {
    //@Configuration -- spring  applicationContext.xml
    @Bean
    public RestTemplate getRestTemplate() {
      	RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
        return template;
    }
}

请求超时设置

通常我们会对 HTTP 请求类进行执行行为的定制,例如调用超时时间设置,连接时长的限制等,而采用默认的 HttpURLConnection 默认的配置时, 从 SimpleClientHttpRequestFactory 源码类可以看到是没有超时限制,也就意味着无限等待请求响应

// RestTemplate 默认超时设置
...
private int connectTimeout = -1;
private int readTimeout = -1;
...

那么我们该如何调整超时时间,可以参考如下代码:

@Configuration
public class ConfigBean {
    //@Configuration -- spring  applicationContext.xml
    @Bean
    public RestTemplate getRestTemplate() {
      	RestTemplate template = new RestTemplate(getClientHttpRequestFactory());
        return template;
    }
  `
    private SimpleClientHttpRequestFactory getClientHttpRequestFactory() {
      SimpleClientHttpRequestFactory clientHttpRequestFactory
        = new SimpleClientHttpRequestFactory();
      // 连接超时设置 10s
      clientHttpRequestFactory.setConnectTimeout(10_000);

      // 读取超时设置 10s
      clientHttpRequestFactory.setReadTimeout(10_000);
      return clientHttpRequestFactory;
    }
}

参考

转载:掌握 Spring 之 RestTemplate - 掘金

这篇关于Spring RestTemplate的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!