Spring之RestTemplate常用API实践

目录

  • 1、RestTemplate简介
  • 2、RestTemplate初始化配置
  • 3、RestTemplate常用API实践
    • 3.1、GET类型方法
      • 3.1.1、GET无参请求方法之getForObject()
      • 3.1.2、GET占位符传参请求方法之getForObject()
      • 3.1.3、GET带参Restful请求方法之getForObject()
      • 3.1.4、GET带参Restful请求方法之getForEntity()
    • 3.2、POST类型方法
      • 3.2.1、POST无参请求方法之postForObject()
      • 3.2.2、POST占位符请求方法之postForObject()
      • 3.2.3、POST表单提交(restful)请求方法之postForObject()
      • 3.2.4、POST传递JSON数据请求方法之postForObject()
      • 3.2.5、POST传递JSON数据请求重定向方法之postForEntity()
      • 3.2.6、POST传递JSON数据请求重定向方法之postForLocation()
    • 3.3、PUT类型方法
    • 3.4、DELETE类型方法
    • 3.5、exchange通用请求类型方法
      • 3.5.1、模拟DELETE类型方法
      • 3.5.1、模拟POST类型方法
    • 3.6、文件上传与下载
      • 3.6.1、文件上传
      • 3.6.2、文件下载
      • 3.6.3、大文件下载
    • 3.7、excute执行方法
      • 3.7.1、RequestCallback接口
      • 3.7.2、ResponseExtractor接口
      • 3.7.3、RestTemplate执行流程


1、RestTemplate简介

在开发的过程中,很多时候我们需要从当前服务端(当前项目)向外进行网络请求(其他项目)获取数据或传送数据,传统情况下,我们一般会使用JDK自带的HttpURLConnectionApache提供的HttpClientOkHttp等客户端请求工具,但是这几种方式使用起来比较繁琐,对于初学者来说不太友好,而Spring为我们提供了一个快捷方便的Restful模板类请求工具类RestTemplate

RestTemplate是一个执行HTTP请求的同步阻塞式请求工具类,它是在JDK HttpURLConnectionApache HttpComponentsOkHttp等客户端的基础上,封装了更高级别的API,使在单行中调用REST端点变得容易。RestTemplate默认是依赖JDK HttpURLConnection,可以根据实际需要通过构造函数中的setRequestFactory方法设置底层依赖请求客户端。

public RestTemplate(ClientHttpRequestFactory requestFactory) {
      this();
      this.setRequestFactory(requestFactory);//指定HTTP客户端
}

2、RestTemplate初始化配置

如果当前是SpringBoot项目(非spring环境),我们需要导入Spring-web依赖,即可引入RestTemplate类:

<dependency>
   <groupId>org.springframeworkgroupId>
   <artifactId>spring-webartifactId>
   <version>5.2.0.RELEASEversion>
dependency>

如果当前是SpringBoot项目(spring环境),我们只需要导入spring-boot-starter-web依赖,即可引入RestTemplate类:

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-webartifactId>
   <version>2.2.0.RELEASEversion>
dependency>

Spring之RestTemplate常用API实践_第1张图片

内置支持以下功能:

  • JDK HttpURLConnection
  • Apache HttpComponents
  • OkHttp

RestTemplate包含以下几个部分:

  • HttpMessageConverter对象转换器
  • ClientHttpRequestFactory 默认是JDKHttpURLConnection
  • ResponseErrorHandler 异常处理器
  • ClientHttpRequestInterceptor 请求拦截器

我们现在大多使用SpringBoot框架,为了后续方便使用,我们将RestTemplate配置初始化为一个Bean

@Configuration
public class RestTemplateConfig {

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate(getSimpleClientHttpRequestFactory());
        return restTemplate;
    }
    
    /**
     * 默认底层依赖JDK HttpURLConnection作为HTTP客户端
     * @return
     */
    public ClientHttpRequestFactory getSimpleClientHttpRequestFactory() {
        int timeout = 5000;
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(timeout);
        factory.setConnectTimeout(timeout);
        return factory;
    }
}

在项目启动的时候会默认加载当前配置类中的@Bean注解方法,将restTemplate()方法注入Spring容器中,注意: @Bean注解的方法名最好是restTemplate首字母小写,后续我们在需要用到的地方直接@Autowire自动装载即可使用。

@Autowired
private RestTemplate restTemplate;

通过管方指导文档描述:

The default constructor uses java.net.HttpURLConnection to perform requests. You can switch to a different HTTP library with an implementation of ClientHttpRequestFactory. There is built-in support for the following:
释义:默认构造函数使用java.net.HttpURLConnection执行请求。您可以切换到具有`ClientHttpRequestFactory`实现的不同HTTP库。
    
//直接new RestTemplate();底层依赖的也是JDK HttpURLConnection

如果我们想切换到Apache HttpComponents客户端,可以使用以下方式:

首先导入Apache HttpClient依赖:


<dependency>
    <groupId>org.apache.httpcomponentsgroupId>
    <artifactId>httpclientartifactId>
    <version>4.5.6version>
dependency>

初始化:

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

但是通常情况下,我们切换客户端会预先做一些连接请求超时参数配置,如下:

@Configuration
public class RestTemplateConfig {

    /**
     * 使用Apache HttpClient作为底层客户端
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
		//RestTemplate restTemplate = new RestTemplate();
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }


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

切换为OkHttp客户端:

先导入依赖:

<dependency>
    <groupId>com.squareup.okhttp3groupId>
    <artifactId>okhttpartifactId>
    <version>4.7.2version>
dependency>

初始化:

RestTemplate template = new RestTemplate(new OkHttp3ClientHttpRequestFactory());

预先做一些连接请求超时参数配置,如下:

@Configuration
public class RestTemplateConfig {

    /**
     * 使用OkHTTP作为底层客户端
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
		//RestTemplate restTemplate = new RestTemplate();
		//RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        RestTemplate restTemplate = new RestTemplate(getOkHttp3ClientHttpRequestFactory());
        return restTemplate;
    }

    /**
     * 使用OkHTTP作为底层客户端
     * @return
     */
    private OkHttp3ClientHttpRequestFactory getOkHttp3ClientHttpRequestFactory(){
        int timeout = 5000;
        OkHttpClient okHttpClient = new OkHttpClient();
        OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient){{{
            setConnectTimeout(timeout);
            setReadTimeout(timeout);
            setWriteTimeout(timeout);
        }}};
        return okHttp3ClientHttpRequestFactory;
    }
}

具网上不完全统计,上述Http客户端的效率:

OkHttp > Apache HttpClient > JDK HttpURLConnection

所以下述我们以OkHttp作为底层客户端依赖。

扩展:

@ConditionalOnMissingBean(RestTemplate.class)

当注入多个相同类型的Bean时,会报异常,此注解就是保证当前类型Bean只能有一个实例化对象。

@ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean

@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean

@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean

@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean

3、RestTemplate常用API实践

Spring之RestTemplate官方指导文档。

它公开了以下几组重载方法:

方法 描述
getForObject 通过GET检索一个表示。
getForEntity 通过使用GET检索一个ResponseEntity(即状态、标题和主体)。
headForHeaders 通过使用HEAD检索资源的所有标头。
postForLocation 使用POST创建一个新资源,并从响应中返回Location报头。
postForObject 使用POST创建新资源,并从响应返回表示。
postForEntity 使用POST创建新资源,并从响应返回表示。
put 通过使用PUT创建或更新资源。
patchForObject 使用PATCH更新资源,并从响应返回表示。注意,JDK HttpURLConnection 不支持PATCH,但Apache HttpComponents和其他组件支持。
delete 使用DELETE删除指定URI处的资源。
optionsForAllow 使用ALLOW为资源检索允许的HTTP方法。
exchange 在需要时提供额外灵活性的前面方法的更一般化(且不那么自以为是)版本。它接受一个’RequestEntity (包括HTTP方法、URL、报头和主体作为输入),并返回一个ResponseEntity。这些方法允许使用ParameterizedTypeReference代替Class来指定具有泛型的响应类型。
execute 执行请求的最通用方法,通过回调接口完全控制请求准备和响应提取。

将上述方法大致归为三类:

常用的Rest API(GET、POST、PUT、DELETE):

  • getForObject
  • getForEntity
  • headForHeaders
  • postForLocation
  • postForObject
  • postForEntity
  • put
  • patchForObject
  • delete
  • optionsForAllow

接收一个 RequestEntity 参数,自定义设置HTTP methodURLheadersbody,返回 ResponseEntity:

  • exchange

通过 callback 接口,可以对请求和返回做更加全面的自定义控制:

  • execute

创建一个响应实体类ResponseBean

@Data
@ToString
public class ResponseBean<T> {
    /**
     * 状态码 200 成功 其他失败
     */
    private String code;

    /**
     * 响应数据
     */
    private T data;

    /**
     * 异常信息
     */
    private String message;
}

创建一个请求实体类RequestBean

@Data
public class RequestBean {
    /**
     * 用户id
     */
    private String userCode;

    /**
     * 
     */
    private String userName;
}

3.1、GET类型方法

通过RestTemplate发送HTTP GET协议请求,经常会用到以下两个方法:

  • getForObject():返回值是HTTP协议的响应体内容
  • getForEntity():返回的是ResponseEntityResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息

getForObject()重载方法:

@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {}

@Nullable
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}

@Nullable
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {}

getForEntity()重载方法:

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {}

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

public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {}

3.1.1、GET无参请求方法之getForObject()

创建一个无参get请求接口:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/no/param/test/get")
    public ResponseBean<String> testGet1(){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("无参get请求!");
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试无参get请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGet1(){
        String url = "http://localhost:6625/rest/no/param/test/get";
        ResponseBean response = restTemplate.getForObject(url, ResponseBean.class);
        System.out.println(response);
    }
}

