RestTemplate

848 阅读10分钟

一,概述

RestTemplate 是 Spring Framework 中用于简化 HTTP 请求的同步客户端工具类,它封装了底层 HTTP 客户端(如 JDK HttpURLConnection、Apache HttpClient 等),提供了更简洁的 API 用于发送 RESTful 请求并处理响应。

核心功能

  1. 支持所有 HTTP 方法 GETPOSTPUTDELETEPATCHHEADOPTIONS 等均有对应方法(如 getForObject(), postForEntity())。

  2. 自动序列化/反序列化 通过 HttpMessageConverter 自动将请求体/响应体与 Java 对象相互转换(如 JSON ↔ POJO)。

  3. URI 模板处理 支持路径变量和查询参数动态填充,例如:

    restTemplate.getForObject("http://api.com/users/{id}", User.class, 123);
    
  4. 异常处理 默认对 HTTP 4xx/5xx 状态码抛出 HttpClientErrorExceptionHttpServerErrorException,需自行捕获处理。

  5. 拦截器与定制 支持添加 ClientHttpRequestInterceptor 实现日志、认证等统一逻辑。

二,快速入门

  1. 导入依赖(springboot-web)自带的就有RestTemplate

  2. 编写配置类

    • 这种初始化方法,是使用了JDK自带的HttpURLConnection作为底层HTTP客户端实现。

      @Configuration
      public class RestTemplateConfig {
          /**
           * 没有实例化RestTemplate时,初始化RestTemplate
           * @return
           */
          @ConditionalOnMissingBean(RestTemplate.class)
          @Bean
          public RestTemplate restTemplate(){
              RestTemplate restTemplate = new RestTemplate();
              return restTemplate;
          }
      }
      
    • 还可以修改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);
          }
      
      }
      
    • 将底层的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);
      }
      
  3. 发起一个get请求

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

三,发起请求

3.1 Get请求

通过RestTemplate发送HTTP GET协议请求,经常使用到的方法有两个:

  • getForObject():返回值是HTTP协议的响应体

  • getForEntity():返回的是ResponseEntity

    ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息

3.1.1 不带参请求

  • 不带参的get请求

    @RestController
    public class TestController {
        /**
         * 不带参的get请求
         * @return
         */
        @GetMapping("/testGet")
        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());
    }
    

3.1.2 带参get请求(传路径参数)

  • 带参get请求(传路径参数)

    /**
     * 带参的get请求传路径参数
     * @return
     */
    @GetMapping("/testGetByRestFul/{id}/{name}")
    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}";
    
        //发起请求,直接返回对象(传路径参数)
        ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001", "张三");
        System.out.println(responseBean.toString());
    }
    

3.1.3 带参get请求(restful风格)

  • 带参get请求(restful风格)

        /**
         * 带参的get请求(restful风格)
         * @return
         */
        @GetMapping("/testGetByParam")
        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());
    }
    

3.2 getForEntity使用示例

上面的所有的getForObject请求传参方法,getForEntity都可以使用,使用方法上也几乎是一致的,只是在返回结果接收的时候略有差别

使用ResponseEntity<T> responseEntity来接收响应结果。用responseEntity.getBody()获取响应体。

@Autowired
private RestTemplate restTemplate; 
/**
 * 单元测试
 */
@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);
}

3.2.1 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);

3.2.2 特殊符号处理

如果要查询的url中有特殊符号比如{},就需要转义处理下,比如根据经纬度查询地理信息接口:

  • url:

    http://api.tianditu.gov.cn/geocoder?type=geocode&tk=xxxx&postStr={'lat':-32,'lon':116.37304,'ver':1}
    
     String postStrJson = "{\"lat\":" + latitude + ",\"lon\":" + longitude + ",\"ver\":1}";
    URI requestUrl = UriComponentsBuilder.fromHttpUrl(locationUrl)
             .queryParam("postStr", postStrJson)
             .build().encode().toUri();
    JSONObject result = restTemplate.getForObject(requestUrl, JSONObject.class);
    
  • 转义后url:

    http://api.tianditu.gov.cn/geocoder?type=geocode&tk=xxxx&postStr=%7B%22lat%22:32.123,%22lon%22:116,%22ver%22:1%7D
    
    • %7B对应{
    • %7D对应}
    • %22对应双引号"

3.3 Post请求

其实POST请求方法和GET请求方法上大同小异,RestTemplatePOST请求也包含两个主要方法:

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

3.3.1 传递单个参数

  • 接口

    @RestController
    public class TestController {
    
        /**
         * 模拟表单请求,post方法测试
         * @return
         */
        @PostMapping("/testPostByForm")
        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());
    }
    

