Spring之RestTemplate详解

Spring之RestTemplate详解

  • 1 RestTemplate
    • 1.1 引言
    • 1.2 环境配置
      • 1.2.1 非Spring环境下使用RestTemplate
      • 1.2.2 Spring环境下使用 RestTemplate
      • 1.2.3 Spring环境下增加线程号
    • 1.3 API 实践
      • 1.3.1 GET请求
        • 1.3.1.1 不带参请求
        • 1.3.1.2 带参的get请求(使用占位符号传参)
        • 1.3.1.3 带参的get请求(restful风格)
        • 1.3.1.4 getForEntity使用示例
        • 1.3.1.5 header设置参数
      • 1.3.2 POST请求
        • 1.3.2.1 模拟表单请求
        • 1.3.2.2 模拟表单请求(传递对象)
        • 1.3.2.3 模拟JSON请求
        • 1.3.2.4 模拟页面重定向
      • 1.3.3 PUT请求
      • 1.3.4 DELETE请求
      • 1.3.5 通用请求方法exchange方法
      • 1.3.6 文件上传与下载
        • 1.3.6.1 文件上传
        • 1.3.6.2 文件下载
        • 1.3.6.3 大文件下载
      • 1.3.7 通过服务名调用
        • 1.3.7.1 简介
        • 1.3.7.2 @LoadBalanced作用
    • 1.4 核心讲解
      • 1.4.1 excute
      • 1.4.2 RequestCallback
      • 1.4.3 ResponseExtractor

1 RestTemplate

1.1 引言

现如今的 IT 项目,由服务端向外发起网络请求的场景,基本上处处可见!
传统情况下,在服务端代码里访问 http 服务时,一般会使用 JDK 的 HttpURLConnection 或者 Apache 的 HttpClient,不过这种方法使用起来太过繁琐,而且 api 使用起来非常的复杂,还得操心资源回收。

以下载文件为例,通过 Apache 的 HttpClient方式进行下载文件,会很复杂
其实Spring已经为我们提供了一种简单便捷的模板类来进行操作,它就是RestTemplate

RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在 HTTP 客户端库(例如 JDK HttpURLConnection,Apache HttpComponents,okHttp 等)基础上,封装了更加简单易用的模板方法 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

<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的HttpClient、Apache的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():返回的是ResponseEntity,ResponseEntity是对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 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请求方法上大同小异,RestTemplate的POST请求也包含两个主要方法:

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方法请求。

打开源码,我们可以很清晰的看到这一点。

在这里插入图片描述

1.3.6 文件上传与下载

除了经常用到的get和post请求以外,还有一个经常会碰到的场景,那就是文件的上传与下载,如果采用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等等文件,特别管用,

1.3.7 通过服务名调用

1.3.7.1 简介

