Master Spring's RestTemplate

22019.05.11 15:56:51 Word number 2787 Reading 1094

Preface

In the field of Java server-side development, spring is an unavoidable topic, especially the concept of micro service is prevalent now. The emergence of Spring Boot has injected new vitality into spring, in addition to Spring Cloud, these frameworks make the spring technical system richer. Spring has been iterating from version 1.0.0 in 2014 to version 5.2.0 M1. Following the development of Java language, spring has introduced new features and functions. This article focuses on the content of RestTemplate in the spring framework, which can reduce the dependency of HttpClient API that we often use in normal development. For the example Demo involved in this article, see Github address: Resttemplate demo  . Thank you for your careful proofreading. If you have any technical problems or article mistakes, please leave a message to contact us and discuss with us It is.

Meet RestTemplate

First of all, before we learn to use RestTemplate, let's recognize this class to see how Spring officially describes it.
From official API documentation RestTemplate javadoc You can find the description of this class as follows:

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.

It is clear from here that RestTemplate uses synchronous HTTP request classes, and the underlying layer uses JDK native HttpURLConnection API, or HttpComponents and other HTTP client request class libraries. Another point is that RestTemplate provides a templating method to make it easier for developers to send HTTP requests.

It is worth noting that the RestTemplate class was introduced in Spring Framework 3.0. Here we use the latest version of Spring, GA version 5.1.6. However, above 5.0, it is officially noted that non blocking responsive HTTP request processing class org.springframework.web.reactive.client.WebClient is more recommended to replace RestTemplate, especially for asynchronous request processing scenarios.

Here we will briefly summarize what is RestTemplate: RestTemplate is a Spring encapsulated class that processes synchronous HTTP requests. How to use this class for HTTP request operations can be seen in the actual part of this article.

Next, let's look at the API s provided by the RestTemplate class. RestTemplate provides nearly 30 request methods, most of which are single method overload implementations. Here I mainly refer to the official documents rest-client-access They are classified as follows:

Method name describe
getForObject GET the response result through GET request
getForEntity GET the ResponseEntity object through GET request, containing status code, response header and response data
headForHeaders Return all response header information as HEAD request resource
postForLocation Create a resource with a POST request and return the data of the field Location of the response header in the response data
postForObject Create resources through PATCH request and get response results
put Create or update resources through PUT request
patchForObject Update the resource by PATH request and get the response result. (JDK HttpURLConnection does not support PATH request, other HTTP client libraries support it)
delete DELETE resources by DELETE
optionsForAllow All HTTP methods that resources are allowed to access are obtained through the ALLOW request. You can see which request methods a request supports
exchange A more general version of the request processing method accepts a RequestEntity object, which can set the path, request header, request information, etc., and finally returns a ResponseEntity entity
execute The most common way to execute HTTP requests. All of the above methods are based on the encapsulation of execute. They control the request information comprehensively and obtain the response data through the callback interface

You can't remember all the methods. For a better understanding, you can simply look at the class hierarchy of RestTemplate. You can see it through the official source code:

/**
 * Interface specifying a basic set of RESTful operations.
 * Implemented by {@link RestTemplate}. Not often used directly, but a useful
 * option to enhance testability, as it can easily be mocked or stubbed.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @since 3.0
 * @see RestTemplate
 */
public interface RestOperations {
                        ...
}

In fact, the request methods of the RestTemplate class are all from the RestOperations interface. According to this name, we can roughly know that this interface mainly provides the interface for RESTful request operations, such as GET, POST, PUT, DELETE, etc. for details, see RestOperation javadoc.

 
RestTemplate class hierarchy

About RESTful:

From the definition of Wikipedia: presentation layer state transition, a design to provide World Wide Web Services Software construction style , also known as REST.

Use URL to locate resources and HTTP verbs to describe operations, such as get, post, delete and put. Simply speaking, you can know what resources are accessed through URL, what operations are performed through HTTP Method, and the results are known through HTTP Status Code.

Practical RestTemplate

OK, after we have a brief understanding of the RestTemplate class, let's try it out and see how to use it.

