Spring之RestTemplate詳解


1 RestTemplate

1.1 引言

現如今的 IT 項目,由服務端向外發起網絡請求的場景,基本上處處可見!
傳統情況下,在服務端代碼里訪問 http 服務時,一般會使用 JDKHttpURLConnection 或者 ApacheHttpClient,不過這種方法使用起來太過繁瑣,而且 api 使用起來非常的復雜,還得操心資源回收。

以下載文件為例,通過 ApacheHttpClient方式進行下載文件,會很復雜
其實Spring已經為我們提供了一種簡單便捷的模板類來進行操作,它就是RestTemplate

RestTemplate是一個執行HTTP請求的同步阻塞式工具類,它僅僅只是在 HTTP 客戶端庫(例如 JDK HttpURLConnectionApache HttpComponentsokHttp 等)基礎上,封裝了更加簡單易用的模板方法 API,方便程序員利用已提供的模板方法發起網絡請求和處理,能很大程度上提升我們的開發效率

1.2 環境配置

1.2.1 非Spring環境下使用RestTemplate

如果當前項目不是Spring項目,加入spring-web包,即可引入RestTemplate

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>

編寫一個單元測試類,使用RestTemplate發送一個GET請求,看看程序運行是否正常

@Test
public void simpleTest() {
    RestTemplate restTemplate = new RestTemplate();
    String url = "http://jsonplaceholder.typicode.com/posts/1";
    String str = restTemplate.getForObject(url, String.class);
    System.out.println(str);
}

1.2.2 Spring環境下使用RestTemplate

如果當前項目是SpringBoot,添加如下依賴接口!

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

同時,將RestTemplate配置初始化為一個Bean

@Configuration
public class RestTemplateConfig {
    /**
     * 沒有實例化RestTemplate時,初始化RestTemplate
     * @return
     */
    @ConditionalOnMissingBean(RestTemplate.class)
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }
}

注意,這種初始化方法,是使用了JDK自帶的HttpURLConnection作為底層HTTP客戶端實現。

當然,我們還可以修改RestTemplate默認的客戶端,例如將其改成HttpClient客戶端,方式如下:

@Configuration
public class RestTemplateConfig {

    @ConditionalOnMissingBean(RestTemplate.class)
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }

    /**
     * 使用HttpClient作為底層客戶端
     * @return
     */
    private ClientHttpRequestFactory getClientHttpRequestFactory() {
        int timeout = 5000;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout)
                .build();
        CloseableHttpClient client = HttpClientBuilder
                .create()
                .setDefaultRequestConfig(config)
                .build();
        return new HttpComponentsClientHttpRequestFactory(client);
    }

}

在需要使用RestTemplate的位置,注入並使用即可!

@Autowired
private RestTemplate restTemplate;

從開發人員的反饋,和網上的各種HTTP客戶端性能以及易用程度評測來看,OkHttp 優於 Apache的HttpClientApache的HttpClient優於HttpURLConnection

因此,我們還可以通過如下方式,將底層的http客戶端換成OkHttp

/**
 * 使用OkHttpClient作為底層客戶端
 * @return
 */
private ClientHttpRequestFactory getClientHttpRequestFactory(){
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .writeTimeout(5, TimeUnit.SECONDS)
            .readTimeout(5, TimeUnit.SECONDS)
            .build();
    return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}

1.2.3 Spring環境下增加線程號

使用RestTemplate調用遠程接口時,有時需要在header中傳遞信息,比如:traceId,source等,便於在查詢日志時能夠串聯一次完整的請求鏈路,快速定位問題。這種業務場景就能通過ClientHttpRequestInterceptor接口實現,具體做法如下

第一步,定義一個LogFilter攔截所有接口請求,在MDC中設置traceId:

public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        MDC.put("TRACE_ID",UUID.randomUUID().toString());
        System.out.println("記錄請求日志");
        chain.doFilter(request, response);
        System.out.println("記錄響應日志");
    }

    @Override
    public void destroy() {
    }
}

第二步,實現ClientHttpRequestInterceptor接口,MDC中獲取當前請求的traceId,然后設置到header

public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        request.getHeaders().set("traceId", MDC.get("TRACE_ID"));
        ClientHttpResponse response = execution.execute(request, body);
        return response;
    }
}

