12 Service Invocation How to Use Rest Template to Consume Restful Services

12 Service Invocation - How to Use RestTemplate to Consume RESTful Services #

In Lesson 11, we introduced how to use Spring Boot to build RESTful web services. The evolution of the SpringCSS sample system has gone from a single service to the interaction between multiple services.

Once we have built the web service, the next step is to learn how to consume the service. This is the focus of Lesson 12, where we will use the RestTemplate template class to achieve this goal.

Using RestTemplate to Access HTTP Endpoints #

RestTemplate is a client-side template class provided by Spring for accessing RESTful services. It resides in the org.springframework.web.client package.

In terms of design, RestTemplate fully satisfies the RESTful architectural principles introduced in Lesson 11. Compared to the traditional Apache HttpClient client class, RestTemplate has made many improvements in terms of coding simplicity and exception handling.

Next, let’s see how to create a RestTemplate object and use its utility methods to efficiently access remote HTTP endpoints.

Creating RestTemplate #

The simplest and most common way to create a RestTemplate object is to directly instantiate it with the new keyword, as shown in the code below:

@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}

Here, we create an instance of RestTemplate and inject it into the Spring container using the @Bean annotation.

Usually, we put the above code in the Bootstrap class of Spring Boot so that we can reference this instance from other parts of the codebase.

Now let’s take a look at the default constructor of RestTemplate to see what it does when creating an instance. The code is as follows:

public RestTemplate() {
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter(false));
    this.messageConverters.add(new SourceHttpMessageConverter<>());
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    
    // Other code omitted for adding HttpMessageConverter
}

As we can see, the default constructor of RestTemplate only does one thing, which is to add a batch of HttpMessageConverter objects for message conversion.

We know that the requests and responses made through RestTemplate are serialized in JSON format, and the parameters and results passed in the subsequent methods like getForObject and exchange are ordinary Java objects. RestTemplate’s HttpMessageConverter automatically handles this conversion for us.

Please note that RestTemplate actually has another more powerful constructor, as shown in the code below:

public RestTemplate(ClientHttpRequestFactory requestFactory) {
    this();
    setRequestFactory(requestFactory);
}

From the above code, we can see that this constructor calls the previous default constructor and allows us to set a ClientHttpRequestFactory interface. With various implementations of this ClientHttpRequestFactory interface, we can finely control the behavior of RestTemplate.

A typical use case is setting properties such as timeout for HTTP requests, as shown in the code below:

@Bean
public RestTemplate customRestTemplate(){
    HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
    httpRequestFactory.setConnectionRequestTimeout(3000);
    httpRequestFactory.setConnectTimeout(3000);
    httpRequestFactory.setReadTimeout(3000);

    return new RestTemplate(httpRequestFactory);
}

Here, we create a HttpComponentsClientHttpRequestFactory factory class, which is an implementation of the ClientHttpRequestFactory interface. By setting properties such as the connection request timeout (ConnectionRequestTimeout), connection timeout (ConnectTimeout), etc., we customize the default behavior of RestTemplate.

Using RestTemplate to Access Web Services #

RestTemplate provides a set of built-in utility methods for remote service access. We can classify these methods based on the HTTP semantics and the principles of RESTful design, as shown in the following table.

Lark20201225-135202.png

Table for categorizing RestTemplate methods

Next, we will provide detailed explanations and examples of the utility methods in RestTemplate based on this table. However, before that, let’s discuss the URL of the request.

In a web request, we can pass parameters through the request path. When using RestTemplate, we can also embed path variables in the URL. Here is an example:

restTemplate.getForObject("http://localhost:8082/account/{id}", 1);

Here, we set a parameter for an endpoint provided by the account-service: we define a URL with a path variable named “id”, and when we actually access it, we set the value of this variable to 1. In fact, multiple path variables can be included in the URL because Java supports variable-length parameter syntax, and the assignment of multiple path variables can be set based on the order of parameters.

In the code below, we use the pageSize and pageIndex path variables in the URL for pagination. When actually accessed, they will be replaced with 20 and 2.

restTemplate.getForObject("http://localhost:8082/account/{pageSize}/{pageIndex}", 20, 2);

Path variables can also be assigned through a Map. The code below similarly defines a URL with path variables pageSize and pageIndex. However, when actually accessed, we will retrieve values from the uriVariables Map object for replacement, resulting in the final request URL: http://localhost:8082/account/20/2.

Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("pageSize", 20);
uriVariables.put("pageIndex", 2);

webClient.getForObject() ("http://localhost:8082/account/{pageSize}/{pageIndex}", Account.class, uriVariables);