1. Generate Demo project and import IDE

In order to build a Demo quickly, we use the Spring Boot framework. First, we use the official Spring Initializr To generate a quick build project skeleton, select Spring Boot version 2.1.4, and its underlying dependent Spring Framework version is the latest release version 5.1.6. Only one Web module can be selected for POM dependency, which is convenient for building Web applications quickly.

 
spring initializr

 

Click the generate project button to download to the compression package of the project. After decompression, import the project with your common IDE. The project structure is as follows:

 
Project skeleton

ResttemplateApplication.java is the boot class of the whole program, which is used to start the project.

2. Write request controller class ProductController

First, in order to use RestTemplate to send HTTP requests in various ways, first build a local product Controller that accepts HTTP requests, create a new package, com.one.learn.resttemplate.controller, and create a new product Controller, ProductController. The code is as follows:

@RequestMapping("/product")
@RestController
public class ProductController {

    @GetMapping("/get_product1")
    public Product get_product1() {
        return new Product(1, "ProductA", BigDecimal.valueOf(6666.0));
    }

    @GetMapping("/get_product2")
    public Product get_product2(Integer id) {
        return new Product(id, "ProductC", BigDecimal.valueOf(6666.0));
    }

    @GetMapping("/get_product3")
    public String get_product3(Product product) {
        return product.toString();
    }


    @PostMapping("/post_product1")
    public String post_product1(Product product) {
        return product.toString();
    }

    @PostMapping("/post_product2")
    public String post_product2(@RequestBody Product product) {
        return product.toString();
    }

    @DeleteMapping("/delete/{id}")
    public String delete(@PathVariable Integer id) {
        String result = String.format("Number is%s Products deleted successfully", id);
        System.out.println(result);
        return result;
    }

    @PutMapping("/update")
    public String updateByPut(Product product) {
        String result = product.toString() + " Update success";
        System.out.println(result);
        return result;
    }

    @PostMapping("/upload")
    public String upload(MultipartRequest request) {
           // Spring MVC uses MultipartRequest to accept HTTP requests with files
        MultipartFile file = request.getFile("file"); 
        String originalFilename = file.getOriginalFilename();
        return "upload success filename: " + originalFilename;
    }
}

The entity class Product involved in the Product controller is created in the package com.one.learn.resttemplate.bean. The code is as follows:

public class Product {
    private Integer id;
    private String name;
    private BigDecimal price;
    
    public Product() {
    }
    
    public Product(Integer id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

                // Omit setter getter method
    
    @Override
    public String toString() {
        return "Product{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}

With these classes, you can use the program guidance class ResttemplateApplication to start the Spring Boot project. A simple Web application is born. Listen to port 8080, and the result is as follows:

 
Project initiation

 

We can simply test it. Open the browser and visit http: / / localhost: 8080 / product / get & product1. You will see the result as shown in the figure:

 
Project access

3. Write test class to send HTTP request with RestTemplate

With Web services, it's time to use RestTemplate to send requests and process responses. We create a new test class com.one.learn.resttemplate.RestTemplateTests under the test file. The code is as follows:

public class RestTemplateTests {
    RestTemplate restTemplate = null;

    @Before
    public void setup() {
        restTemplate = new RestTemplate();
    }
    
}

Here, we write a test method to use the RestTemplate API to implement the request for each interface of the Product controller.

GET request

Let's start with the simplest step. Try to use RestTemplate to access the request path to product / GET ﹣ product1, a GET request without any parameters. The code is as follows:

@Test
public void testGet_product1() {
   String url = "http://localhost:8080/product/get_product1";
   //Method 1: GET the JSON string data
   String result = restTemplate.getForObject(url, String.class);
   System.out.println("get_product1 Return result:" + result);
   Assert.hasText(result, "get_product1 Return result is empty");
    
       //Method 2: GET the Product entity object after JSON data mapping
   Product product = restTemplate.getForObject(url, Product.class);
   System.out.println("get_product1 Return result:" + product);
   Assert.notNull(product, "get_product1 Return result is empty");
    
       //Method 3: GET the response entity ResponseEntity object containing the Product entity object, and GET it with getBody()
   ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class);
   System.out.println("get_product1 Return result:" + responseEntity);
   Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product1 Unsuccessful response");
   
}

First, look at the output log of the console after running the test method testget ﹣ product1:

...
get_product1 Return result:{"id":1,"name":"ProductA","price":6666.0}
...
get_product1 Return result: Product{id='1', name='ProductA', price='6666.0'}
...
get_product1 Return result:<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"]>
...

You can see that the testget ﹣ product1 requests are all successful responses and the data is obtained. From the above code, it is not very simple. Now for a slightly complex request mode, use the exchange and execute methods in the RestTemplate API to send a GET request, which can control the request behavior more finely, such as Header information, data processing mode, etc. also add the following code in the testget ﹣ product1 method:

@Test
public void testGet_product1() {
    String url = "http://localhost:8080/product/get_product1";
    //....
    
    //Method 1: construct the request entity HttpEntity object to configure Header information and request parameters
    MultiValueMap header = new LinkedMultiValueMap();
    header.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    HttpEntity<Object> requestEntity = new HttpEntity<>(header);
    //Method 2: execute the request to obtain the response entity ResponseEntity object containing the Product entity object, and use getBody() to obtain
    ResponseEntity<Product> exchangeResult = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Product.class);
    System.out.println("get_product1 Return result:" + exchangeResult);
    Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "get_product1 Unsuccessful response");