第三步,定義配置類,配置上面定義的RestTemplateInterceptor類:

@Configuration
public class RestTemplateConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(restTemplateInterceptor()));
        return restTemplate;
    }

    @Bean
    public RestTemplateInterceptor restTemplateInterceptor() {
        return new RestTemplateInterceptor();
    }
}

能使用MDC保存traceId等參數的根本原因是,用戶請求到應用服務器,Tomcat會從線程池中分配一個線程去處理該請求。那么該請求的整個過程中,保存到MDC的ThreadLocal中的參數,也是該線程獨享的,所以不會有線程安全問題

1.3 API 實踐

RestTemplate最大的特色就是對各種網絡請求方式做了包裝,能極大的簡化開發人員的工作量,下面我們以GET、POST、PUT、DELETE、文件上傳與下載為例,分別介紹各個API的使用方式

1.3.1 GET請求

通過RestTemplate發送HTTP GET協議請求,經常使用到的方法有兩個:

  • getForObject():返回值是HTTP協議的響應體
  • getForEntity():返回的是ResponseEntityResponseEntity是對HTTP響應的封裝,除了包含響應體,還包含HTTP狀態碼、contentType、contentLength、Header等信息

Spring Boot環境下寫一個單元測試用例,首先創建一個Api接口,然后編寫單元測試進行服務測試。

1.3.1.1 不帶參請求

不帶參的get請求

@RestController
public class TestController {

    /**
     * 不帶參的get請求
     * @return
     */
    @RequestMapping(value = "testGet", method = RequestMethod.GET)
    public ResponseBean testGet(){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("請求成功,方法:testGet");
        return result;
    }
}
public class ResponseBean {
    private String code;
    private String msg;
    省去getset方法 
}
@Autowired
private RestTemplate restTemplate;
/**
 * 單元測試(不帶參的get請求)
 */
@Test
public void testGet(){
    //請求地址
    String url = "http://localhost:8080/testGet";

    //發起請求,直接返回對象
    ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class);
    System.out.println(responseBean.toString());
}

1.3.1.2 帶參的get請求(使用占位符號傳參)

@RestController
public class TestController {
    /**
     * 帶參的get請求(restful風格)
     * @return
     */
    @RequestMapping(value = "testGetByRestFul/{id}/{name}", method = RequestMethod.GET)
    public ResponseBean testGetByRestFul(@PathVariable(value = "id") String id, @PathVariable(value = "name") String name){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("請求成功,方法:testGetByRestFul,請求參數id:" +  id + "請求參數name:" + name);
        return result;
    }
}
@Autowired
private RestTemplate restTemplate;
 /**
 * 單元測試(帶參的get請求)
 */
@Test
public void testGetByRestFul(){
    //請求地址
    String url = "http://localhost:8080/testGetByRestFul/{1}/{2}";

    //發起請求,直接返回對象(restful風格)
    ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001", "張三");
    System.out.println(responseBean.toString());
}

1.3.1.3 帶參的get請求(restful風格)

@RestController
public class TestController {
    /**
     * 帶參的get請求(使用占位符號傳參)
     * @return
     */
    @RequestMapping(value = "testGetByParam", method = RequestMethod.GET)
    public ResponseBean testGetByParam(@RequestParam("userName") String userName,
                                             @RequestParam("userPwd") String userPwd){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("請求成功,方法:testGetByParam,請求參數userName:" +  userName + ",userPwd:" + userPwd);
        return result;
    }
}
@Autowired
private RestTemplate restTemplate;

 /**
 * 單元測試(帶參的get請求)
 */
@Test
public void testGetByParam(){
    //請求地址
    String url = "http://localhost:8080/testGetByParam?userName={userName}&userPwd={userPwd}";

    //請求參數
    Map<String, String> uriVariables = new HashMap<>();
    uriVariables.put("userName", "唐三藏");
    uriVariables.put("userPwd", "123456");

    //發起請求,直接返回對象(帶參數請求)
    ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, uriVariables);
    System.out.println(responseBean.toString());
}

1.3.1.4 getForEntity使用示例

上面的所有的getForObject請求傳參方法,getForEntity都可以使用,使用方法上也幾乎是一致的,只是在返回結果接收的時候略有差別。