3.3.2 传递对象

  • 接口

    @RestController
    public class TestController {
        /**
         * 模拟表单请求,post方法测试
         * @param request
         * @return
         */
        @PostMapping(value = "testPostByFormAndObj")
        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());
    }
    

3.3.3 传递json数据

  • 接口

    @RestController
    public class TestController {
        /**
         * 模拟JSON请求,post方法测试
         * @param request
         * @return
         */
        @PostMapping("/testPostByJson")
        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());
    }
    

3.3.4 模拟页面重定向

  • 接口

    @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
    

3.4 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);
    }
    

3.5 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);
    }
    

3.6 通用请求方法

RestTemplate工具类里面,还有一个exchange通用协议请求方法,它可以发送GET、POST、DELETE、PUT、OPTIONS、PATCH等等HTTP方法请求

  • exchange方法签名

    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException
    
  • 参数详解

    • url: 请求的目标 URL。
    • method: HTTP 方法(如 HttpMethod.GETHttpMethod.POST)。
    • requestEntity: 封装了请求头和请求体的实体。可以使用 HttpEntity 或其子类 RequestEntity
    • responseType: 期望的响应类型。可以是 String.classJsonNode.class 等。
    • uriVariables: URL 中的变量(可选)。在 URL 中可以使用占位符,后面传递对应的值。

代码示例:使用 RestTemplate.exchange 方法发送一个 POST 请求,并接收 JSON 响应:

public class RestTemplateExample {
    public static void main(String[] args) throws Exception {
        // 创建 RestTemplate 实例
        RestTemplate restTemplate = new RestTemplate();

        // 创建请求 URL
        String url = "http://localhost/testPost";

        // 设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");

        // 设置请求体
        String requestBody = "{\"name\":\"John\",\"age\":30}";

        // 创建 HttpEntity 封装请求头和请求体
        HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);

        // 发送 POST 请求
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);

        // 打印响应状态码
        System.out.println("Status Code: " + responseEntity.getStatusCode());

        // 打印响应头
        System.out.println("Response Headers: " + responseEntity.getHeaders());

        // 打印响应体
        System.out.println("Response Body: " + responseEntity.getBody());

        // 将响应体解析为 JSON
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody());
        System.out.println("Parsed JSON Response: " + jsonResponse);
    }
}


使用 RequestEntity

  • RequestEntityHttpEntity 的子类,提供了更方便的方法来构建请求实体。下面是使用 RequestEntity 的示例:

    public class RestTemplateExample {
        public static void main(String[] args) throws Exception {
            // 创建 RestTemplate 实例
            RestTemplate restTemplate = new RestTemplate();
    
            // 创建请求 URL
            String url = "http://localhost/testPost";
    
            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json");
    
            // 设置请求体
            String requestBody = "{\"name\":\"John\",\"age\":30}";
    
            // 创建 RequestEntity 封装请求头和请求体
            RequestEntity<String> requestEntity = RequestEntity
                    .post(new URI(url))
                    .headers(headers)
                    .body(requestBody);
    
            // 发送 POST 请求
            ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
    
            // 打印响应状态码
            System.out.println("Status Code: " + responseEntity.getStatusCode());
    
            // 打印响应头
            System.out.println("Response Headers: " + responseEntity.getHeaders());
    
            // 打印响应体
            System.out.println("Response Body: " + responseEntity.getBody());
        }
    }
    
    

四,Spring环境下增加线程号

使用RestTemplate调用远程接口时,有时需要在header中传递信息,比如:traceId,source等,便于在查询日志时能够串联一次完整的请求链路,快速定位问题。这种业务场景就能通过ClientHttpRequestInterceptor接口实现,具体做法如下

  1. 定义一个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() {
        }
    }
    
  2. 实现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;
        }
    }
    
  3. 定义配置类,配置上面定义的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中的参数,也是该线程独享的,所以不会有线程安全问题

五,no suitable HttpMessageConverter异常

Springboot项目, 使用 spring 自家封装的 RestTemplate 来远程调用接口时,由于RestTemplate请求不支持content type [text/html;charset=UTF-8]类型

解决办法Springboot注入RestTemplate类,追踪RestTemplate 实例化过程发现默认的RestTemplate 只支持application/json格式,所以需要手动补充text/html格式

	@Bean("restTemplate")
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                MediaType.TEXT_HTML,
                MediaType.TEXT_PLAIN));
        restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);

        return restTemplate;
    }