Are you familiar with the use and principles of RestTemplate? [Enjoy Spring MVC]

Posted by jwrightnisha on Tue, 17 Sep 2019 06:09:41 +0200

Each sentence

Happy Mid-Autumn Festival

Preface

Before reading this article, I suggest reading it first. Opening chapter The effect is better. RestTemplate is a client tool provided by Spring to access Rest services. It provides a variety of convenient methods to access remote Http services, which can greatly improve the efficiency of client writing.
For those who are still using HttpClient (or other Client) in Spring environment, after reading this article today, we suggest switching to RestTemplate.

RestTemplate simplifies communication with HTTP services, and program code can provide it with URLs and extract results. It uses JDK's HTTP URLConnection by default to communicate, but we can switch to different HTTP sources through RestTemplate.setRequestFactory: Apache HttpComponents, Netty, OkHttp, and so on.

RestOperations

Specify a set of interfaces for basic restful operations, define a basic set of Rest operations, whose only implementation is RestTemplate; not directly used, but this is a useful option to enhance testability because it is easy to emulate or stub (see the following sentence).

Referring to RedisOperations for comparison, it has only one implementation class, RedisTemplate. They both adopted the template pattern in the design pattern.

Methods:

Since there are so many methods in this interface (40 + ones), I categorize them according to Http standard as follows:

// @since 3.0
public enum HttpMethod {
    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
    ...
}
HttpMethod Method
GET
HEAD
POST
PUT
PATCH
DELETE
OPTIONS
TRACE nothing
Any (execute any Http method)

Observation shows that although there are many methods, there are strong rules to follow. Each method has three overload implementations: two url parameters are strings and one URI parameter, so if you master the rules and then use them, you don't have to be afraid of how many of them are used.

xxxForObject: Return the response body (which is the body's physical strength directly) (T)
xxxForEntity: Returns the corresponding row, response header, response code, response body, etc. (ResponseEntity < T>)
xxxForLocation: After successful submission, return the URI of the new resource. This only requires the service provider to return a URI, which represents the location of the new resource and is very lightweight. (URI)

Note: By default, URLs that use string types are escaped, such as http://example.com/hotel list when executed, to http://example.com/hotel%20list. Implicit escaping is no problem. But if you've transferred yourself, that's not ok ay.
If you do not want this implicit escape, it is recommended to construct it using URI (URI uri = uriComponents.toUri()).

== Three ways of POST request in RestTemplate==

postRequest Representatives to Build New/Create a resource, so it has a return value. Because its use is the most complex, this paper takes it as an example to explain.

If you've used the browser proficientlyDeveloper ToolsAfter debugging, you must know.POSTThere are two ways to request it to pass parameters:

  1. Form Data mode: We use from The way the form is submitted is it; use it ajax(Note: This refers to jQuery Of ajax,Not from the source js The default submission method is also it~

  1. request payload mode: Multipart approach/json mode


These two ways are throughContent-TypeTo distinguish: ifapplication/x-www-form-urlencodedThat isformdataHow; if soapplication/jsonperhapsmultipart/form-dataWait a minute. That's it.request payloadmode

jQueryIn execution post When requested, the default settings will be set for youContent-Typebyapplication/x-www-form-urlencoded,So the server can parse correctly.
If used js Native ajax,If notDisplayedSet up Content-Type,So the default is text/plain,At this point, the server does not know how to parse the data, so it can only parse the request data by acquiring the original data stream. I don't believe anybody did that.~)

exchange and execute Method:

exchange Method: More general request method. It must accept oneRequestEntity,This allows you to set the requested path, header, and so on, and ultimately all return one.ResponseEntity(Can send Get,Post,Put Wait for all requests.
execute Method:The lowest and most generalRequest method.

RequestCallback: Used to manipulate request headers and body,On requestFrontExecution; ResponseExtractor: analysis/extract HTTP Response data, and there is no need to worry about exceptions and resource closures
RequestCallback.doWithRequest(ClientHttpRequest)To put it plainly is to get itClientHttpRequestAfter that, he continued to be dealt with.~
RestTemplateOfacceptHeaderRequestCallback,httpEntityCallbackThese methods can set it up~

HttpAccessor,InterceptingHttpAccessor

These two abstract classes cannot be ignored.HystrixCommand and RibbonThe logic of the interceptor is related to it.
HttpAccessorIs an abstract base class that defines operationsClientHttpRequestFactoryCommon attributes, which are generally not used directly.

// @since 3.0
public abstract class HttpAccessor {
    
    // RestTemplate's default client factory: based on the native JDK
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

    // To switch to the underlying components of a tripartite library, you can set this method
    public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
        this.requestFactory = requestFactory;
    }
    ... // get method
    
    // Providing subclasses makes it easy to get a ClientHttpRequest
    protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        ClientHttpRequest request = getRequestFactory().createRequest(url, method);
        return request;
    }    
}

Its subclass is Intercepting Http Accessor, which is also an abstract implementation, mainly manages the interceptors of requests: ClientHttp Request Interceptor.

InterceptingHttpAccessor

// @since 3.0
// @see InterceptingClientHttpRequestFactory
public abstract class InterceptingHttpAccessor extends HttpAccessor {

    // Loading interceptors that need to be used on RestTemplate~~~
    private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    @Nullable
    private volatile ClientHttpRequestFactory interceptingRequestFactory;

    // The meaning here is set, so it's completely replaced.
    public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
        if (this.interceptors != interceptors) {
            this.interceptors.clear();
            this.interceptors.addAll(interceptors);
            AnnotationAwareOrderComparator.sort(this.interceptors);
        }
    }

    // It's interesting to rewrite the parent class.
    // If the caller manually sets in, the factory set by the caller is the criterion, otherwise the Intercepting Client HttpRequestFactory is used.
    @Override
    public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
        super.setRequestFactory(requestFactory);
        this.interceptingRequestFactory = null;
    }

    // If the interceptor is configured, the Intercepting Client HttpRequestFactory is used by default instead of SimpleClient HttpRequestFactory.~~~
    @Override
    public ClientHttpRequestFactory getRequestFactory() {
        List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
        if (!CollectionUtils.isEmpty(interceptors)) {
            ClientHttpRequestFactory factory = this.interceptingRequestFactory;
            if (factory == null) {
                factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
                this.interceptingRequestFactory = factory;
            }
            return factory;
        } else {
            return super.getRequestFactory();
        }
    }
}