使用ResponseEntity<T> responseEntity來接收響應結果。用responseEntity.getBody()獲取響應體。

 /**
 * 單元測試
 */
@Test
public void testAllGet(){
    //請求地址
    String url = "http://localhost:8080/testGet";

    //發起請求,返回全部信息
    ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url, ResponseBean.class);

    // 獲取響應體
    System.out.println("HTTP 響應body:" + response.getBody().toString());

    // 以下是getForEntity比getForObject多出來的內容
    HttpStatus statusCode = response.getStatusCode();
    int statusCodeValue = response.getStatusCodeValue();
    HttpHeaders headers = response.getHeaders();

    System.out.println("HTTP 響應狀態:" + statusCode);
    System.out.println("HTTP 響應狀態碼:" + statusCodeValue);
    System.out.println("HTTP Headers信息:" + headers);
}

1.3.1.5 header設置參數

//請求頭
HttpHeaders headers = new HttpHeaders();
headers.add("token", "123456789");

//封裝請求頭
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(headers);

ResponseEntity<Map> exchange = restTemplate.exchange('請求的url', HttpMethod.GET, formEntity, Map.class);

1.3.2 POST請求

其實POST請求方法和GET請求方法上大同小異,RestTemplatePOST請求也包含兩個主要方法:

  • postForObject():返回body對象
  • postForEntity():返回全部的信息

1.3.2.1 模擬表單請求

模擬表單請求,post方法測試

@RestController
public class TestController {

    /**
     * 模擬表單請求,post方法測試
     * @return
     */
    @RequestMapping(value = "testPostByForm", method = RequestMethod.POST)
    public ResponseBean testPostByForm(@RequestParam("userName") String userName,
                                        @RequestParam("userPwd") String userPwd){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("請求成功,方法:testPostByForm,請求參數userName:" + userName + ",userPwd:" + userPwd);
        return result;
    }
}
@Autowired
private RestTemplate restTemplate;

/**
 * 模擬表單提交,post請求
 */
