一,概述
RestTemplate 是 Spring Framework 中用于简化 HTTP 请求的同步客户端工具类,它封装了底层 HTTP 客户端(如 JDK HttpURLConnection、Apache HttpClient 等),提供了更简洁的 API 用于发送 RESTful 请求并处理响应。
核心功能
-
支持所有 HTTP 方法
GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS等均有对应方法(如getForObject(),postForEntity())。 -
自动序列化/反序列化 通过
HttpMessageConverter自动将请求体/响应体与 Java 对象相互转换(如 JSON ↔ POJO)。 -
URI 模板处理 支持路径变量和查询参数动态填充,例如:
restTemplate.getForObject("http://api.com/users/{id}", User.class, 123); -
异常处理 默认对 HTTP 4xx/5xx 状态码抛出
HttpClientErrorException或HttpServerErrorException,需自行捕获处理。 -
拦截器与定制 支持添加
ClientHttpRequestInterceptor实现日志、认证等统一逻辑。
二,快速入门
-
导入依赖(springboot-web)自带的就有RestTemplate
-
编写配置类
-
这种初始化方法,是使用了
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); }
-
-
发起一个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():返回的是ResponseEntityResponseEntity是对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请求方法上大同小异,RestTemplate的POST请求也包含两个主要方法:
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.GET,HttpMethod.POST)。 - requestEntity: 封装了请求头和请求体的实体。可以使用
HttpEntity或其子类RequestEntity。 - responseType: 期望的响应类型。可以是
String.class,JsonNode.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
-
RequestEntity是HttpEntity的子类,提供了更方便的方法来构建请求实体。下面是使用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接口实现,具体做法如下
-
定义一个
LogFilter拦截所有接口请求,在MDC中设置traceIdpublic 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中的参数,也是该线程独享的,所以不会有线程安全问题
五,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;
}