打印结果:

{"code":"200","data":"响应成功了!","message":null}

发现出现了乱码,原因在于RestTemplate中对字符串默认使用的转换器是StringHttpMessageConverter,而StringHttpMessageConverter默认的字符集编码是ISO_8859_1:

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
    public static final Charset DEFAULT_CHARSET;

    public StringHttpMessageConverter() {
        this(DEFAULT_CHARSET);
    }  
    
    static {
        //默认字符集
        DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
    }
}

ISO_8859_1编码格下,中文是乱码的。因此我们需要将编码格式设置为UTF-8的格式才能支持中文:

@Configuration
public class RestTemplateConfig {


    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate(getOkHttp3ClientHttpRequestFactory());
        //设置RestTemplate的字符集为UTF-8
        setRestTemplateEncode(restTemplate);
        return restTemplate;
    }

    /**
     * 使用OkHTTP作为底层客户端
     * @return
     */
    private OkHttp3ClientHttpRequestFactory getOkHttp3ClientHttpRequestFactory(){
        int timeout = 5000;
        OkHttpClient okHttpClient = new OkHttpClient();
        OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient){{{
            setConnectTimeout(timeout);
            setReadTimeout(timeout);
            setWriteTimeout(timeout);
        }}};
        return okHttp3ClientHttpRequestFactory;
    }

    /**
     * 指定设置RestTemplate的字符转换器编码为UTF_8
     * @param restTemplate
     */
    public static void setRestTemplateEncode(RestTemplate restTemplate) {
        if (ObjectUtils.isEmpty(restTemplate) || ObjectUtils.isEmpty(restTemplate.getMessageConverters())){
            return;
        }
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (int i = 0; i < messageConverters.size(); i++) {
            HttpMessageConverter<?> httpMessageConverter = messageConverters.get(i);
            if (StringHttpMessageConverter.class.equals(httpMessageConverter.getClass())){
                messageConverters.set(i,new StringHttpMessageConverter(StandardCharsets.UTF_8));
            }
        }
    }
}

再次重启,打印结果:

ResponseBean(code=200, data=无参get请求!, message=null)

问题解决!

3.1.2、GET占位符传参请求方法之getForObject()

创建一个占位符传参get请求接口:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;
    
    @GetMapping("/path/param/test/get/{code}/{data}")
    public ResponseBean<String> testGet2(@PathVariable("code") String code,@PathVariable("data") String data){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode(code);
        responseBean.setData(data);
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试占位符传参get请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGet2(){
        String url = "http://localhost:6625/rest/path/param/test/get/{code}/{data}";
        ResponseBean response = restTemplate.getForObject(url, ResponseBean.class,"200","占位符传参get请求!");
        System.out.println(response);
    }
}

打印结果:

ResponseBean(code=200, data=占位符传参get请求!, message=null)

3.1.3、GET带参Restful请求方法之getForObject()

创建一个带参RestFul请求方法:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/restful/param/test/get")
    public ResponseBean<String> testGet3(@RequestParam("userCode") String userCode,@RequestParam("userName") String userName){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("userCode = " + userCode + ",userName = " + userName);
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试Restful传参get请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGet3(){
        String url = "http://localhost:6625/rest/restful/param/test/get?userCode={userCode}&userName={userName}";
        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("userCode","123456");
        paramMap.put("userName","张三三");
        ResponseBean response = restTemplate.getForObject(url, ResponseBean.class,paramMap);
        System.out.println(response);
    }
}

打印结果:

ResponseBean(code=200, data=userCode = 123456,userName = 张三三, message=null)

3.1.4、GET带参Restful请求方法之getForEntity()

getForEntity方法的应用和上述getForObject类似,三种重载方法参数一致,只是返回的结果略有差异,下面我们只演示Restful传参get请求请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGet4(){
        String url = "http://localhost:6625/rest/restful/param/test/get?userCode={userCode}&userName={userName}";
        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("userCode","123456");
        paramMap.put("userName","张三三");
        ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url, ResponseBean.class, paramMap);
        System.out.println(response);
    }
}

打印结果:

HTTP 响应结果体:<200,ResponseBean(code=200, data=userCode = 123456,userName = 张三三, message=null),[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 02:55:35 GMT", Transfer-Encoding:"chunked"]>
HTTP 响应状态:200 OK
HTTP 响应状态码:200
HTTP 响应headers信息:[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 02:55:35 GMT", Transfer-Encoding:"chunked"]
HTTP 响应headers信息内容类型:application/json
HTTP 响应headers信息内容长度:-1

如果我们想要完整的HTTP响应体,那推荐使用getForEntity,可以根据响应码判断是否响应成功!

3.2、POST类型方法

我们在平时开发的过程中可以了解到,其实POST请求方法和GET请求方法上大同小异,RestTemplatePOST请求也主要包含以下几个方法,比POST多了一个postForLocation方法:

  • postForObject():返回值是HTTP协议的响应体body内容
  • postForEntity():返回的是ResponseEntityResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息
  • postForLocation:使用POST创建一个新资源,返回UTI对象,可以从响应中返回Location报头。

getForObject()重载方法:

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

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

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

getForEntity()重载方法:

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

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

public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}

getForLocation()重载方法:

@Nullable
public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {}

@Nullable
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException {}

@Nullable
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException {}

3.2.1、POST无参请求方法之postForObject()

创建一个post无参请求方法:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/no/param/test/post")
    public ResponseBean<String> testPost1(){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("无参post请求!");
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试无参post请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPost1(){
        String url = "http://localhost:6625/rest/no/param/test/post";
        ResponseBean response = restTemplate.postForObject(url, null, ResponseBean.class);
        System.out.println(response);
    }
}

打印结果:

ResponseBean(code=200, data=无参post请求!, message=null)

3.2.2、POST占位符请求方法之postForObject()

创建一个post无参请求方法:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/path/param/test/post/{code}/{data}")
    public ResponseBean<String> testPost2(@PathVariable("code") String code,
                                          @PathVariable("data") String data){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode(code);
        responseBean.setData(data);
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试无参post请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPost2(){
        String url = "http://localhost:6625/rest/path/param/test/post/{code}/{data}";
        ResponseBean response = restTemplate.postForObject(url, null, ResponseBean.class,"200","占位符post请求");
        System.out.println(response);
    }
}

打印结果:

ResponseBean(code=200, data=占位符post请求, message=null)

3.2.3、POST表单提交(restful)请求方法之postForObject()

创建一个post表单提交请求方法:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/form/param/test/post")
    public ResponseBean<String> testPost3(@RequestParam("userCode") String userCode,
                                          @RequestParam("userName") String userName){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("userCode = " + userCode + ",userName = " + userName);
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试表单提交post请求:

	/**
     * postForObject 表单form传参(restful)
     */
    @Test
    public void testPost3(){
        String url = "http://localhost:6625/rest/form/param/test/post";
        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("userCode","123456");
        paramMap.put("userName","李四四");
        ResponseBean response = restTemplate.postForObject(url, null, ResponseBean.class,paramMap);
        System.out.println(response);
    }

这时候发现上述请求会报错,因为post提交表单请求,需要将参数封装在请求体中,还要指定请求头信息,也就是postForObject方法的第二个参数位,如下所示:

源码请求头信息ContentType

public static final MediaType APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPost3(){
        String url = "http://localhost:6625/rest/form/param/test/post";
        //请求头设为application/x-www-form-urlencoded
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //表单参数(专用map)
        MultiValueMap<String,String> paramMap = new LinkedMultiValueMap<String,String>();
        paramMap.add("userCode","123456");
        paramMap.add("userName","李四四");
        //组装请求体
        HttpEntity<MultiValueMap<String,String>> requestEntity = new HttpEntity<>(paramMap,httpHeaders);
        ResponseBean response = restTemplate.postForObject(url, requestEntity, ResponseBean.class);
        System.out.println(response);
    }
}

打印结果:

ResponseBean(code=200, data=userCode = 123456,userName = 李四四, message=null)

上述传递的表单参数,是用@RequestParam接收的:

@RequestParam("userCode") String userCode,
@RequestParam("userName") String userName

还可以通过实体类对象映射接收,将上述post表单提交请求方法的参数列表改为实体类RequestBean

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/form/param/test/post")
    public ResponseBean<String> testPost3(RequestBean requestBean){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
        responseBean.setMessage(null);
        return responseBean;
    }
}

再次请求,效果一样一样的!

3.2.4、POST传递JSON数据请求方法之postForObject()

将上述post表单提交请求方法的参数列表改为实体类RequestBean

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/json/param/test/post")
    public ResponseBean<String> testPost4(@RequestBody RequestBean requestBean){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试传递json数据提交post请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPost4(){
        String url = "http://localhost:6625/rest/json/param/test/post";
        RequestBean requestBean = new RequestBean();
        requestBean.setUserCode("123456");
        requestBean.setUserName("张五五");
        ResponseBean response = restTemplate.postForObject(url, requestBean, ResponseBean.class);
        System.out.println(response);
    }
}

打印结果:

ResponseBean(code=200, data=userCode = 123456,userName = 张五五, message=null)

3.2.5、POST传递JSON数据请求重定向方法之postForEntity()

POST请求postForEntity()方法和postForObject()重载方法用法一样,只是返回结果不同!!!

将上述post表单提交请求方法的参数列表改为实体类RequestBean

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/json/param/test/post/entity")
    public ResponseBean<String> testPost5(@RequestBody RequestBean requestBean){
        ResponseBean<String> responseBean = new ResponseBean<String>();
        responseBean.setCode("200");
        responseBean.setData("userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
        responseBean.setMessage(null);
        return responseBean;
    }
}