The main processing logic of Intercepting Http Accessor is that if the caller is found to have set up a request interceptor, the factory it creates is the Intercepting Client Http Request Factory with interception function, otherwise it is the default SimpleClient Http Request Factory.

Intercepting Client HttpRequest Factory which produces Client HttpRequest is Intercepting Client HttpRequest, but it will execute the interceptor's interception method: nextInterceptor.intercept(request, body, this)

Question: If there are multiple request interceptors configured, will they be executed?
Answer: Don't be confused and jump to the conclusion that it's wrong to assume that there is no iterator.next() but only iterator.next() that if there are more than one, only one will be executed. In fact, there is an execution chain. As long as the intercept method of the intercept of the intercept of the intercept of the intercept of the intercept finally calls the intercept() method of the executor, the intercept chain will continue to execute. The fundamental reason for this is that the third parameter is passed in by the same executor (this=InterceptingRequestExecution)

==RestTemplate==

RestTemplateUsesynchronizationMode execution HTTP The requested class, which is used by default at the bottomJDKNative HttpURLConnection API . It implements interfacesRestOperations,There are a lot of template methods (overload methods) that allow developers to send more easily HTTP Request.

It should be noted that,RestTemplateyesSpring 3.0Yes, but Spring5.0 Later, Spring Official recommendationorg.springframework.web.reactive.function.client.WebClientReplace it, especially for asynchronous scenarios.

RestTemplateBecause it is widely used, so Even when it comes to Spring 5.0,Officials only suggest alternatives, but not labels.@Deprecated,So at least for now, you can use it as you want.
howeverAsyncRestTemplateIt's clearly marked.@Deprecated,It is strongly recommended to useorg.springframework.web.reactive.function.client.WebClientTo replace, so in 5.0 It is not recommended to use it again.~.

Of course, there's one more point to make: if you don't use it in your projectWebFluxThe technology stack handles requests, so there's no need to say it's for use, so there's no need to import packages specifically for it (personal advice)~

