RestTemplate使用手册

传统服务端请求HTTP服务,一般使用Jdk自带HttpURLConnection 或者Apache工具类HttpClient,请求过于繁琐与复杂,还需要关注资源回收。

因此,Spring 提供RestTemplate 模板类简化操作。RestTemplate 属于HTTP请求同步阻塞式工具类,底层基于HTTP客户端库(例如JDK HttpURLConnection 、Apache HttpComponents 或者okHttp等)封装更加简单易用模板方法API,方便程序员利用模板方法发起网络请求和处理,很大程度上能够提升开发效率。

01 使用案例

1.1 非Spring项目

java 复制代码
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>
java 复制代码
public class RestTemplateMain {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        String url = "http://localhost:8080/youxinren/user";
        String reponse = restTemplate.getForObject(url, String.class);
        System.out.println(reponse);
    }
}

1.2 Spring项目

gradle 复制代码
implementation platform('org.springframework.boot:spring-boot-dependencies:2.3.10.RELEASE')
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web'
java 复制代码
@Configuration
public class RestTemplateConfig {
    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }
}

注意,RestTemplate 默认使用JDK自带HttpURLConnection 作为底层客户端实现,不过也支持切换至高性能OkHttp 或者Apache的HttpClient 客户端。从性能方面对比,OkHttp 工具类性能明显高于HttpClient ,然而HttpClient 又高于HttpURLConnection

java 复制代码
@Configuration
public class RestTemplateConfig {
    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }

    // 使用HttpClient作为底层客户端
    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);
    }
}
java 复制代码
@Configuration
public class RestTemplateConfig {
    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }

    // 使用OkHttpClient作为底层客户端
    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.3 拦截器

使用RestTemplate调用远程服务,可以使用拦截器添加请求头传递信息,比如添加TraceId便于日志串联完整请求链路,提升问题定位速读。

第一步,定义LogFilter拦截所有接口请求,使用MDC底层ThreadLocal变量保存请求TraceId。

java 复制代码
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 {
        if (StringUtils.isBlank(MDC.get("TRACE_ID"))) {
            MDC.put(""TRACE_ID"", UUID.randomUUID().toString());
        }
        chain.doFilter(request, response);
    }
}

第二步,实现ClientHttpRequestInterceptor接口,通过MDC获取当前请求TraceId,然后设置到请求头。

java 复制代码
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"));
        return execution.execute(request, body);
    }
}

第三步,配置RestTemplate自定义RestTemplateInterceptor实现。

java 复制代码
@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();
    }
}

1.4 扩展请求类型

SpringBoot使用RestTemplate远程调用接口,不支持Content-Type=text/html;charset=UTF-8类型,导致出现no suitable HttpMessageConverter异常。

java 复制代码
@Configuration
public class RestTemplateConfiguration {
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML, MediaType.TEXT_PLAIN));
        restTemplate.getMessageConverters().add(converter);

        return restTemplate;
    }
}

02 RestTemplate API

RestTemplate 包装各种网络请求方式(GETPOSTPUTDELETE和文件上传与下载),极大简化开发工作量。

2.1 GET

RestTemplate提供两种方式发送GET请求,getForObject仅返回响应体,getForEntity返回ResponseEntity封装响应体、请求头信息。

java 复制代码
public class TestRestTemplate {
    @Autowired
    private RestTemplate restTemplate;

    // 不带参get请求
    @Test
    public void testGetNoArgument(){
        String url = "http://host:8080/getNoArgument";
        // 发起请求, 直接返回对象
        ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class);
        System.out.println(responseBean);
    }

    // 使用占位符号传参
    @Test
    public void testGetWithArgument(){
        // 使用占位符号传参
        String url = "http://host:8080/getWithArgument/{1}";

        // 发起请求, 直接返回对象(Restful风格)
        ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001");
        System.out.println(responseBean);
    }

    // 带参请求
    @Test
    public void testGetForObject1(){
        String url = "http://host:8080/getForObject?userName={userName}&password={password}";

        // 请求参数
        Map<String, String> uriVariables = new HashMap<>();
        uriVariables.put("userName", "feiyu");
        uriVariables.put("password", "123456");

        // 发起请求, 直接返回对象(带参数请求)
        ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, uriVariables);
        System.out.println(responseBean);
    }
    
    @Test
    public void testGetForObject2() {
        // URL特殊符号进行转义查询, 如{、}
        String param = "{"lat":" + latitude + ","lon":" + longitude + ","ver": 1}";
        URI requestUrl = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("param", param)
                .build().encode().toUri();
        JSONObject result = restTemplate.getForObject(requestUrl, JSONObject.class);
    }
}

RestTemplate工具类还提供exchange通用请求方法,用于封装GET、POST、DELETE、PUT、OPTIONS、PATCH等方法请求。

java 复制代码
public class TestRestTemplate {
    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testGetForEntity(){
        String url = "http://localhost:8080/getForEntity";

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

        // 获取响应体
        System.out.println("响应体=" + response.getBody().toString());

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

        System.out.println("响应状态=" + statusCode);
        System.out.println("响应状态码=" + statusCodeValue);
        System.out.println("响应头信息=" + headers);
    }

    @Test
    public void testExchange() {
        String url = "http://localhost:8080/exchange";

        // 请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("auth", "xuhx");

        // 封装请求头
        HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(headers);
        ResponseEntity<Map<?, ?>> exchange = restTemplate.exchange(url, HttpMethod.GET, formEntity, Map.class);
    }
}

2.2 POST

POST请求用于向服务器提交数据,属于非幂等操作。与GET请求不同,POST封装请求数据到请求体,而不是URL,适用于传输大量数据和敏感信息。