@Test
public void testPostByForm(){
    //請求地址
    String url = "http://localhost:8080/testPostByForm";

    // 請求頭設置,x-www-form-urlencoded格式的數據
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    //提交參數設置
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("userName", "唐三藏");
    map.add("userPwd", "123456");

    // 組裝請求體
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

    //發起請求
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

1.3.2.2 模擬表單請求(傳遞對象)

模擬表單請求,post方法測試(對象接受)

@RestController
public class TestController {
    /**
     * 模擬表單請求,post方法測試
     * @param request
     * @return
     */
    @RequestMapping(value = "testPostByFormAndObj", method = RequestMethod.POST)
    public ResponseBean testPostByForm(RequestBean request){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("請求成功,方法:testPostByFormAndObj,請求參數:" + JSON.toJSONString(request));
        return result;
    }
}

public class RequestBean {
   private String userName;
   private String userPwd;
   省去getset方法
}
@Autowired
private RestTemplate restTemplate;

/**
 * 模擬表單提交,post請求
 */
@Test
public void testPostByForm(){
    //請求地址
    String url = "http://localhost:8080/testPostByFormAndObj";
    // 請求頭設置,x-www-form-urlencoded格式的數據
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    //提交參數設置
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("userName", "唐三藏");
    map.add("userPwd", "123456");

    // 組裝請求體
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

    //發起請求
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

1.3.2.3 模擬JSON請求

模擬JSON請求,post方法測試

@RestController
public class TestController {
    /**
     * 模擬JSON請求,post方法測試
     * @param request
     * @return
     */
    @RequestMapping(value = "testPostByJson", method = RequestMethod.POST)
    public ResponseBean testPostByJson(@RequestBody RequestBean request){
        ResponseBean result = new ResponseBean();
        result.setCode("200");
        result.setMsg("請求成功,方法:testPostByJson,請求參數:" + JSON.toJSONString(request));
        return result;
    }
}
@Autowired
private RestTemplate restTemplate;
/**
 * 模擬JSON提交,post請求
 */
@Test
public void testPostByJson(){
    //請求地址
    String url = "http://localhost:8080/testPostByJson";

    //入參
    RequestBean request = new RequestBean();
    request.setUserName("唐三藏");
    request.setUserPwd("123456789");

    //發送post請求,並打印結果,以String類型接收響應結果JSON字符串
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

1.3.2.4 模擬頁面重定向

模擬頁面重定向,post請求

@Controller
public class LoginController {
    /**
     * 重定向
     * @param request
     * @return
     */
    @RequestMapping(value = "testPostByLocation", method = RequestMethod.POST)
    public String testPostByLocation(@RequestBody RequestBean request){
        return "redirect:index.html";
    }
}
@Autowired
private RestTemplate restTemplate;
/**
 * 重定向,post請求
 */
@Test
public void testPostByLocation(){
    //請求地址
    String url = "http://localhost:8080/testPostByLocation";
    //入參
    RequestBean request = new RequestBean();
    request.setUserName("唐三藏");
    request.setUserPwd("123456789");

    //用於提交完成數據之后的頁面跳轉,返回跳轉url
    URI uri = restTemplate.postForLocation(url, request);
    System.out.println(uri.toString());
}

輸出結果如下:
http://localhost:8080/index.html

1.3.3 PUT請求

put請求方法,可能很多人都沒用過,它指的是修改一個已經存在的資源或者插入資源,該方法會向URL代表的資源發送一個HTTP PUT方法請求,示例如下

@RestController
public class TestController {
    /**
     * 模擬JSON請求,put方法測試
     * @param request
     * @return
     */
    @RequestMapping(value = "testPutByJson", method = RequestMethod.PUT)
    public void testPutByJson(@RequestBody RequestBean request){
        System.out.println("請求成功,方法:testPutByJson,請求參數:" + JSON.toJSONString(request));
    }
}
@Autowired
private RestTemplate restTemplate;
/**
 * 模擬JSON提交,put請求
 */
@Test
public void testPutByJson(){
    //請求地址
    String url = "http://localhost:8080/testPutByJson";
    //入參
    RequestBean request = new RequestBean();
    request.setUserName("唐三藏");
    request.setUserPwd("123456789");

    //模擬JSON提交,put請求
    restTemplate.put(url, request);
}

1.3.4 DELETE請求

與之對應的還有delete方法協議,表示刪除一個已經存在的資源,該方法會向URL代表的資源發送一個HTTP DELETE方法請求。

@RestController
public class TestController {
    /**
     * 模擬JSON請求,delete方法測試
     * @return
     */
    @RequestMapping(value = "testDeleteByJson", method = RequestMethod.DELETE)
    public void testDeleteByJson(){
        System.out.println("請求成功,方法:testDeleteByJson");
    }
}
@Autowired
private RestTemplate restTemplate;
/**
 * 模擬JSON提交,delete請求
 */
@Test
public void testDeleteByJson(){
    //請求地址
    String url = "http://localhost:8080/testDeleteByJson";

    //模擬JSON提交,delete請求
    restTemplate.delete(url);
}

1.3.5 通用請求方法exchange方法

如果以上方法還不滿足你的要求。在RestTemplate工具類里面,還有一個exchange通用協議請求方法,它可以發送GET、POST、DELETE、PUT、OPTIONS、PATCH等等HTTP方法請求。

打開源碼,我們可以很清晰的看到這一點。
在這里插入圖片描述
在這里插入圖片描述

采用exchange方法,可以滿足各種場景下的請求操作

1.3.6 文件上傳與下載

除了經常用到的getpost請求以外,還有一個經常會碰到的場景,那就是文件的上傳與下載,如果采用RestTemplate,該怎么使用呢?

案例如下,具體實現細節參考代碼注釋!

1.3.6.1 文件上傳

@RestController
public class FileUploadController {

    private static final String UPLOAD_PATH = "/springboot-frame-example/springboot-example-resttemplate/";

    /**
     * 文件上傳
     * @param uploadFile
     * @return
     */
    @RequestMapping(value = "upload", method = RequestMethod.POST)
    public ResponseBean upload(@RequestParam("uploadFile") MultipartFile uploadFile,
                               @RequestParam("userName") String userName) {
        // 在 uploadPath 文件夾中通過用戶名對上傳的文件歸類保存
        File folder = new File(UPLOAD_PATH + userName);
        if (!folder.isDirectory()) {
            folder.mkdirs();
        }

        // 對上傳的文件重命名,避免文件重名
        String oldName = uploadFile.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));

        //定義返回視圖
        ResponseBean result = new ResponseBean();
        try {
            // 文件保存
            uploadFile.transferTo(new File(folder, newName));
            result.setCode("200");
            result.setMsg("文件上傳成功,方法:upload,文件名:" + newName);
        } catch (IOException e) {
            e.printStackTrace();
            result.setCode("500");
            result.setMsg("文件上傳失敗,方法:upload,請求文件:" + oldName);
        }
        return result;
    }
}
@Autowired
private RestTemplate restTemplate;

/**
 * 文件上傳,post請求
 */
@Test
public void upload(){
    //需要上傳的文件
    String filePath = "/Users/panzhi/Desktop/Jietu20220205-194655.jpg";

    //請求地址
    String url = "http://localhost:8080/upload";

    // 請求頭設置,multipart/form-data格式的數據
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);

    //提交參數設置
    MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
    param.add("uploadFile", new FileSystemResource(new File(filePath)));
    //服務端如果接受額外參數,可以傳遞
    param.add("userName", "張三");

    // 組裝請求體
    HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(param, headers);

    //發起請求
    ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
    System.out.println(responseBean.toString());
}