// @since 3.0
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
    // De classpath to detect if there are jar s associated with these message converters~
    // In general, we will guide Jackson 2Present.~~~
    private static boolean romePresent;
    private static final boolean jaxb2Present;
    private static final boolean jackson2Present;
    private static final boolean jackson2XmlPresent;
    private static final boolean jackson2SmilePresent;
    private static final boolean jackson2CborPresent;
    private static final boolean gsonPresent;
    private static final boolean jsonbPresent;
    ...
    
    // The following four variables are important:

    // Message converters (apparently the best supported JSON format by default)
    private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
    // The default request exception handler, after Spring 5.0, can actually use it to extract Response Error Handler
    // It can use message exchange to extract your wrong content. It also supports custom error codes, error sequences, and so on.~
    private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
    // Construction of URL s
    private UriTemplateHandler uriTemplateHandler;
    // Default Return Value Extractor~~~~
    private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();

    // Empty constructs should be the most commonly used: everything uses default components to configure resources, and so on.
    public RestTemplate() {
        // These message converters are supported. Byte arrays, strings,
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        this.messageConverters.add(new ResourceHttpMessageConverter(false));
        this.messageConverters.add(new SourceHttpMessageConverter<>());
        // Support for form submission
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        // Then there are some columns of judgments that will only be added if there are classpaths.
        if (jackson2Present) {
            this.messageConverters.add(new MappingJackson2HttpMessageConverter());
        }
        ...
        // new DefaultUriBuilderFactory()
        this.uriTemplateHandler = initUriTemplateHandler();
    }

    // You know, if you want to use OkHttp, you can also specify it at construction time.
    public RestTemplate(ClientHttpRequestFactory requestFactory) {
        this();
        setRequestFactory(requestFactory);
    }

    // If you don't want to use the default message converter, you can also specify it by yourself (in fact, it's usually not done that way, but you can add it in later).
    public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
        Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
        this.messageConverters.addAll(messageConverters);
        this.uriTemplateHandler = initUriTemplateHandler();
    }
    ... // get/set offenders who omit the above attributes
}

This part of the source code I listed is related to the preparation of building a RestTemplate instance, including the settings for various related components.

Next, more important is the interface method it implements. I draw out some key points to describe it.

RestTemplate: 

    @Override
    @Nullable
    public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
        //1. New AcceptHeader Request Callback (responseType) can do something like this before sending a request:
        // request.getHeaders().setAccept(allSupportedMediaTypes)
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);

        // The final call is the execute method, where the URL is a string
        // The responseExtractor Return Value Extractor uses a message converter to read the body clip.~
        // The return value is the body itself returned (does not contain the return response header, etc.)
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
    }

    // It returns ResponseEntity, and the last call that does not return null is still the execute method.
    // Instead of using the extractor of the message converter, the internal class `ResponseEntityResponseExtractor'(bottom or dependent on the message converter)
    // But this extractor can extract all the ResponseEntity < T > instances.~
    @Override
    public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
    }

    // HEAD request: Simply, the extractor used is headers extractor, which takes the response header out of the return value.
    @Override
    public HttpHeaders headForHeaders(String url, Object... uriVariables) throws RestClientException {
        return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables));
    }


    // POST request
    @Override
    @Nullable
    public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {
        // 1. HttpEntityRequestCallback adaptation: adapting request to a HttpEntity
        // Then, before execution, the header information, body information, etc. are written in through the message converter.
        RequestCallback requestCallback = httpEntityCallback(request);
        // Because you need to get the URI, you can use the headers extractor extractor here to get the response header first.~~~
        HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables);
        return (headers != null ? headers.getLocation() : null);
    }

    // Except for httpEntityCallback(), the rest is the same as get requests.
    @Override
    @Nullable
    public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }

    // PUT request: Because there is no return value, there is no need for a return value extractor. So, it's very simple.~~~
    @Override
    public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = httpEntityCallback(request);
        execute(url, HttpMethod.PUT, requestCallback, null, uriVariables);
    }

    // DELETE request: Also wood has return value.
    // And please note: DELETE requests can not receive body here, and can not set the request body.
    // (Although the underlying httpCLient support may be available, it is not supported here. Please follow the specifications.)
    @Override
    public void delete(String url, Object... uriVariables) throws RestClientException {
        execute(url, HttpMethod.DELETE, null, null, uriVariables);
    }

    // OPTIONS requests: Almost the same processing logic as HEAD requests
    @Override
    public Set<HttpMethod> optionsForAllow(String url, Object... uriVariables) throws RestClientException {
        ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
        HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables);
        return (headers != null ? headers.getAllow() : Collections.emptySet());
    }

The logic of execution of all methods is basically the same, which is related to RequestCallback, responseExtractor and so on, and ultimately delegated to the lowest execute() method to execute.

Do you have any questions: the return values of the put method provided by it are all void, so what if I put the request has a return value swollen? Then I'll introduce a more general approach: exchange()