java 复制代码
class TestRestTemplate {
    @Autowired
    private RestTemplate restTemplate;

    // 模拟表单提交
    @Test
    public void testPostByForm() {
        String url = "http://host:8080/testPostByForm";

        // 请求头: Content-Type=x-www-form-urlencoded
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        // 提交参数设置
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("userName", "feiyu");
        map.add("password", "123456");

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

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

    // 模拟JSON提交
    @Test
    public void testPostByJson() {
        String url = "http://host:8080/testPostByJson";

        // 入参
        RequestBean request = new RequestBean();
        request.setUserName("feiyu");
        request.setPassword("123456789");

        ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);
        System.out.println(responseBean.toString());
    }

    // 模拟重定向
    @Test
    public void testPostByLocation(){
        String url = "http://host:8080/testPostByLocation";

        // 入参
        RequestBean request = new RequestBean();
        request.setUserName("feiyu");
        request.setPaaword("123456789");

        // 接收服务器重定向URL, 服务器返回值=redirect:index.html
        // 打印: http://host:8080/index.html
        URI uri = restTemplate.postForLocation(url, request);
        System.out.println(uri.toString());
    }
}

2.3 PUT

HTTP PUT请求用于创建新资源或者替换目标资源。其次,PUT请求属于幂等操作,意味着无论调用多次效果都相同,不会产生副作用。

java 复制代码
public class TestRestTemplate {
    @Autowired
    private RestTemplate restTemplate;
    
    // 模拟PUT请求JSON提交
    @Test
    public void testPutByJson(){
        String url = "http://host:8080/testPutByJson";
        
        // 入参
        RequestBean request = new RequestBean();
        request.setUserName("feiyu");
        request.setPassword("123456789");

        // 模拟PUT请求JSON提交, 没有返回值
        restTemplate.put(url, request);
    }
}

2.4 DELETE

HTTP DELETE请求用于删除指定资源,广泛应用于数据库或者文件系统交互操作,例如删除用户、删除文件信息。

java 复制代码
class TestRestTemplate {
    @Autowired
    private RestTemplate restTemplate;
    
    // 模拟JSON提交, DELETE请求
    @Test
    public void testDeleteByJson(){
        String url = "http://localhost:8080/testDeleteByJson";

        // 提交DELETE请求, 没有返回值
        restTemplate.delete(url);
    }
}

2.5 文件上传与下载

大文件下载需要设置请求头ContentType=APPLICATION_OCTET_STREAM,表示以流的形式进行数据加载。RequestCallback结合File.copy保证接收到一部分文件内容,就向磁盘写入一部分内容,而不是全部加载到内存,最后写入磁盘文件。

java 复制代码
public class TestRestTemplate {
    @Autowired
    private RestTemplate restTemplate;

    // POST请求模拟文件上传
    @Test
    public void testUpload(){
        String url = "http://host:8080/upload";

        // 请求头: ContentType=multipart/form-data
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        // 提交参数设置
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
        paramMap.add("file", new FileSystemResource(new File("image1.jpg")));
        paramMap.add("file", new FileSystemResource(new File("image2.jpg")));
        // 服务端接受额外参数, 可以传递
        paramMap.add("userName", "feiyu");

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

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

    // POST请求模拟文件下载
    @Test
    public void downloadFile() throws IOException {
        String url = "http://host:8080/downloadFile/{1}/{2}";

        // 发起请求, 直接返回对象(restful风格)
        ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class, userName,fileName);
        System.out.println("文件下载请求结果状态码 = " + rsp.getStatusCode());

        // 下载文件内容保存到本地
        Files.write(Paths.get("image.jpg"), Objects.requireNonNull(rsp.getBody(), "未获取到下载文件"));
    }

    // 大文件下载
    @Test
    public void downloadBigFile() throws IOException {
        String url = "http://host:8080/downloadFile/{1}";

        // 定义请求头接收类型
        RequestCallback requestCallback = request -> request.getHeaders()
                .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

        // 响应进行流式处理而不是全部加载到内存
        String targetPath = "data.csv";
        restTemplate.execute(url, HttpMethod.GET, requestCallback, response -> {
            Files.copy(response.getBody(), Paths.get(targetPath));
            return null;
        }, userName);
    }
}

03 总结

RestTemplate通过高度封装请求API、深度整合Spring生态以及灵活扩展机制,显著降低HTTP通信开发复杂度。尽管Spring5推荐使用WebClient作为响应式编程替代方案,但是RestTemplate在传统同步场景仍具有广泛应用价值。

相关推荐
Code季风5 小时前
深入理解微服务中的服务注册与发现(Consul)
java·运维·微服务·zookeeper·架构·go·consul
光军oi5 小时前
java微服务(Springboot篇)——————IDEA搭建第一个Springboot入门项目
java·spring boot·微服务
guojl6 小时前
RestTemplate原理分析
spring cloud·微服务
Ken_11156 小时前
SpringCloud系列(51)--SpringCloud Stream之使用分组解决消息重复消费问题
spring cloud
LCG元7 小时前
云原生微服务间的异步消息通信:最终一致性与系统容错的架构实战
微服务·云原生·架构
lwb_01187 小时前
SpringCloud——Gateway新一代网关
spring·spring cloud·gateway
vim怎么退出9 小时前
万字长文带你了解微前端架构
前端·微服务·前端框架
述雾学java11 小时前
Spring Cloud Feign 整合 Sentinel 实现服务降级与熔断保护
java·spring cloud·sentinel
何苏三月14 小时前
SpringCloud系列 - Sentinel 服务保护(四)
spring·spring cloud·sentinel