单元测试传递json数据提交post请求:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPost5(){
        String url = "http://localhost:6625/rest/json/param/test/post/entity";
        //请求头请求头设为application/json
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        RequestBean requestBean = new RequestBean();
        requestBean.setUserCode("123456");
        requestBean.setUserName("马七七");
        //组装请求体(直接传实体类requestBean,也可以识别为json对象数据)
        HttpEntity<RequestBean> requestEntity = new HttpEntity<>(requestBean,httpHeaders);
        ResponseEntity<ResponseBean> response = restTemplate.postForEntity(url, requestEntity, ResponseBean.class);
        System.out.println("HTTP 响应结果体:" + response);
        System.out.println("HTTP 响应状态:" + response.getStatusCode());
        System.out.println("HTTP 响应状态码:" + response.getStatusCodeValue());
        System.out.println("HTTP 响应headers信息:" + response.getHeaders());
        System.out.println("HTTP 响应headers信息内容类型:" + response.getHeaders().getContentType());
        System.out.println("HTTP 响应headers信息内容长度:" + response.getHeaders().getContentLength());
    }
}

打印结果:

HTTP 响应结果体:<200,ResponseBean(code=200, data=userCode = 123456,userName = 马七七, message=null),[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 09:51:53 GMT", Transfer-Encoding:"chunked"]>
HTTP 响应状态:200 OK
HTTP 响应状态码:200
HTTP 响应headers信息:[Content-Type:"application/json", Date:"Sat, 29 Oct 2022 09:51:53 GMT", Transfer-Encoding:"chunked"]
HTTP 响应headers信息内容类型:application/json
HTTP 响应headers信息内容长度:-1

3.2.6、POST传递JSON数据请求重定向方法之postForLocation()

postForLocation方法的返回值是一个URI对象,POST请求一般用来新增数据,举个例子:用户注册之后,一半情况下会自动带着用户注册信息跳转到登录界面,这个时候就可以用到postForLocation,也就是第一次请求之后,会返回一个请求资源URI,这个资源可以是重定向,也可以是其他操作。

注意:@RestController 要换成@Controller,需要返回json数据的方法,标注@ResponseBody

创建请求方法:

@Controller
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 当做注册请求
     * @param requestBean
     * @return
     */
    @PostMapping("/redirect/json/param/test/post")
    public String testPostLocation(@RequestBody RequestBean requestBean){
        //重定向到登录请求
        return "redirect:/rest/testPostByLocation";
    }

    /**
     * 当做登录请求
     * @param requestBean
     * @return
     */
    @GetMapping("/testPostByLocation")
    @ResponseBody
    public String testPostByLocation(RequestBean requestBean){
        return "hello postByLocation";
    }
}	

单元测试:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPostLocation(){
        String url = "http://localhost:6625/rest/redirect/json/param/test/post";
        RequestBean requestBean = new RequestBean();
        requestBean.setUserCode("123456");
        requestBean.setUserName("张五五");
        URI uri = restTemplate.postForLocation(url, requestBean);
        System.out.println(uri);
    }
}

打印结果:

http://localhost:6625/rest/testPostByLocation

POST请求postForLocation()方法和前两种方法postForObject()postForEntity()重载方法用法一样,只是返回结果不同!!!

3.3、PUT类型方法

PUT请求一般是用来新增或者修改资源,重载方法的用法和GETPOST也基本一样,只是没有返回值。

重载方法:

public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {}

public void put(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException {}

public void put(URI url, @Nullable Object request) throws RestClientException {}

创建请求方法:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PutMapping("/json/param/test/put")
    public void testPut(@RequestBody RequestBean requestBean){
        System.out.println("put请求更新成功了!userCode = " + requestBean.getUserCode() + ",userName = " + requestBean.getUserName());
    }
}	

单元测试:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPut(){
        String url = "http://localhost:6625/rest/json/param/test/put";
        RequestBean requestBean = new RequestBean();
        requestBean.setUserCode("123456");
        requestBean.setUserName("张五五");
        restTemplate.put(url,requestBean);
    }
}

打印结果:

put请求更新成功了!userCode = 123456,userName = 张五五

3.4、DELETE类型方法

DELETE请求一般是用来删除资源,重载方法的用法和GETPOSTDELETE、也基本一样, 参数列表稍有区别也是没有返回值。

重载方法:

public void delete(String url, Object... uriVariables) throws RestClientException {}

public void delete(String url, Map<String, ?> uriVariables) throws RestClientException {}

public void delete(URI url) throws RestClientException {}

创建请求方法:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @DeleteMapping("/restful/param/test/delete")
    public void testDelete(@RequestParam("userCode") String userCode,
                           @RequestParam("userName") String userName){
        System.out.println("delete请求删除成功了!userCode = " + userCode + ",userName = " + userName);
    }
}	

单元测试:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testDelete(){
        String url = "http://localhost:6625/rest/restful/param/test/delete?userCode={userCode}&userName={userName}";
        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("userCode","123456");
        paramMap.put("userName","张三三");
        restTemplate.delete(url,paramMap);
    }
}

打印结果:

delete请求删除成功了!userCode = 123456,userName = 张三三

3.5、exchange通用请求类型方法

如果觉得上述常用请求方法还不够灵活,可以使用exchange通用协议请求方法,可以根据自己的需要,自由配置请求头、请求体等参数,常见的GET、POST、DELETE、PUT、OPTIONS、PATCH,这些请求都可以通过exchange实现,看下源码重载方法:

<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, Class<T> var4, Object... var5) throws RestClientException;

<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, Class<T> var4, Map<String, ?> var5) throws RestClientException;

<T> ResponseEntity<T> exchange(URI var1, HttpMethod var2, @Nullable HttpEntity<?> var3, Class<T> var4) throws RestClientException;

<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, ParameterizedTypeReference<T> var4, Object... var5) throws RestClientException;

<T> ResponseEntity<T> exchange(String var1, HttpMethod var2, @Nullable HttpEntity<?> var3, ParameterizedTypeReference<T> var4, Map<String, ?> var5) throws RestClientException;

<T> ResponseEntity<T> exchange(URI var1, HttpMethod var2, @Nullable HttpEntity<?> var3, ParameterizedTypeReference<T> var4) throws RestClientException;

<T> ResponseEntity<T> exchange(RequestEntity<?> var1, Class<T> var2) throws RestClientException;

<T> ResponseEntity<T> exchange(RequestEntity<?> var1, ParameterizedTypeReference<T> var2) throws RestClientException;

解释其一:

public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {}
  • url:请求地址
  • method:请求类型
  • requestEntity:请求体
  • responseType:响应类型
  • uriVariables:请求参数

PUTDELETE没有返回值,也要默认指定一个类型,直接Object.class即可!

3.5.1、模拟DELETE类型方法

使用上述方法单元测试,模拟DELETE请求方法:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testExchangeDelete(){
        String url = "http://localhost:6625/rest/restful/param/test/delete?userCode={userCode}&userName={userName}";
        //请求参数
        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("userCode","123456");
        paramMap.put("userName","张三三");
        //请求头
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //组装请求体
        HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(httpHeaders);
        restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, Object.class,paramMap);
    }
}

打印结果:

delete请求删除成功了!userCode = 123456,userName = 张三三

和使用delete请求方法效果一样一样的!并不是更佳麻烦,而是更佳灵活!!!

3.5.1、模拟POST类型方法

使用上述方法单元测试,模拟POST请求方法postForEntity

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testExchangePostForObject(){
        String url = "http://localhost:6625/rest/json/param/test/post/entity";
        //请求头请求头设为application/json
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        RequestBean requestBean = new RequestBean();
        requestBean.setUserCode("123456");
        requestBean.setUserName("马七七");
        //组装请求体(直接传实体类requestBean,也可以识别为json对象数据)
        HttpEntity<RequestBean> requestEntity = new HttpEntity<>(requestBean,httpHeaders);

        //postForEntity方法
        //ResponseEntity response = restTemplate.postForEntity(url, requestEntity, ResponseBean.class);

        //exchange方法
        ResponseEntity<ResponseBean> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, ResponseBean.class);

        System.out.println("HTTP 响应结果体:" + response);
        System.out.println("HTTP 响应状态:" + response.getStatusCode());
        System.out.println("HTTP 响应状态码:" + response.getStatusCodeValue());
        System.out.println("HTTP 响应headers信息:" + response.getHeaders());
        System.out.println("HTTP 响应headers信息内容类型:" + response.getHeaders().getContentType());
        System.out.println("HTTP 响应headers信息内容长度:" + response.getHeaders().getContentLength());
    }
}

打印结果:

HTTP 响应结果体:<200,ResponseBean(code=200, data=userCode = 123456,userName = 马七七, message=null),[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Sun, 30 Oct 2022 05:12:38 GMT"]>
HTTP 响应状态:200 OK
HTTP 响应状态码:200
HTTP 响应headers信息:[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Sun, 30 Oct 2022 05:12:38 GMT"]
HTTP 响应headers信息内容类型:application/json
HTTP 响应headers信息内容长度:-1

和使用postForEntity请求方法效果一样一样的!!!

只要方法掌握了,所有的请求类型基本都可以兼容,兄弟们,方便又哇塞哈!!!

public enum HttpMethod {
    GET,
    HEAD,
    POST,
    PUT,
    PATCH,
    DELETE,
    OPTIONS,
    TRACE;
}

3.6、文件上传与下载

RestTemplate常用于请求资源或添加资源,也可以用来上传和下载资源!

3.6.1、文件上传

创建文件上传请求:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/file/test/post/upload")
    public ResponseBean testPostUpload(@RequestParam("multipartFile") MultipartFile multipartFile,
                                       @RequestParam("userCode") String userCode,
                                       @RequestParam("userName") String userName) throws Exception {
        //上传存放路径
        File folder = new File(UPLOAD_PATH + userCode + "/" + userName);
        if (!folder.isDirectory()){
            //如果不是文件夹,则创建为一个多级目录文件夹
            boolean mkdirs = folder.mkdirs();
            if (BooleanUtils.isNotTrue(mkdirs)){
                throw new Exception("创建文件目录异常!");
            }
        }

        //对上传文件重命名
        String originalFilename = multipartFile.getOriginalFilename();
        String newFilename = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));

        ResponseBean responseBean = new ResponseBean();
        try {
            //文件保存
            multipartFile.transferTo(new File(folder,newFilename));
            responseBean.setCode("200");
            responseBean.setData("文件名:" + newFilename);
            responseBean.setMessage("文件上传成功!");
        }catch (IOException e){
            LOGGER.error("上传文件异常!",e.getMessage());
            responseBean.setCode("500");
            responseBean.setData("文件名:" + newFilename);
            responseBean.setMessage("文件上传失败!");
        }
        return responseBean;
    }
}	

