restTemplate使用及源碼分析


最近使用restTemplate實現了一個服務對另一個服務中的Rest接口的調用,這里總結下其用法,並分析下源碼,話不多說這就開始。

 

一、RestTemplate簡單使用

 

1》首先是RestTemplate的配置文件:

package cn.com.ctsi.busiobj.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig{

    @Bean
    public RestTemplate restTemplate(@Qualifier("simpleClientHttpRequestFactory") ClientHttpRequestFactory factory){
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//ms
        factory.setConnectTimeout(15000);//ms
        return factory;
    }
}

1.使用@Configuration表名這是一個配置文件

2.使用RestTemplate的構造方法:

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

ClientHttpRequestFactory對超時時間等屬性進行設置。

3.ClientHttpRequestFactory接口有4個實現類,分別是:

  1).AbstractClientHttpRequestFactoryWrapper 用來裝配其他request factory的抽象類。
  2).CommonsClientHttpRequestFactory 允許用戶配置帶有認證和http連接池的httpclient,已廢棄,推薦用HttpComponentsClientHttpRequestFactory。
  3).HttpComponentsClientHttpRequestFactory 同2.
  4).SimpleClientHttpRequestFactory 接口的一個簡單實現,可配置proxy,connectTimeout,readTimeout等參數。
 
本例中我們使用@Qualifer注解指明使用SimpleClientHttpRequestFactory
 

2》RestTemplate的調用

Service層調用方式如下:

@Service
public class CdmApiUtil {

    @Autowired
    RestTemplate restTemplate;


public CdmRes sendCdmReq(CdmReq cctReq)throws Exception{ try{
            CdmRes res =restTemplate.postForObject(cdmBaseUrl+tableCreateUrl,cctReq,CdmRes.class);
            Assert.notNull(cdmCreateTemplateTableRes,"調用接口,返回為null");
            int code= res.getStatus().getCode();
            Assert.isTrue((code==Status.CODE_SUCCESS),"sendCreateCdmTableReq 創建業務數據表失敗,"+ res.getStatus().getDesc());return res;
        }catch (Exception e){
            logger.error("sendCdmReq,"+e.getMessage());
            throw new Exception("調用cdm接口失敗,"+e.getMessage());
        }
    }

}

這里我們使用了RestTemplate的postForObject方法發送請求,此方法中的參數:

1)調用rest接口的url路徑,

2)拼接的請求實體對象,

3)返回的響應的class實體對象

至此就完成了對遠程rest接口的簡單調用。

 

二、RestTemplate使用詳解

 

轉自:https://blog.csdn.net/u012702547/article/details/77917939

GET請求

在RestTemplate中,發送一個GET請求,我們可以通過如下兩種方式:

第一種:getForEntity

@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 execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
            throws RestClientException {

        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
    }

 

 

getForEntity方法的返回值是一個ResponseEntity<T>ResponseEntity<T>是Spring對HTTP請求響應的封裝,包括了幾個重要的元素,如響應碼、contentType、contentLength、響應消息體等。比如下面一個例子:

@RequestMapping("/gethello")
public String getHello() {
  ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class);
    String body = responseEntity.getBody();
    HttpStatus statusCode = responseEntity.getStatusCode();
    int statusCodeValue = responseEntity.getStatusCodeValue();
    HttpHeaders headers = responseEntity.getHeaders();
    StringBuffer result = new StringBuffer();
    result.append("responseEntity.getBody():").append(body).append("<hr>")
            .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>")
            .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>")
            .append("responseEntity.getHeaders():").append(headers).append("<hr>");
    return result.toString();
}

關於這段代碼:

  • getForEntity的第一個參數為我要調用的服務的地址,這里我調用了服務提供者提供的/hello接口,注意這里是通過服務名調用而不是服務地址,如果寫成服務地址就沒法實現客戶端負載均衡了。
  • getForEntity第二個參數String.class表示我希望返回的body類型是String
  • 拿到返回結果之后,將返回結果遍歷打印出來

最終顯示結果如下:

這里寫圖片描述

有時候我在調用服務提供者提供的接口時,可能需要傳遞參數,有兩種不同的方式,如下:

@RequestMapping("/sayhello")
public String sayHello() {
    ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "張三");
    return responseEntity.getBody();
}
@RequestMapping("/sayhello2")
public String sayHello2() {
    Map<String, String> map = new HashMap<>();
    map.put("name", "李四");
    ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody();
}
  • 可以用一個數字做占位符,最后是一個可變長度的參數,來一一替換前面的占位符
  • 也可以前面使用name={name}這種形式,最后一個參數是一個map,map的key即為前邊占位符的名字,map的value為參數值

第一個調用地址也可以是一個URI而不是字符串,這個時候我們構建一個URI即可,參數神馬的都包含在URI中了,如下:

@RequestMapping("/sayhello3")
public String sayHello3() {
    UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode();
    URI uri = uriComponents.toUri();
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
    return responseEntity.getBody();
}

通過Spring中提供的UriComponents來構建Uri即可。

當然,服務提供者不僅可以返回String,也可以返回一個自定義類型的對象,比如我的服務提供者中有如下方法:

@RequestMapping(value = "/getbook1", method = RequestMethod.GET)
public Book book1() {
    return new Book("三國演義", 90, "羅貫中", "花城出版社");
}

 

對於該方法我可以在服務消費者中通過如下方式來調用:

@RequestMapping("/book1")
public Book book1() {
    ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/getbook1", Book.class);
    return responseEntity.getBody();
}

運行結果如下:

這里寫圖片描述

第二種:getForObject

getForObject函數實際上是對getForEntity函數的進一步封裝,如果你只關注返回的消息體的內容,對其他信息都不關注,此時可以使用getForObject,舉一個簡單的例子,如下:

@RequestMapping("/book2")
public Book book2() {
    Book book = restTemplate.getForObject("http://HELLO-SERVICE/getbook1", Book.class);
    return book;
}

getForObject也有幾個重載方法,如下:

@Override
    public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
    }

 

這幾個重載方法參數的含義和getForEntity一致,我就不再贅述了。

 

POST請求

在RestTemplate中,POST請求可以通過如下三個方法來發起:

第一種:postForEntity

 

該方法和get請求中的getForEntity方法類似,如下例子:

@RequestMapping("/book3")
public Book book3() {
    Book book = new Book();
    book.setName("紅樓夢");
    ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class);
    return responseEntity.getBody();
}
  • 方法的第一參數表示要調用的服務的地址
  • 方法的第二個參數表示上傳的參數
  • 方法的第三個參數表示返回的消息體的數據類型

我這里創建了一個Book對象,這個Book對象只有name屬性有值,將之傳遞到服務提供者那里去,服務提供者代碼如下:

@RequestMapping(value = "/getbook2", method = RequestMethod.POST)
public Book book2(@RequestBody Book book) {
    System.out.println(book.getName());
    book.setPrice(33);
    book.setAuthor("曹雪芹");
    book.setPublisher("人民文學出版社");
    return book;
}

 

服務提供者接收到服務消費者傳來的參數book,給其他屬性設置上值再返回,調用結果如下:

postForEntity的其他重載方法如下:

@Override
    public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
    }

 

這些方法的參數含義和getForEntity參數的含義一致,不再贅述。

第二種:postForObject

如果你只關注,返回的消息體,可以直接使用postForObject。用法和getForObject一致。

 

@Override
    public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }

    @Override
    public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
    }

 

第三種:postForLocation

postForLocation也是提交新資源,提交成功之后,返回新資源的URI,postForLocation的參數和前面兩種的參數基本一致,只不過該方法的返回值為Uri,這個只需要服務提供者返回一個Uri即可,該Uri表示新資源的位置。

PUT請求

在RestTemplate中,PUT請求可以通過put方法調用,put方法的參數和前面介紹的postForEntity方法的參數基本一致,只是put方法沒有返回值而已。舉一個簡單的例子,如下:

@RequestMapping("/put")
public void put() {
    Book book = new Book();
    book.setName("紅樓夢");
    restTemplate.put("http://HELLO-SERVICE/getbook3/{1}", book, 99);
}

book對象是我要提交的參數,最后的99用來替換前面的占位符{1}

DELETE請求

