RestTemplate最詳解


在項目中,當我們需要遠程調用一個HTTP接口時,我們經常會用到RestTemplate這個類。這個類是Spring框架提供的一個工具類。Spring官網對它的介紹如下:

RestTemplate: The original Spring REST client with a synchronous, template method API.

從上面的介紹中我們可以知道:RestTemplate是一個同步的Rest API客戶端。下面我們就來介紹下RestTemplate的常用功能。

RestTemplate簡單使用

RestTemplate提供高度封裝的接口,可以讓我們非常方便地進行Rest API調用。常見的方法如下:

表格:RestTemplate的方法

上面的方法我們大致可以分為三組:

  • getForObject --- optionsForAllow分為一組,這類方法是常規的Rest API(GET、POST、DELETE等)方法調用;
  • exchange:接收一個RequestEntity 參數,可以自己設置HTTP method, URL, headers和body。返回ResponseEntity。
  • execute:通過callback 接口,可以對請求和返回做更加全面的自定義控制。

一般情況下,我們使用第一組和第二組方法就夠了。

創建RestTemplate

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

@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setReadTimeout(5000);
    factory.setConnectTimeout(15000);
    //設置代理
    //factory.setProxy(null);
    return factory;
}

創建RestTemplate時需要一個ClientHttpRequestFactory,通過這個請求工廠,我們可以統一設置請求的超時時間,設置代理以及一些其他細節。通過上面代碼配置后,我們直接在代碼中注入RestTemplate就可以使用了。

接口調用

1. 普通接口調用

Map<String, String> vars = Collections.singletonMap("hotel", "42");
//通過GET方式調用,返回一個String值,還可以給URL變量設置值(也可通過uriTemplateHandler這個屬性自定義)
String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

String url = "http://127.0.0.1:8080/hello";
JSONObject param = new JSONObject();
//restTemplate會根據params的具體類型,調用合適的HttpMessageConvert將請求參數寫到請求體body中,並在請求頭中添加合適的content-type;
//也會根據responseType的類型(本列子中是JSONObject),設置head中的accept字段,當響應返回的時候再調用合適的HttpMessageConvert進行響應轉換
ResponseEntity<JSONObject> responseEntity=restTemplate.postForEntity(url,params,JSONObject.class);
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
JSONObject body = responseEntity.getBody();

2. 添加Header和Cookie

有時候,我們需要在請求中的Head中添加值或者將某些值通過cookie傳給服務端,那么上面這種調用形式就不太滿足要求了。

 UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("127.0.0.1:8080").
                path("/test").build(true);
 URI uri = uriComponents.toUri();
 