RestTemplate: 

    @Override
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
        // Adapt the requester to HttpEntity
        RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
        // Message extractor uses ResponseEntityResponseExtractor
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);

        // From the above two parts, we can see that the exchange method is very common in both input and output parameters.~~~
        return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
    }

    // Parameterized Type Reference parameterized type for handling generics
    // The responseType above is a Class. Here is a parameterized type~~~~~
    @Override
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {

        Type type = responseType.getType();
        RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
        return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
    }

    // This method is very concise, allowing the caller to construct the RequestEntity itself, which contains information such as the URL and method of the request.
    @Override
    public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor));
    }

All exchange methods use HttpEntity and ResponseEntity to represent requesting and responding entities, enough to see the versatility of its design.

After Spring 3.2, a Parameterized Type Reference is provided to handle parameterized types - > mainly for generic types such as List s.

It can be found that even the exchange() method is ultimately delegated to execute/doExecute for execution:

RestTemplate: 

    // Three execute methods. The final call is the doExecute method
    // One thing it does: it uses UriTemplateHandler to fill in the parameters of the URL~~~
    // The underlying use is the `UriComponents Builder', which I introduced earlier, or is relatively simple
    @Override
    @Nullable
    public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
        URI expanded = getUriTemplateHandler().expand(url, uriVariables);
        return doExecute(expanded, method, requestCallback, responseExtractor);
    }

doExecute Method:
    @Nullable
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
        ClientHttpResponse response = null;
        ClientHttpRequest request = createRequest(url, method);
        // If there is a callback, then call back first to process the request.
        if (requestCallback != null) {
            requestCallback.doWithRequest(request);
        }
        // Send requests in a real sense.
        // Note: If the request here is `Intercepting Client HttpRequest', then execute the intercept method of the interceptor.~~~
        // When is Intercepting Client HttpRequest? Here's what's on it.
        response = request.execute();
        // Processing results (throw exceptions if there are errors)
        handleResponse(url, method, response);
        
        // The request is normal. Then use the return value extractor responseExtractor to extract the content.~~~
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
        ...
        // Close Response (ClientHttpResponse inherits the Closeable interface)
        finally {
            if (response != null) {
                response.close();
            }
        }
    }

After looking at the template implementation steps of doExecute(), the complete process of RestTemplate from sending a request to receiving a response is clear. Spring designed a number of related components, providing hooks to allow us to intervene in the process, the most common of course is the request interceptor, which has a good application in Ribbon load balancing and Hystrix fuse.~

AsyncRestTemplate

It is a new scenario for @since 4.0 to solve some asynchronous Http requests, but it has a short lifetime. It is marked @Deprecated in Spring 5.0 and is recommended to replace it with WebClient.

Its basic principle is: RestTemplate + SimpleAsyncTaskExecutor task pool to achieve the asynchronous request, the return value is ListenableFuture. After mastering RestTemplate, there is no barrier to its use.

Minimalist use of Demo Show

Having read the description of the principle, I have reason to believe that you are thoroughly familiar with RestTemplate and can use it freely. So as for usage, this article only gives a very simple Demo Show as follows, which I think is enough:

public static void main(String[] args) throws IOException {
    RestTemplate restTemplate = new RestTemplate();
    String pageHtml = restTemplate.getForObject("http://www.baidu.com", String.class);
    System.out.println(pageHtml); // Baidu Home Page html.
}

Explanation: The request here is an html page, so when HttpMessageConverter Extractor extracts the response, it uses String HttpMessageConverter to process it. The extraction code is as follows:

StringHttpMessageConverter: 
    @Override
    protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
        // Extract from the contentType of the response header (if application/json, the default is urf-8)
        // If no encoding is specified, the value getDefaultCharset is taken. For example, when we visit Baidu, we use the default value `ISO-8859-1'to encode body.~
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }

Compare this request case with the one I sent with Client HttpRequestFactory above (or with your own HttpClient step), and feel how elegant RestTemplate is.~

Recommended reading

RestTemplate components: Client HttpRequestFactory, Client HttpRequest Interceptor, Response Extractor
Why does a @LoadBalanced annotation enable RestTemplate to have load balancing capabilities? [Enjoy Spring Cloud]

summary

Today, RestTemplate is a powerful tool for mainstream microservices, and every programmer should master it. Deep understanding of it is of great practical significance for practical application and optimization, so I believe this article can help you to be thoroughly familiar with it.
Preview: The next article will explain why a simple @LoadBalanced annotation enables RestTemplate to have load balancing capabilities.

== If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==
== If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==

Topics: Java Spring REST JSON JDK