在Spring Cloud中,通过服务名称访问其他服务通常需要使用@LoadBalanced的RestTemplate,可以使用服务名(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,然后使用服务名称来发送请求。

注意:这种方法需要服务被注册到服务发现组件(如Eureka,Nacos,Consul等)。这样,应用才能找到服务名对应的实际网络位置。

1.3.7.2 @LoadBalanced作用

@LoadBalanced 是 Netflix 的 ribbon 中的一个负载均衡的注解,并完成以下工作:

从负载均衡器中选一个对应的服务实例,所有的服务名实例都放在负载均衡器中的serverlist中;
从挑选的实例中去请求内容;
由服务名转为真正使用的ip地址;

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对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/211565.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

西南科技大学模拟电子技术实验四(集成运算放大器的线性应用)预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 反相比例运算电路(1)实验…

VMware Workstation Pro 17及 Windows 11 虚拟机的安装与激活

六点钟&#xff1a; 吃晚饭吗 不吃&#xff0c;胖胖 十点钟&#xff1a; 阿昊要吃夜宵对不对 ——CSDN&#xff0c;记录牛马生活 本文是在学习 Linux 期间&#xff0c;使用 VMware 时顺带学习 Windows 11 虚拟机的安装与激活 VMware Workstation Pro 17及 Windows 11 虚拟机…

MacDroid Pro for Mac – 安卓设备文件传输助手,实现无缝连接与传输!

想要在Mac电脑上轻松管理和传输您的安卓设备文件吗&#xff1f;MacDroid Pro for Mac 是您的最佳选择&#xff01;这款强大的文件传输助手可以让您在Mac上与安卓设备之间实现快速、方便的文件传输。 MacDroid Pro for Mac 提供了简单易用的界面&#xff0c;让您能够直接在Mac上…

基于Springboot + vue的汽车资讯网站

qq&#xff08;2829419543&#xff09;获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;springboot 前端&#xff1a;采用vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xf…

Vue学习记录

Vue学习记录 一、搭建Vue开发环境 到官网下载安装Vue&#xff1a;安装 — Vue.js (vuejs.org) ----->点击开发版下载 怎么解决&#xff1f; 到官网&#xff1a;安装 — Vue.js 点击&#xff1a; 下滑找到&#xff1a; 下载相应的版本的就行&#xff1a; 下载拓展后刷新…

Collection的其他相关知识

前置知识&#xff1a;可变参数 就是一种特殊参数&#xff0c;定义在方法 构造器的形参列表里&#xff0c;格式是&#xff1a;数据类型...参数名称&#xff1b; 可变参数的特点和好处 特点&#xff1a;可以不传数据给它&#xff1b;可以传一个或者同时传多个数据给它&#xff…

Halcon tiff 点云读取以及平面矫正

一、读取tiff 图 dev_close_window () dev_open_window (0, 0, 512, 512, black, WindowHandle)xResolution:0.0025 yResolution:0.0025 zResolution:0.001 read_image (IntputImage, C:/Users/alber/Desktop/2023-08-15_16-38-24-982_/Sta5_002.tif) zoom_image_factor (Intpu…

YOLOv8改进 | 2023 | 给YOLOv8换个RT-DETR的检测头(重塑目标检测前沿技术)

一、本文介绍 本文给大家带来是用最新的RT-DETR模型的检测头去替换YOLOv8中的检测头。RT-DETR号称是打败YOLO的检测模型&#xff0c;其作为一种基于Transformer的检测方法&#xff0c;相较于传统的基于卷积的检测方法&#xff0c;提供了更为全面和深入的特征理解&#xff0c;将…

【探索Linux】—— 强大的命令行工具 P.19(多线程 | 线程的概念 | 线程控制 | 分离线程)

阅读导航 引言一、 Linux线程概念1. 什么是线程2. 线程的概念3. 线程与进程的区别4. 线程异常 二、Linux线程控制1. POSIX线程库2. 创建线程 pthread_create() 函数&#xff08;1&#xff09;头文件&#xff08;2&#xff09;函数原型&#xff08;3&#xff09;参数解释&#x…

初识消息队列

1、消息 消息&#xff08;Message&#xff09;是指在应用间传送的数据。消息可以非常简单&#xff0c;比如只包含文本字符串&#xff0c;也可以更复杂&#xff0c;可能包含嵌入对象。 2、消息队列 消息队列&#xff08;Message Queue&#xff09;是一种应用间的通信方式&#…

力扣题:字符串的反转-11.23

力扣题-11.23 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;557. 反转字符串中的单词 III 解题思想&#xff1a;先读取单词&#xff0c;然后将单词进行翻转即可 class Solution(object):def reverseWords(self, s):""":type s…

C语言-指针_02

指针-02 1. 指针的指针 概念&#xff1a;指针变量中存储的是指针的地址&#xff0c;又名 二维指针 语法&#xff1a; 数据类型 **p;示例&#xff1a; #include <stdio.h> int main(int argc, char const *argv[]) {int num 10;int *p1 &num;int **p2 &p1…

win11 无法登录微软账户 终极解决方案

背景&#xff1a;win11突然无法登录微软账户&#xff0c;office无法激活&#xff0c;Edge里的微软账户也无法登录&#xff0c;反馈中心也无法打开等&#xff0c;有网络&#xff0c;浏览器可以访问微软并进行登录。 试过网上的网络配置&#xff08;SSL及TLS协议勾选&#xff09…

论文编写软件latex安装教程

目录 1.下载安装包2.安装texlive 本人系统为windows&#xff0c;本教程基于windows系统&#xff0c;如果是其它系统请参考对应教程&#xff0c;注意选择对应系统的安装包&#xff01; 1.下载安装包 有三种集成环境安装包 texlive 是主流的环境&#xff0c;集成了较多的包&…

k8s(三): 基本概念-ReplicaSet与Deployment

PeplicaSet ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合&#xff0c;通常用来保证给定数量的、完全相同的 Pod 的可用性。 最佳实践 Deployment 是一个可以拥有 ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。 尽管 Rep…

华为杯研究生数学建模优秀参考论文(优秀论文参考2004-2022年)

一、背景介绍 中国研究生数学建模竞赛是一项面向在校研究生进行数学建模应用研究的学术竞赛活动&#xff0c;是广大在校研究生提高建立数学模型和运用互联网信息技术解决实际问题能力&#xff0c;培养科研创新精神和团队合作意识的大平台&#xff0c;大赛赞助单位为华为技术有限…

<JavaEE> synchronized关键字和锁机制 -- 锁的特点、锁的使用、锁竞争和死锁、死锁的解决方法

目录 一、synchronized 关键字简介 二、synchronized 的特点 -- 互斥 三、synchronized 的特点 -- 可重入 四、synchronized 的使用示例 4.1 修饰代码块 - 锁任意实例 4.2 修饰代码块 - 锁当前实例 4.3 修饰普通方法 - 锁方法所在实例 4.4 修饰代码块 - 锁指定类对象 …

【开源】基于JAVA语言的校园疫情防控管理系统

项目编号&#xff1a; S 037 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S037&#xff0c;文末获取源码。} 项目编号&#xff1a;S037&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生2.2 老师2.3 学校管理部门 三、…

【数据结构】二叉树---C语言版

二叉树 一、树的概念及结构1.树的概念2.树的相关概念3.树的表示4.树在实际中的应用 二、二叉树的概念及结构1.二叉树的概念2.满二叉树3.完全二叉树4.二叉树的性质5.二叉树的储存结构 三、二叉树的遍历1.前序遍历2.中序遍历3.后序遍历4.层序遍历 四、手撕二叉树&#xff08;务必…

基于JSP的网络考试系统/在线考试系统的设计与实现

摘 要 网络考试系统是由高校的一个网络考试&#xff0c;按照章程自主开展网络考试系统。网络考试是实施素质教育的重要途径和有效方式&#xff0c;在加强校园文化建设、提高学生综合素质、引导学生适应社会、促进学生成才就业等方面发挥着重要作用&#xff0c;是新形势下有效凝…