Once the request URL is ready, we can use a series of utility methods provided by RestTemplate to access the remote service.

Let’s start with the get method group, which includes getForObject and getForEntity methods. Each group has three methods.

For example, the three methods in the getForObject method group are as follows:

public <T> T getForObject(URI url, Class<T> responseType)

public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables){}

public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

From the above method definitions, we can see that the difference between them is only in the way parameters are passed.

Here, we notice that the first getForObject method has only two parameters (the other two getForObject methods support variable parameters and a Map object, respectively). If we want to add a parameter to the access path, we need to construct an independent URI object, as shown in the following example:

String url = "http://localhost:8080/hello?name=" + URLEncoder.encode(name, "UTF-8");

URI uri = URI.create(url);

Let’s review the AccountController we introduced in Lecture 12, as shown in the following code:

@RestController

@RequestMapping(value = "accounts")

public class AccountController {

 

    @GetMapping(value = "/{accountId}")

    public Account getAccountById(@PathVariable("accountId") Long accountId) {

	

    }

}

For the above endpoint, we can use the getForObject method to construct an HTTP request to obtain the target Account object. The implementation code is as follows:

Account result = restTemplate.getForObject("http://localhost:8082/accounts/{accountId}", Account.class, accountId);

Of course, we can also achieve the same effect using the getForEntity method, but there will be some differences in the syntax, as shown in the following code:

ResponseEntity<Account> result = restTemplate.getForEntity("http://localhost:8082/accounts/{accountId}", Account.class, accountId);

Account account = result.getBody();

In the above code, we can see that the return value of the getForEntity method is a ResponseEntity object, which also contains HTTP message headers and other information, while the return value of the getForObject method is only the business object itself. This is the main difference between these two method groups, and you can choose according to your personal needs.

Compared with GET requests, the POST requests in RestTemplate provide a group of methods in addition to postForObject and postForEntity, which are the postForLocation methods.

Suppose we have an OrderController as shown below, which exposes an endpoint for adding an Order:

@RestController

@RequestMapping(value="orders")

public class OrderController {

 

    @PostMapping(value = "")

    public Order addOrder(@RequestBody Order order) {

        Order result = orderService.addOrder(order);

         return result;

    }

}

In this case, we can use postForEntity to send a POST request, as shown in the following code:

Order order = new Order();

order.setOrderNumber("Order0001");

order.setDeliveryAddress("DemoAddress");

ResponseEntity<Order> responseEntity = restTemplate.postForEntity("http://localhost:8082/orders", order, Order.class);

return responseEntity.getBody();

From the above code, we can see that we have constructed an Order entity here, passed it to the endpoint exposed by OrderController using postForEntity, and obtained the return value of that endpoint. (Please note: the operation method of postForObject is similar to this.)

Once you have mastered the get and post method groups, it will be easy to understand the put method group and delete method group. The put method group is only different from the post method group in terms of operational semantics, and the usage process of the delete method group is similar to that of the get method group. We will not go into detail here, but you can try some exercises on your own.

Finally, it is necessary to introduce the exchange method group.

For RestTemplate, exchange is a universal and unified method that can be used to send GET and POST requests, as well as various other types of requests.

Let’s take a look at one of the method signatures in the exchange method group, as shown in the following code:

public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException

Please note that the requestEntity variable here is an HttpEntity object, which encapsulates the request headers and body, and responseType is used to specify the return data type. If there is an endpoint in the previous OrderController that obtains Order information based on the order number OrderNumber, then the code for making the request using the exchange method would be like this, as shown in the following code:

ResponseEntity<Order> result = restTemplate.exchange("http://localhost:8082/orders/{orderNumber}", HttpMethod.GET, null, Order.class, orderNumber);

And a more complex usage is to separately set HTTP request headers and access parameters, and make remote calls. The example code is as follows:

```java
//Set HTTP headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

//Set access parameters
HashMap<String, Object> params = new HashMap<>();
params.put("orderNumber", orderNumber);

//Set request entity
HttpEntity entity = new HttpEntity<>(params, headers);

ResponseEntity<Order> result = restTemplate.exchange(url, HttpMethod.GET, entity, Order.class);

Other Tips for RestTemplate #

In addition to implementing regular HTTP requests, RestTemplate also has some advanced usage, such as specifying message converters, setting interceptors, and handling exceptions.

Specify Message Converters

In RestTemplate, there is actually a third constructor, as shown in the following code:

public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
    Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
    this.messageConverters.addAll(messageConverters);
}

From the above code, it can be seen that we can initialize RestTemplate by passing in a set of HttpMessageConverters, which provides a way to customize message converters.

For example, if we want to load the GsonHttpMessageConverter that supports Gson into RestTemplate, we can use the following code:

@Bean
public RestTemplate restTemplate() {
    List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
    messageConverters.add(new GsonHttpMessageConverter());
    RestTemplate restTemplate = new RestTemplate(messageConverters);
    return restTemplate;
}

In principle, we can implement various custom HttpMessageConverters as needed and initialize RestTemplate through the above method.

Set Interceptors

If we want to set some common interceptors for requests, we can use interceptors. However, before using interceptors, we need to implement the ClientHttpRequestInterceptor interface.

The most typical application scenario is to add load balancing mechanism to RestTemplate through the @LoadBalanced annotation in Spring Cloud. We can find the following code in the LoadBalanceAutoConfiguration auto-configuration class:

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
    final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
            restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };
}

In the above code, we can see the LoadBalancerInterceptor class, which implements the ClientHttpRequestInterceptor interface. Then we inject this custom LoadBalancerInterceptor into the interceptor list of RestTemplate by calling the setInterceptors method.

Handle Exceptions

By default, when the request status code is not 200, RestTemplate will throw an exception and interrupt the subsequent operations. If we don’t want to use this default processing, we need to override the default ResponseErrorHandler. The example code structure is as follows:

RestTemplate restTemplate = new RestTemplate();

ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {
    @Override
    public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
        return true;
    }
    @Override
    public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
        //Add custom exception handling code
    }
};

restTemplate.setErrorHandler(responseErrorHandler);

In the above handleError method, we can implement any custom exception handling code.

Implement Service Interaction in the SpringCSS Case Study #

After introducing the usage of the RestTemplate template class, let’s go back to the SpringCSS case study.

In Chapter 11, we have given the code structure of the generateCustomerTicket method in the CustomerService class of the customer-service, which integrates with the account-service and order-service, as shown below:

public CustomerTicket generateCustomerTicket(Long accountId, String orderNumber) {
    // Create a customer ticket object
    CustomerTicket customerTicket = new CustomerTicket();

    // Get the Account object from the remote account-service
    Account account = getRemoteAccountById(accountId);

    // Get the Order object from the remote order-service
    Order order = getRemoteOrderByOrderNumber(orderNumber);

    // Set the CustomerTicket object and save it
    customerTicket.setAccountId(accountId);
    customerTicket.setOrderNumber(order.getOrderNumber());
    customerTicketRepository.save(customerTicket);

    return customerTicket;
}

Let’s take the getRemoteOrderByOrderNumber method as an example to expand on its implementation process. The definition code of the getRemoteOrderByOrderNumber method is as follows:

@Autowired
private OrderClient orderClient;

private OrderMapper getRemoteOrderByOrderNumber(String orderNumber) {
    return orderClient.getOrderByOrderNumber(orderNumber);
}

The implementation process of the getRemoteAccountById method is similar.

Next, we will create an OrderClient class to complete the remote access to the order-service, as shown in the following code:

@Component
public class OrderClient {
    private static final Logger logger = LoggerFactory.getLogger(OrderClient.class);

    @Autowired
    RestTemplate restTemplate;

    public OrderMapper getOrderByOrderNumber(String orderNumber) {
        logger.debug("Get order from remote: {}", orderNumber);

        ResponseEntity<OrderMapper> result = restTemplate.exchange(
            "http://localhost:8083/orders/{orderNumber}", HttpMethod.GET, null,
            OrderMapper.class, orderNumber);

         OrderMapper order = result.getBody();

        return order;
    }
}

Note: We inject a RestTemplate object here and use its exchange method to complete the request process to the remote order-service. The return object here is an OrderMapper, not an Order object. Finally, the built-in HttpMessageConverter of RestTemplate automatically maps between OrderMapper and Order.

In fact, the internal fields of OrderMapper and Order objects correspond one-to-one, and they are located in two different code projects. To differentiate them, we deliberately made a distinction in their names.

Summary and Preview #

In Chapter 12, we introduced the RestTemplate template class to complete the access to remote HTTP endpoints based on the content of Chapter 11. RestTemplate provides a wide range of useful utility methods for sending HTTP requests and obtaining responses for developers. At the same time, the template class also provides some customized entry points for developers to embed and implement fine-grained management and processing logic for HTTP requests. Like JdbcTemplate, RestTemplate is also a very effective tool class in terms of design and implementation.