单元测试,上传图片文件到指定文件目录:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testPostUpload(){
        //需要上传的文件
        String filePath = "/D:/Workspace/壁纸/傍晚雪山.jpg";

        //请求地址
        String url = "http://localhost:6625/rest/file/test/post/upload";

        // 请求头设置,multipart/form-data格式的数据
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        //提交参数设置
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
        param.add("multipartFile", new FileSystemResource(new File(filePath)));
        param.add("userCode", "anightmonarch");
        param.add("userName", "一宿君");

        // 组装请求体
        HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(param, headers);

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

打印结果:

ResponseBean(code=200, data=文件名:aa4da235-43ef-4687-a16c-1911ca0ab3ba.jpg, message=文件上传成功!)

Spring之RestTemplate常用API实践_第2张图片

3.6.2、文件下载

创建文件下载请求:

@RestController
@RequestMapping("/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/file/test/get/download")
    public void testGetDownload(@RequestParam("fileName") String fileName,
                                 @RequestParam("userCode") String userCode,
                                 @RequestParam("userName") String userName,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        //组装要下载文件所在的路径
        File file = new File(UPLOAD_PATH + userCode + "/" + userName + File.separator + fileName);
        if (!file.exists()){
            throw new Exception("文件路径不存在!");
        }
        //获取文件流
        FileInputStream fileInputStream = new FileInputStream(file);
        //获取文件扩展名
        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 outputStream = response.getOutputStream();
        //使用spring框架中的FileCopyUtils工具,下载文件
        FileCopyUtils.copy(fileInputStream,outputStream);
    }
}	

单元测试,下载文件到指定文件目录:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGetDownload() throws IOException {
        //需要下载的文件
        String fileName = "aa4da235-43ef-4687-a16c-1911ca0ab3ba.jpg";
        //用户信息
        String userCode = "anightmonarch";
        String userName = "一宿君";

        //请求地址
        String url = "http://localhost:6625/rest/file/test/get/download?fileName={fileName}&userCode={userCode}&userName={userName}";

        //提交参数设置
        Map<String,Object> paramMap = new HashMap<String,Object>();
        paramMap.put("fileName",fileName);
        paramMap.put("userCode",userCode);
        paramMap.put("userName",userName);

        //发起请求(响应内容为字节文件)
        ResponseEntity<byte[]> response = restTemplate.getForEntity(url, byte[].class, paramMap);
        System.out.println("HTTP 响应状态:" + response.getStatusCode());

        //下载文件保存路径
        File savePath = new File("D:\\Workspace\\download\\img\\");
        if (!savePath.isDirectory()){
            savePath.mkdirs();
        }
        Path path = Paths.get(savePath.getPath() + File.separator + fileName);
        Files.write(path, Objects.requireNonNull(response.getBody(),"下载文件失败!"));
    }
}

打印结果:

HTTP 响应状态:200 OK

Spring之RestTemplate常用API实践_第3张图片

3.6.3、大文件下载

上述文件下载的方式,是一次性将响应内容加载到客户端内存中,然后再从内存中将文件写入到磁盘中,这种方式适用于一些小文件的下载,比如:图片等。

但是在实际的工作场景中,如果下载一些比较大的文件,比如:excel、pdf、zip等文件,一次性将响应内容加载到内存中,会占用大量内存,严重影响系统的运行效率。

解决办法,设置请求头接收文件类型为MediaType.APPLICATION_OCTET_STREAM,表示可以将响应内容以流的形式加载到内存,同时保证接收到一部分内容,就向磁盘中写入一部分内容,而不是等全部加载一次性加载,这样整体效率运行起来就非常哇塞,流畅丝滑!!!

创建文件下载请求:和上述请求保持一致。

单元测试,下载大文件到指定文件目录:

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGetDownloadBigFile() throws IOException {
        //需要下载的文件
        String fileName = "aa4da235-43ef-4687-a16c-1911ca0ab3ba.jpg";
        //用户信息
        String userCode = "anightmonarch";
        String userName = "一宿君";

        //请求地址
        String url = "http://localhost:6625/rest/file/test/get/download?fileName={fileName}&userCode={userCode}&userName={userName}";

        //提交参数设置
        Map<String,Object> paramMap = new HashMap<String,Object>();
        paramMap.put("fileName",fileName);
        paramMap.put("userCode",userCode);
        paramMap.put("userName",userName);

        //定义请求头的接收类型
        RequestCallback requestCallback = clientHttpRequest
                -> clientHttpRequest.getHeaders()
           .setAccept(Lists.newArrayList(MediaType.APPLICATION_OCTET_STREAM,MediaType.ALL));

        //下载文件保存路径
        File savePath = new File("D:\\Workspace\\download\\img\\");
        if (!savePath.isDirectory()){
            savePath.mkdirs();
        }
        Path path = Paths.get(savePath.getPath() + File.separator + fileName);

        try {
            //对响应结果进行流式处理,而不是一次性将响应结果全部加载到内存
            restTemplate.execute(url, HttpMethod.GET, requestCallback,clientHttpResponse
                    -> {
                //每响应一次就写入磁盘一次,不在内存中逗留
                Files.copy(Objects.requireNonNull(clientHttpResponse.getBody(),"下载文件失败!"), path);
                return null;
            }, paramMap);
        }catch (Exception e){
            LOGGER.error("文件下载异常或已经存在!{}",e.getMessage());
            throw new Exception("文件下载异常或已经存在!");
        }
    }
}

如果指定目录下已经存在要下载的文件,会报异常!!!

3.7、excute执行方法

细心的小伙伴可能早就发现,前文我们用过的所有请求方法POST、GET、DELETE、PUT、EXCHANGE等方法,底层基本都调用了excute方法,那excute是干啥的,请看下文。

源码重载方法:

@Nullable
<T> T execute(String var1, HttpMethod var2, @Nullable RequestCallback var3, @Nullable ResponseExtractor<T> var4, Object... var5) throws RestClientException;

@Nullable
<T> T execute(String var1, HttpMethod var2, @Nullable RequestCallback var3, @Nullable ResponseExtractor<T> var4, Map<String, ?> var5) throws RestClientException;

@Nullable
<T> T execute(URI var1, HttpMethod var2, @Nullable RequestCallback var3, @Nullable ResponseExtractor<T> var4) throws RestClientException;

上述下载大文件的执行方法使用的是第二个重载方法:

@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException {
        URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
        return this.doExecute(expanded, method, requestCallback, responseExtractor);
}

到这会发现,excute方法将String格式的URI转成了java.net.URI,然后有调用了方法doExecute,这又是干啥的,再往里扒拉:

@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    	Assert.notNull(url, "URI is required");
        Assert.notNull(method, "HttpMethod is required");
        ClientHttpResponse response = null;

        Object var14;
        try {
            //1.生成请求
            ClientHttpRequest request = this.createRequest(url, method);
            if (requestCallback != null) {
                //2.设置header
                requestCallback.doWithRequest(request);
            }
			//3.执行请求
            response = request.execute();
            //4.处理响应
            this.handleResponse(url, method, response);
            //5.返回执行结果
            var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
        } catch (IOException var12) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
            throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
        } finally {
            if (response != null) {
                response.close();
            }

        }

        return var14;
}

此方法并没有对外暴露,只能通过继承调用。可以看到我们每次请求的时候内部会发生这几个步骤:

  1. 生成请求
  2. 设置header
  3. 执行请求
  4. 处理响应
  5. 返回执行结果

此处我们需要注意两个关键接口:RequestCallbackResponseExtractor

3.7.1、RequestCallback接口

RequestCallback官方指导文档。

RequestCallback用于操作ClientHttpRequest的代码的回调接口。允许操作请求头,并写入请求体。

在RestTemplate内部使用,但对应用程序代码也很有用。有以下两个实现类:

  • AcceptHeaderRequestCallback:只处理请求头回调,用于restTemplate.getXXX()方法;
  • HttpEntityRequestCallback:继承AcceptHeaderRequestCallback,可以处理请求头body,用于restTemplate.putXXX()、restTemplate.postXXX()和restTemplate.exchange()等方法。

3.7.2、ResponseExtractor接口

响应提取器,主要就是用来从Response中提取数据。RestTemplate请求完成后,都是通过它来从ClientHttpResponse提取出指定内容,比如:请求头、请求body等。

ResponseExtractor有三个实现类:

  • HeadersExtractor:用于提取请求头
  • HttpMessageConverterExtractor:用于提取响应body体内容
  • ResponseEntityResponseExtractor:内部借助HttpMessageConverterExtractor提取body体(委托模式),然后将body和响应头、状态封装成ResponseEntity对象返回调用者。

3.7.3、RestTemplate执行流程

Spring之RestTemplate常用API实践_第4张图片

最后,上述方法总结的比较浅显,仅限于熟练调用,底层实现原理还需进一步深究!

你可能感兴趣的:(Java编程技术,spring,okhttp,HttpClient,JDK,HttpClient,RestTemplate)