1.3.6.2 文件下載

@RestController
public class FileUploadController {

    private static final String UPLOAD_PATH = "springboot-frame-example/springboot-example-resttemplate/";

    /**
     * 帶參的get請求(restful風格)
     * @return
     */
    @RequestMapping(value = "downloadFile/{userName}/{fileName}", method = RequestMethod.GET)
    public void downloadFile(@PathVariable(value = "userName") String userName,
                             @PathVariable(value = "fileName") String fileName,
                             HttpServletRequest request,
                             HttpServletResponse response) throws Exception {

        File file = new File(UPLOAD_PATH + userName + File.separator + fileName);
        if (file.exists()) {
            //獲取文件流
            FileInputStream fis = new FileInputStream(file);
            //獲取文件后綴(.png)
            String extendFileName = fileName.substring(fileName.lastIndexOf('.'));
            //動態設置響應類型,根據前台傳遞文件類型設置響應類型
            response.setContentType(request.getSession().getServletContext().getMimeType(extendFileName));
            //設置響應頭,attachment表示以附件的形式下載,inline表示在線打開
            response.setHeader("content-disposition","attachment;fileName=" + URLEncoder.encode(fileName,"UTF-8"));
            //獲取輸出流對象(用於寫文件)
            OutputStream os = response.getOutputStream();
            //下載文件,使用spring框架中的FileCopyUtils工具
            FileCopyUtils.copy(fis,os);
        }
    }
}
@Autowired
private RestTemplate restTemplate;

/**
 * 小文件下載
 * @throws IOException
 */
@Test
public void downloadFile() throws IOException {
    String userName = "張三";
    String fileName = "c98b677c-0948-46ef-84d2-3742a2b821b0.jpg";
    //請求地址
    String url = "http://localhost:8080/downloadFile/{1}/{2}";

    //發起請求,直接返回對象(restful風格)
    ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class, userName,fileName);
    System.out.println("文件下載請求結果狀態碼:" + rsp.getStatusCode());

    // 將下載下來的文件內容保存到本地
    String targetPath = "/Users/panzhi/Desktop/"  + fileName;
    Files.write(Paths.get(targetPath), Objects.requireNonNull(rsp.getBody(), "未獲取到下載文件"));
}

這種下載方法實際上是將下載文件一次性加載到客戶端本地內存,然后從內存將文件寫入磁盤。這種方式對於小文件的下載還比較適合,如果文件比較大或者文件下載並發量比較大,容易造成內存的大量占用,從而降低應用的運行效率

1.3.6.3 大文件下載

@Autowired
private RestTemplate restTemplate;
/**
 * 大文件下載
 * @throws IOException
 */
@Test
public void downloadBigFile() throws IOException {
    String userName = "張三";
    String fileName = "c98b677c-0948-46ef-84d2-3742a2b821b0.jpg";
    //請求地址
    String url = "http://localhost:8080/downloadFile/{1}/{2}";

    //定義請求頭的接收類型
    RequestCallback requestCallback = request -> request.getHeaders()
    .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

    //對響應進行流式處理而不是將其全部加載到內存中
    String targetPath = "/Users/panzhi/Desktop/"  + fileName;
    restTemplate.execute(url, HttpMethod.GET, requestCallback, clientHttpResponse -> {
        Files.copy(clientHttpResponse.getBody(), Paths.get(targetPath));
        return null;
    }, userName, fileName);
}