        //Method 3: set Header information according to the RequestCallback interface implementation class, and use the ResponseExtractor interface to implement class reading response data
    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);
    }); // Note: Java 8 feature is used here: Lambda expression syntax. If you do not touch Lambda expression, you can use anonymous inner class instead of implementation
    System.out.println("get_product1 Return result:" + executeResult);
    Assert.hasText(executeResult, "get_product1 Return result is empty");
}

Also run the output log of the console after the test method testget product 1:

...
get_product1 Return result:<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 Return result:{"id":1,"name":"ProductA","price":6666.0}
...

The results are all returned normally, indicating that the executed requests are all correct.

Now try to execute the GET request with parameters. Write a new test method in the same way. The implementation code is as follows:

@Test
public void testGet_product2() {
    String url = "http://localhost:8080/product/get_product2/id={id}";
    
        //Method 1: store the value of the parameter in the variable length parameter and match the parameters in order
    ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class, 101);
    System.out.println(responseEntity);
    Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product2 Request unsuccessful");
    Assert.notNull(responseEntity.getBody().getId(), "get_product2  Failed to pass parameter");

        //Method 2: store the request parameters in the Map set in the form of key value pairs, which is used for splicing on the URL when requesting
    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  Failed to pass parameter");
}

The normal operation results are as follows:

...
<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 request

After learning how to use the RestTemplate API to send GET requests, take a look at how to use POST requests, which are also common at ordinary times. Because the content type of POST request data is different, there are more POST requests sent. Here we take two common content types, application/x-www-form-urlencoded and application/json, as examples.

  • Send POST request with content type of application/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));
//Set the content type of the request to application/x-www-form-urlencoded
MultiValueMap<String, String> header = new LinkedMultiValueMap();
header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));

            //Mode 2: Use & splice the request parameter value in the form of K=V, and send the request to use
    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 Request unsuccessful");

        //Method 1: store the request parameters in the form of key value pairs in the MultiValueMap collection, and use the
    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 Request unsuccessful");

}
```
The corresponding output log is as follows:

```

...
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"]>
```

  • Send POST request with content type of application/json:

    @Test
    

public void testPost_product2() {
String url = "http://localhost:8080/product/post_product2";

// Set the content type of the request to application/json
MultiValueMap<String, String> header = new LinkedMultiValueMap();
header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
// Set Accept to indicate to the server the type of content that the client can process
header.put(HttpHeaders.ACCEPT, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
// The entity Product is directly passed in as a request parameter, and the underlying layer uses the Jackson framework to serialize it into a JSON string to send the request
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 Request unsuccessful");

}
```