RequestEntity<JSONObject> requestEntity = RequestEntity.post(uri).
                //添加cookie(這邊有個問題,假如我們要設置cookie的生命周期,作用域等參數我們要怎么操作)
                header(HttpHeaders.COOKIE,"key1=value1").
                //添加header
                header(("MyRequestHeader", "MyValue")
                accept(MediaType.APPLICATION_JSON).
                contentType(MediaType.APPLICATION_JSON).
                body(requestParam);
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(requestEntity,JSONObject.class);
//響應結果
JSONObject responseEntityBody = responseEntity.getBody();

3. 文件上傳

上面兩個列子基本能覆蓋我們平時開發的大多數功能了。這邊再講個文件上傳的列子(RestTemplate功能還是蠻全的)。

public Object uplaod(@RequestBody JSONObject params) throws Exception{

        final String url = "http://localhost:8888/hello/m3";
        //設置請求頭
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        //設置請求體,注意是LinkedMultiValueMap
        FileSystemResource resource1 = new FileSystemResource("D:\\dir1\\ss\\pic1.jpg");
        FileSystemResource resource2 = new FileSystemResource("D:\\dir1\\ss\\pic2.jpg");
       
        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
        form.add("file", resource1);
        form.add("file", resource2);
        form.add("param1","value1");

        HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
        JSONObject s = restTemplate.postForObject(url, files, JSONObject.class);
        return s;
    }

上面的代碼中上傳了兩個本地圖片,通過下面代碼可以順利接收。

@RequestMapping("/m3")
public Object fileUpload(@RequestParam("file") MultipartFile[] files, HttpServletRequest request) throws Exception {
    //攜帶的其他參數可以使用getParameter方法接收
    String param1 = request.getParameter("param1");
    Response response = new Response();
    if (files == null) {
        response.failure("文件上傳錯誤,服務端未拿到上傳的文件!");
        return response;
    }
    for (MultipartFile file : files) {
        if (!file.isEmpty() && file.getSize() > 0) {
            String fileName = file.getOriginalFilename();
            //參考FileCopyUtils這個工具類
            file.transferTo(new File("D:\\" + fileName));
            logger.info("文件:{} 上傳成功...",fileName);
        }
    }
    response.success("文件上傳成功");
    return response;
    }

但是我們發現上面的上傳代碼中,上傳文件的類必須使用FileSystemResource。有時我們會碰到這種情況:文件我們會從文件服務下載到內存中一個InputStream的形式存在,那此時在使用FileSystemResource就不行了。

當然,我們使用討巧一點的辦法也是可以的:先將下載下來的InputStream保存到本地,然后再讀取到FileSystemResource,上傳后再刪除本地臨時文件。

但是總覺得這個方法不夠完美。最后發現有個同事已經寫了相關的實現。這邊就直接拿來用了。

//自己實現了一個Resource
public class InMemoryResource extends ByteArrayResource {
    private final String filename;
    private final long lastModified;

    public InMemoryResource(String filename, String description, byte[] content, long lastModified) {
        super(content, description);
        this.lastModified = lastModified;
        this.filename = filename;
    }
    
    @Override
    public long lastModified() throws IOException {
        return this.lastModified;
    }

    @Override
    public String getFilename() {
        return this.filename;
    }
}

調整后的上傳代碼

 @PostMapping("/m3")
    public Object m3(@RequestBody JSONObject params) throws Exception{

        final String url = "http://localhost:8888/hello/m3";
        //設置請求頭
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        //設置請求體,注意是LinkedMultiValueMap
        //下面兩個流從文件服務下載,這邊省略(注意最后關閉流)
        InputStream fis1 = 
        InputStream fis2 = 

        InMemoryResource resource1 = new InMemoryResource("file1.jpg","description1", FileCopyUtils.copyToByteArray(fis1), System.currentTimeMillis());
        InMemoryResource resource2 = new InMemoryResource("file2.jpg","description2", FileCopyUtils.copyToByteArray(fis2), System.currentTimeMillis());
        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
        form.add("file", resource1);
        form.add("file", resource2);
        form.add("param1","value1");

        HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
        JSONObject s = restTemplate.postForObject(url, files, JSONObject.class);
        return s;
    }

一些其他設置

1. 攔截器配置

RestTemplate也可以設置攔截器做一些統一處理。這個功能感覺和Spring MVC的攔截器類似。配置也很簡單:

class MyInterceptor implements ClientHttpRequestInterceptor{

        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            logger.info("enter interceptor...");
            return execution.execute(request,body);
        }
    }
 @Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate(factory);
    MyInterceptor myInterceptor = new MyInterceptor();
    List<ClientHttpRequestInterceptor> list = new ArrayList<>();
    list.add(myInterceptor);
    restTemplate.setInterceptors(list);
    return restTemplate;
}

2. ErrorHandler配置

ErrorHandler用來對調用錯誤對統一處理。

public class MyResponseErrorHandler extends DefaultResponseErrorHandler {

        @Override
        public boolean hasError(ClientHttpResponse response) throws IOException {
            return super.hasError(response);
        }

        @Override
        public void handleError(ClientHttpResponse response) throws IOException {
            HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
            if (statusCode == null) {
                throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
            }
            handleError(response, statusCode);
        }
        @Override
        protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
            switch (statusCode.series()) {
                case CLIENT_ERROR:
                    HttpClientErrorException exp1 = new HttpClientErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
                    logger.error("客戶端調用異常",exp1);
                    throw  exp1;
                case SERVER_ERROR:
                    HttpServerErrorException exp2 = new HttpServerErrorException(statusCode, response.getStatusText(),
                            response.getHeaders(), getResponseBody(response), getCharset(response));
                    logger.error("服務端調用異常",exp2);
                    throw exp2;
                default:
                    UnknownHttpStatusCodeException exp3 = new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
                            response.getHeaders(), getResponseBody(response), getCharset(response));
                    logger.error("網絡調用未知異常");
                    throw exp3;
            }
        }

    }
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate(factory);
    MyResponseErrorHandler errorHandler = new MyResponseErrorHandler();
    restTemplate.setErrorHandler(errorHandler);
    List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
    //通過下面代碼可以添加新的HttpMessageConverter
    //messageConverters.add(new );
    return restTemplate;
}

3. HttpMessageConverter配置
RestTemplate 也可以配置HttpMessageConverter,配置的原理和Spring MVC中類似。

簡單總結

通過RestTemplate,我們可以非常方便的進行Rest API調用。但是在Spring 5中已經不再建議使用RestTemplate,而是建議使用WebClient。WebClient是一個支持異步調用的Client。所以喜歡研究新東西的同學可以開始研究下新東西了。

公眾號推薦

歡迎大家關注我的微信公眾號「程序員自由之路」


免責聲明!

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



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