delete請求我們可以通過delete方法調用來實現,如下例子:

@RequestMapping("/delete")
public void delete() {
    restTemplate.delete("http://HELLO-SERVICE/getbook4/{1}", 100);
}

delete方法也有幾個重載的方法,不過重載的參數和前面基本一致,不贅述。

 

 

三、源碼分析

以postForObject為例進行源碼分析:

@Override
    @Nullable
    public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)
            throws RestClientException {

        RequestCallback requestCallback = httpEntityCallback(request, responseType);//1.
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<>(responseType, getMessageConverters());//2.
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor);//3.
    }

 

httpEntityCallback(@Nullable Object requestBody, Type responseType)方法:

protected <T> RequestCallback httpEntityCallback(@Nullable Object requestBody, Type responseType) {
        return new HttpEntityRequestCallback(requestBody, responseType);
    }

 

private HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) {
            super(responseType);
            if (requestBody instanceof HttpEntity) {
                this.requestEntity = (HttpEntity<?>) requestBody;
            }
            else if (requestBody != null) {
                this.requestEntity = new HttpEntity<>(requestBody);
            }
            else {
                this.requestEntity = HttpEntity.EMPTY;
            }
        }

1)如果傳遞進來的requestBody是HttpEntity類型則將其強制轉型為HttpEntity<?>類型;

2)否則如果requestBody不為null,則將其轉換成HttpEntity格式

3)否則如果requestBody為null,則創建一個沒有請求頭和請求體的空HttpEntity。

 

HttpEntity實體類如下:

public class HttpEntity<T> {

    /**
     * The empty {@code HttpEntity}, with no body or headers.
     */
    public static final HttpEntity<?> EMPTY = new HttpEntity<>();


    private final HttpHeaders headers;

    @Nullable
    private final T body;

    /**
     * Create a new, empty {@code HttpEntity}.
     */
    protected HttpEntity() {
        this(null, null);
    }

    /**
     * Create a new {@code HttpEntity} with the given body and no headers.
     * @param body the entity body
     */
    public HttpEntity(T body) {
        this(body, null);
    }

    /**
     * Create a new {@code HttpEntity} with the given headers and no body.
     * @param headers the entity headers
     */
    public HttpEntity(MultiValueMap<String, String> headers) {
        this(null, headers);
    }

    /**
     * Create a new {@code HttpEntity} with the given body and headers.
     * @param body the entity body
     * @param headers the entity headers
     */
    public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
        this.body = body;
        HttpHeaders tempHeaders = new HttpHeaders();
        if (headers != null) {
            tempHeaders.putAll(headers);
        }
        this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders);
    }


    /**
     * Returns the headers of this entity.
     */
    public HttpHeaders getHeaders() {
        return this.headers;
    }

    /**
     * Returns the body of this entity.
     */
    @Nullable
    public T getBody() {
        return this.body;
    }

    /**
     * Indicates whether this entity has a body.
     */
    public boolean hasBody() {
        return (this.body != null);
    }

....... }

 即為將requestBody寫成HttpEntity的請求頭、請求體的形式。

 

下面我們來看第二步:

官方對HttpMessageConverterExtractor的解釋是:

Response extractor that uses the given {@linkplain HttpMessageConverter entity converters}
* to convert the response into a type {@code T}

即能夠將返回的相應轉換為某個特定的類型responseType。

 

第三步:

首先將String類型的URI轉換為URI類型,然后execute方法底層調用doExecute方法。

@Override
    public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException {

        URI expanded = getUriTemplateHandler().expand(url, uriVariables);
        return doExecute(expanded, method, requestCallback, responseExtractor);
    }

 

    /**
     * Execute the given method on the provided URI.
     * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
     * the response with the {@link ResponseExtractor}.
     * @param url the fully-expanded URL to connect to
     * @param method the HTTP method to execute (GET, POST, etc.)
     * @param requestCallback object that prepares the request (can be {@code null})
     * @param responseExtractor object that extracts the return value from the response (can be {@code null})
     * @return an arbitrary object, as returned by the {@link ResponseExtractor}
     */
    protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor) throws RestClientException {

        Assert.notNull(url, "'url' must not be null");
        Assert.notNull(method, "'method' must not be null");
        ClientHttpResponse response = null;
        try {
            ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            handleResponse(url, method, response);
            if (responseExtractor != null) {
                return responseExtractor.extractData(response);
            }
            else {
                return null;
            }
        }
        catch (IOException ex) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource);
            throw new ResourceAccessException("I/O error on " + method.name() +
                    " request for \"" + resource + "\": " + ex.getMessage(), ex);
        }
        finally {
            if (response != null) {
                response.close();
            }
        }
    }