The output log of verification is as follows:

```
···
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 request and PUT request

DELETE request and PUT request belong to two RESTful request modes, but they are not usually used. Here is a simple demonstration. The specific code is as follows:

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

// PUT method request
@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);
}

Upload files

Now let's try again how to use the RestTemplate API for file upload. It's also relatively simple. First, let's look at the implementation code:

@Test
public void testUploadFile() {
    String url = "http://localhost:8080/product/upload";
    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 Request unsuccessful");
}

If you need to upload file type data, you can only use POST request, and the content type is multipart / form data. You need to manually specify the content type for the Header. The file to be uploaded can be encapsulated by the FileSystemResource object, which represents a file resource. At the same time, the server needs to use the MultipartRequest object to obtain the file data. Combined with the running Web service, running the above test method can get the following log output:

...
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"]>
...

Advanced RestTemplate

So far, we have learned several common ways to request data from RestTemplate API. Now, let's further use RestTemplate.

Bottom HTTP request library switch

Let's first look at the description of the official documents:

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

As can be seen from the above, RestTemplate uses JDK's native java.net.HttpURLConnection to execute the request by default. In addition, Spring also encapsulates three request libraries: Apache HttpComponents, Netty and OkHttp. The first one is the HttpClient API related library we usually use Netty It is a high-performance NIO request processing network library, OkHttp As a rich and efficient network framework, it is mostly used in Android programs.

The RestTemplate instance we created with the default constructor method above adopts the JDK native network API. To switch, just pass in a specific ClientHttpRequestFactory implementation class in the construction method, as follows:

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

We can't find the code using JDK HttpURLConnection API by default when looking at the RestTemplate source code. Then, according to the RestTemplate class hierarchy chart given earlier, we can find the following code on the parent HttpAccessor:

public abstract class HttpAccessor {
    // ...
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    // ...
}

Spring describes the factory class SimpleClientHttpRequestFactory as: implementation that uses standard JDK facilities, which also shows that the default behavior of constructing RestTemplate instances will directly use the JDK network API.

Request timeout

Generally, we will customize the execution behavior of HTTP request classes, such as call timeout setting, connection time limit, etc. when using the default HttpURLConnection configuration, we can see from the SimpleClientHttpRequestFactory source class that there is no timeout limit, which means infinite waiting for request response:

// RestTemplate default timeout
...
private int connectTimeout = -1;
private int readTimeout = -1;
...

Then, how to adjust the timeout can refer to the following code:

RestTemplate customRestTemplate = ne11w RestTemplate(getClientHttpRequestFactory());

private SimpleClientHttpRequestFactory getClientHttpRequestFactory() {
    SimpleClientHttpRequestFactory clientHttpRequestFactory
            = new SimpleClientHttpRequestFactory();
    // Connection timeout setting 10s
    clientHttpRequestFactory.setConnectTimeout(10_000);

    // Read timeout setting 10s
    clientHttpRequestFactory.setReadTimeout(10_000);
    return clientHttpRequestFactory;
}

If you want to adjust the timeout setting of HttpComponentsClient, please refer to the article resttemplate-timeout-example  . Of course, in addition to setting the timeout, there are more parameters for customization, which are not listed here. Please refer to the article resttemplate-httpclient-java-config Further study.

Here we have finished our study of RestTemplate. If you are interested, you can further study the relevant source code, and have a chance to try to make it. A kind of

Reference material

https://www.baeldung.com/rest-template

http://blog.didispace.com/spring-boot-learning-21-1-1

https://www.baeldung.com/spring-rest-template-multipart-upload

https://www.zhihu.com/question/28557115

https://howtodoinjava.com/spring-boot2/resttemplate-timeout-example

https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/integration.html#rest-client-access

https://zh.wikipedia.org/wiki/%E8%A1%A8%E7%8E%B0%E5%B1%82%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestOperations.html

Keywords: Programming Spring JSON JDK Java

Added by jefkin on Tue, 18 Feb 2020 06:33:28 +0200