這種下載方式的區別在於:
設置了請求頭APPLICATION_OCTET_STREAM,表示以流的形式進行數據加載
RequestCallback結合File.copy保證了接收到一部分文件內容,就向磁盤寫入一部分內容。而不是全部加載到內存,最后再寫入磁盤文件。
在下載大文件時,例如excel、pdf、zip等等文件,特別管用,

轉載於:https://mp.weixin.qq.com/s/FEn7w12Cvuy-Or6qiZxYBg

1.3.7 通過服務名調用

1.3.7.1 簡介

Spring Cloud中,通過服務名稱訪問其他服務通常需要使用@LoadBalancedRestTemplate,可以使用服務名(service ID)來代替具體的URL

例如:

@Configuration
public class Config 
{
   @LoadBalanced
   @Bean
   public RestTemplate restTemplate() {
   SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        // 設置超時
        requestFactory.setConnectTimeout(60 * 1000);
        requestFactory.setReadTimeout(60 * 1000);
        //利用復雜構造器可以實現超時設置,內部實際實現為 HttpClient
        RestTemplate restTemplate = new RestTemplate(requestFactory);
      	return restTemplate ;
   }
}

上面的代碼定義了一個 RestTemplate bean,並使用@LoadBalanced注解來開啟負載均衡。這樣,就可以在 應用中自動注入RestTemplate,然后使用服務名稱來發送請求。

注意:這種方法需要服務被注冊到服務發現組件(如EurekaNacosConsul等)。這樣,應用才能找到服務名對應的實際網絡位置。

1.3.7.2 @LoadBalanced作用

@LoadBalancedNetflixribbon 中的一個負載均衡的注解,並完成以下工作:

  • 從負載均衡器中選一個對應的服務實例,所有的服務名實例都放在負載均衡器中的serverlist中;
  • 從挑選的實例中去請求內容;
  • 由服務名轉為真正使用的ip地址;

RestTemplate使用服務名訪問服務的流程
通過源碼跟蹤可知,restTemplate能通過服務名獲取到具體的服務,是由LoadBalancerInterceptor這個攔截器實現的,而具體的工作是由RibbonLoadBalancerClient來完成的。
RibbonLoadBalancerClient將服務名通過負載均衡策略轉為了實際的ip和端口后再apply給restTemplate。

1.4 核心講解

1.4.1 excute

所有的get、post、delete、put、options、head、exchange(一部分)方法最終調用的都是excute方法

public T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor responseExtractor, Object… uriVariables)

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
從上面的Excute方法中我們可以看出Excute方法只是將String格式的URI轉成了java.net.URI,之后調用了doExecute方法。
整個調用過程如下
在這里插入圖片描述
doExecute方法如下:
在這里插入圖片描述
doExecute 方法並沒有暴露出來,只能通過繼承調用
這里需要了解兩個類: RequestCallback & ResponseExtractor

1.4.2 RequestCallback

RequestCallback用於在ClientHttpRequest上操作的代碼的回調接口。允許操作請求頭,並寫入請求主體。

RequestCallback有兩個實現類,都是內部類:

  • AcceptHeaderRequestCallback:只處理請求頭,用於restTemplate.getXXX()方法
  • HttpEntityRequestCallback:繼承於AcceptHeaderRequestCallback可以處理請求頭body,用於restTemplate.putXXX()、restTemplate.postXXX()和restTemplate.exchange()方法

1.4.3 ResponseExtractor

restTemplate對此接口ResponseExtractor的檢索方法實現,使用的通用回調接口執行,從clienthttpresponse提取數據的實際工作(解析HTTP響應的數據),但不需要擔心異常處理或關閉資源

RequestCallback有三個實現類:

  • HeadersExtractor:用於提取請求頭
  • HttpMessageConverterExtractor:用於提取響應body
  • ResponseEntityResponseExtractor:使用HttpMessageConverterExtractor提取body(委托模式),然后將body和響應頭、狀態封裝成ResponseEntity對象。


免責聲明!

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



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