doExecute方法:

1.首先使用請求的url和method(post 或者get)構造出一個ClientHttpRequest。

2.requestCallback.doWithRequest將之前的requestBody requestHeader放入此ClientHttpRequest中;

3.調用request的execute方法獲得response,調用handleResponse方法處理response中存在的error;

4.使用ResponseExtractor的extraData方法將返回的response轉換為某個特定的類型;

5.最后記得關閉ClientHttpResponse資源,這樣就完成了發送請求並獲得對應類型的返回值的全部過程。

 

 

四、另外一種方式:使用RestTemplateUtil使用restTemplate

 
        
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package ctsi.mts.common.mvc.util;

import com.fasterxml.jackson.databind.DeserializationFeature;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

public class RestTemplateUtil {
    private static Logger logger = LoggerFactory.getLogger(RestTemplateUtil.class);

    public RestTemplateUtil() {
    }

    private static RestTemplate getRestTemplate() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.getObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        List<HttpMessageConverter<?>> lstConverters = new ArrayList();
        lstConverters.add(new StringHttpMessageConverter(Charset.forName("utf-8")));
        lstConverters.add(converter);
        RestTemplate rt = new RestTemplate();
        rt.setMessageConverters(lstConverters);
        return rt;
    }

    public static String getForStringResult(String url) {
        return (String)get(url, String.class, (Map)null);
    }

    public static String getForStringResult(String url, Map<String, ?> urlVariables) {
        return (String)get(url, String.class, urlVariables);
    }

    public static <T> T get(String url, Class<T> responseType) {
        return get(url, responseType, (Map)null);
    }

    public static <T> T get(String url, Class<T> responseType, Map<String, ?> urlVariables) {
        logger.info("[rest get] {}", url);
        logger.info("[responseType] {}", responseType);
        Object result;
        if (urlVariables == null) {
            result = getRestTemplate().getForObject(url, responseType, new Object[0]);
        } else {
            Iterator var4 = urlVariables.entrySet().iterator();

            while(var4.hasNext()) {
                Entry entry = (Entry)var4.next();
                logger.info("[params] {} = {}", entry.getKey(), entry.getValue());
            }

            result = getRestTemplate().getForObject(url, responseType, urlVariables);
        }

        logger.info("[result] {}", result);
        return result;
    }

    public static String postForStringResult(String url, Object request) {
        return (String)post(url, request, String.class, (Map)null);
    }

    public static String postForStringResult(String url, Object request, Map<String, ?> urlVariables) {
        return (String)post(url, request, String.class, urlVariables);
    }

    public static <T> T post(String url, Object request, Class<T> responseType) {
        return post(url, request, responseType, (Map)null);
    }

    public static <T> T post(String url, Object request, Class<T> responseType, Map<String, ?> urlVariables) {
        logger.info("[rest post] {}", url);
        logger.info("[request] {}", request);
        logger.info("[responseType] {}", responseType);
        Object result;
        if (urlVariables == null) {
            result = getRestTemplate().postForObject(url, request, responseType, new Object[0]);
        } else {
            Iterator var5 = urlVariables.entrySet().iterator();

            while(var5.hasNext()) {
                Entry entry = (Entry)var5.next();
                logger.info("[params] {} = {}", entry.getKey(), entry.getValue());
            }

            result = getRestTemplate().postForObject(url, request, responseType, urlVariables);
        }

        logger.info("[result] {}", result);
        return result;
    }
}
 
        

看了下,大致只有getForString  getForObject  postForString postForObject沒有forEntity類型,對restTemplate的readTimeOut proxy等屬性沒有要求時可通過RestTemplateUtil來獲取restTemplate對象。








免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM