详解spring cloud的RestTemplate
RestTemplate 详解RestTemplate是Spring 提供的同步 HTTP 客户端Spring Cloud 早期用来做微服务之间的服务调用发送 HTTP 请求Get/Post/Put/Delete。⚠️注意现在微服务开发优先用OpenFeignRestTemplate 是底层原生工具很多底层、第三方接口调用仍然会用Spring6 之后官方推荐WebClient响应式。RestTemplate ≠ RPC走的就是标准 HTTPJSON本质封装 HttpClient默认 JDK HttpURLConnection也可替换 OkHttp、Apache HttpClient。1. RestTemplate 在 SpringCloud 中的两种用法微服务场景有两种使用模式普通 RestTemplate写死 IP 地址调用不经过注册中心不做负载均衡。LoadBalanced 注解修饰的 RestTemplate交给 Spring Cloud LoadBalancer从 Nacos/Eureka 注册中心拿服务实例实现客户端负载均衡。① 普通 RestTemplate无负载均衡Configuration public class RestConfig { Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }调用直接写完整 url// user-service 服务写死ip端口 String url http://127.0.0.1:8082/user/get/1; String resp restTemplate.getForObject(url, String.class);缺点硬编码 IP无法利用注册中心不能负载均衡微服务几乎不用。② LoadBalanced 负载均衡版SpringCloud 核心用法Configuration public class RestConfig { Bean LoadBalanced //关键注解开启客户端负载均衡 public RestTemplate loadBalancedRestTemplate(){ return new RestTemplate(); } }加上LoadBalanced之后RestTemplate 会被增强拦截请求的时候写服务名不要写 IP 端口。// user-service 是Nacos注册的服务名称 String url http://user-service/user/get/1; User user restTemplate.getForObject(url, User.class);底层原理拦截 RestTemplate 的 http 请求解析 url 中的主机名user‑service向注册中心 (Nacos) 拉取该服务全部实例列表LoadBalancer 负载均衡算法轮询 / 随机选出一个实例 ipport替换 url真正发起 http 调用。SpringCloud2020 之后移除 Ribbon默认使用Spring‑Cloud‑LoadBalancer作为负载均衡组件替代 Ribbon。2. RestTemplate 常用 API5 大类方法方法组说明GETgetForObject()GET 请求直接拿到响应对象拿不到响应头、状态码GETgetForEntity()GET返回 ResponseEntityT可以获取状态码、响应头、bodyPOSTpostForObject()POST 提交获取返回对象POSTpostForEntity()POST 提交返回 ResponseEntity通用万能exchange()万能方法支持 GET/POST/PUT/DELETE自定义请求头、请求体PUT/DELETEput() / delete()put、delete 操作总结getForObject/postForObject只拿响应体丢失 http 状态码、响应头getForEntity/postForEntity封装为ResponseEntity可以拿到 status、headers、bodyexchange()复杂请求首选可以自己设置 header、请求方式适用 PUT、DELETE 等RestTemplate 搭配LoadBalanced注解才支持写服务名调用LoadBalancer。不加就是普通 RestTemplate会把user‑service当成域名去 DNS 解析解析失败报错。/ 加上LoadBalancedSpring Cloud 会给这个 RestTemplate 自动装配一个拦截器LoadBalancerInterceptor。示例 1 getForObject// 返回实体 User user restTemplate.getForObject(http://user-service/user/{id}, User.class, 1L);示例 2 getForEntity拿状态码、响应头ResponseEntityUser entity restTemplate.getForEntity(http://user-service/user/{id}, User.class,1L); // 状态码 HttpStatusCode statusCode entity.getStatusCode(); // body User body entity.getBody(); //响应头 HttpHeaders headers entity.getHeaders();示例 3 postForObject JSON 提交UserDTO dto new UserDTO(); dto.setName(zhangsan); // post json User resp restTemplate.postForObject(http://user-service/user/add, dto, User.class);示例 4 exchange 万能方法自定义请求头token微服务经常要传递请求头tokentraceIdgetForObject/postForObject 不方便设置 header用 exchangeHttpHeaders headers new HttpHeaders(); headers.set(token,abc123456); headers.setContentType(MediaType.APPLICATION_JSON); UserDTO body new UserDTO(); HttpEntityUserDTO httpEntity new HttpEntity(body, headers); ResponseEntityUser res restTemplate.exchange( http://user-service/user/add, HttpMethod.POST, httpEntity, User.class ); User result res.getBody();3. LoadBalanced 原理源码简述面试LoadBalanced是一个标记注解 Spring Cloud 自动装配LoadBalancerInterceptor拦截器加到 RestTemplate 拦截器链。执行流程restTemplate 发起 http 请求进入LoadBalancerInterceptor拦截解析 URI 的 host当做服务名称LoadBalancerClient调用注册中心获取实例列表负载均衡挑选实例替换 URI 为真实ip:port继续执行 http 请求。坑同一个配置类不要同时存在普通 RestTemplate 和 LoadBalanced RestTemplate。如果要两个需要分开两个 Bean区分名字。4. RestTemplate 常见坑点坑 1LoadBalanced RestTemplate 不能直接访问外部 http 地址比如调用百度、第三方接口url 写http://www.baidu.com会报错。原因拦截器把域名当成服务名去 Nacos 找叫www.baidu.com的服务找不到报错。✅解决方案两套 RestTemplate一套加LoadBalanced用于内部微服务调用一套不加专门调用第三方外部接口。坑 2默认底层是 JDK HttpURLConnection性能差无连接池默认没有 http 连接池每次请求新建连接高并发性能差。 生产环境替换底层实现切换 Apache HttpClient / OkHttp开启连接池。pom 引入 Apache httpclientdependency groupIdorg.apache.httpcomponents/groupId artifactIdhttpclient/artifactId /dependency配置 RestTemplate 使用 HttpClientBean LoadBalanced public RestTemplate restTemplate(){ //使用HttpClient连接池 HttpClient httpClient HttpClients.createDefault(); HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(factory); }坑 3超时时间没有设置接口卡死线程必须设置连接超时、读取超时HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(3000); //连接超时3秒 factory.setReadTimeout(5000); //读取超时5秒 RestTemplate restTemplate new RestTemplate(factory);坑 4微服务调用请求头丢失token、traceIdRestTemplate 默认不会把当前服务的 header 向下游传递。OpenFeign 有拦截器可以传递 headerRestTemplate 自己要手动拿 request塞到 HttpHeaders 里面。坑 5序列化问题泛型、时间转换异常RestTemplate 底层使用MappingJackson2HttpMessageConverter 遇到日期、复杂泛型需要自定义消息转换器。坑 6UnknownHostExceptionRestTemplate 搭配LoadBalanced注解 才支持写服务名例如http://user-service否则直接报 UnknownHostException。原理LoadBalanced是 Spring Cloud 的标记注解。Bean LoadBalanced // ✅关键注解 public RestTemplate restTemplate() { return new RestTemplate(); }不加就是普通 RestTemplate会把user‑service当成域名去 DNS 解析解析失败报错。加上LoadBalancedSpring Cloud 会给这个 RestTemplate 自动装配一个拦截器LoadBalancerInterceptor。拦截器执行流程代码执行restTemplate.getForObject(http://user‑service/xxx, String.class)请求被LoadBalancerInterceptor拦截。提取服务名user‑service交给Spring Cloud LoadBalancer。LoadBalancer 从 Nacos 获取该服务实例列表本地算法选出一个实例192.168.x.x:8081。将 URI 替换成真实地址http://192.168.x.x:8081/xxx。真实 HTTP 请求发出。⚠️注意只有被LoadBalanced修饰的 RestTemplate Bean 才有这个拦截器。如果你 new RestTemplate ()没有交给 Spring 管理就算类上写注解也无效。Feign/WebClient 的LoadBalanced作用逻辑类似。依赖必须引入spring‑cloud‑starter‑loadbalancer新版 Spring Cloud 不再自带。Q能不能同时存在两个 RestTemplate Bean一个带 LoadBalanced一个不带可以。带注解用于微服务内部调用写服务名http://user‑service不带注解用于调用外部第三方 http 接口真实域名如https://baidu.com示例// 负载均衡版调用微服务 Bean(lbRestTemplate) LoadBalanced public RestTemplate lbRestTemplate(){ return new RestTemplate(); } // 普通版调用外部第三方接口 Bean(normalRestTemplate) public RestTemplate normalRestTemplate(){ return new RestTemplate(); }一句话总结LoadBalanced不会修改 RestTemplate 源码只是给 Bean 注入拦截器拦截请求借助 LoadBalancer 把服务名解析成真实 IP 端口。5. RestTemplate VS OpenFeign面试高频表格对比RestTemplateOpenFeign编码方式硬编码调用 api手写 url声明式接口写接口 注解像调用本地方法负载均衡需要加LoadBalanced注解内置集成 LoadBalancer自动负载均衡请求头传递需要手动组装 HttpEntity自定义拦截器统一处理代码量代码量大url 字符串容易写错简洁面向接口底层HTTP 客户端自己写 http 调用封装 RestTemplate实际开发业务微服务调用优先 OpenFeign调用第三方外部 http 接口用 RestTemplate。6. RestTemplate 和 WebClientRestTemplate同步阻塞每个请求占用一个线程高并发场景线程池容易耗尽。WebClientSpring5 推出非阻塞响应式 HTTP 客户端SpringBoot3 中 RestTemplate 已经标记过时官方推荐 WebClient。面试题汇总LoadBalanced做了什么给 RestTemplate 增加拦截器 LoadBalancerInterceptor拦截 http 请求解析服务名从注册中心拿实例做客户端负载均衡。RestTemplate 为什么调用外部域名报错LoadBalancer 拦截器把域名当成微服务服务名去注册中心查找找不到报错分开两个 RestTemplate Bean。RestTemplate 默认底层什么生产要做哪些优化默认 JDK HttpURLConnection无连接池生产替换 HttpClient/OkHttp配置连接池设置超时时间。RestTemplate getForEntity 和 getForObject 区别getForObject 只拿到响应体getForEntity 返回 ResponseEntity可以拿到状态码、响应头。微服务调用 请求头丢失问题token、traceId现象网关 / 服务 A 带 token/traceId调用下游微服务 B 时下游拿不到这个 header。根源RestTemplate、OpenFeign 默认不会自动把当前入站请求的 Header 往下游调用透传。1、RestTemplate 场景RestTemplate 本身只是发起一个全新 HTTP 请求不会自动拷贝当前 Web 请求的 request header。Controller 收到前端请求里面有 token、traceId当用 RestTemplate 去调用别的微服务是新建 Http 请求原有 header 不会复制过去。解决方案两种思路每次调用手动从HttpServletRequest获取 header设置到 RestTemplate 的HttpHeaders。 缺点每个调用地方都写一遍重复代码。自定义 RestTemplate 拦截器ClientHttpRequestInterceptor全局透传✅推荐注意使用 ThreadLocal 存放需要传递的 headertoken、traceId拦截器读取 ThreadLocal 设置到请求头。 ⚠️坑如果使用异步线程ThreadLocal 会失效要用TransmittableThreadLocal。伪代码示意// 1.拦截器给RestTemplate增加拦截 public class HeaderCopyInterceptor implements ClientHttpRequestInterceptor { Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 从ThreadLocal取出token、traceId String token ContextHolder.getToken(); String traceId ContextHolder.getTraceId(); HttpHeaders headers request.getHeaders(); if(token ! null){ headers.set(token, token); } if(traceId ! null){ headers.set(traceId, traceId); } return execution.execute(request, body); } } // 注册给 LoadBalanced 的 RestTemplate Bean LoadBalanced public RestTemplate restTemplate(){ RestTemplate rt new RestTemplate(); rt.setInterceptors(Collections.singletonList(new HeaderCopyInterceptor())); return rt; }不能直接在拦截器拿HttpServletRequestRestTemplate 拦截器不一定在 web 线程拿不到 request 对象所以要用 ThreadLocal 做上下文存储。在 Controller 层把 HttpServletRequest 里面的 token/traceId 存入 ContextHolder。2、OpenFeign 场景Feign 也不会自动透传 header但是 Feign 提供RequestInterceptor拦截器。Component public class FeignHeaderInterceptor implements RequestInterceptor { Override public void apply(RequestTemplate template) { // 从ThreadLocal拿上下文 String token ContextHolder.getToken(); if(token ! null){ template.header(token, token); } } }只要注入这个RequestInterceptor所有 Feign 调用都会自动带上 header。3、常见坑点面试高频❌直接在 RestTemplate 拦截器获取HttpServletRequest大概率 null。 RestTemplate 调用可以不在 web 线程所以不要直接拿 request使用 ThreadLocal。❌使用普通 ThreadLocal异步 Async 场景子线程拿不到父线程上下文traceId 丢失。 解决方案使用阿里TransmittableThreadLocal。Gateway 网关层转发丢失 headerGateway 用 GlobalFilter 过滤器处理。Seata、Sentinel、链路追踪框架SkyWalking内部已经做了 traceId 透传自己不用处理 traceId但业务 token 需要自己处理。对比总结调用方式透传 header 方案RestTemplate实现ClientHttpRequestInterceptor配合 ThreadLocalOpenFeign实现RequestInterceptor一句话面试口述 RestTemplate/Feign 发起的是全新 http 请求默认不会把上游请求的 header 往下游传递。RestTemplate 需要注册 ClientHttpRequestInterceptorFeign 使用 RequestInterceptor结合 ThreadLocal 存放 token、traceId 实现透传异步场景要注意 ThreadLocal 传递问题。响应头 Response HeadersHTTP 报文分为两大部分响应体 Body真正业务数据比如{id:1,name:张三}就是你要的 User 对象响应头 Headers附加元数据不是业务数据是服务端告诉客户端的附加信息一堆 key‑valuegetForObject()只帮你拿【响应体 body】直接转成对象直接把状态码、响应头全部丢弃。getForEntity()返回ResponseEntity可以同时拿到状态码、响应头、响应体。举个真实 HTTP 返回样例HTTP/1.1 200 OK Content-Type: application/json Date: Wed,20 Aug 2026 10:20:00 GMT Server: tomcat token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 Content-Length: 120 {id:1,name:张三}上面这些Content-Type、Date、token、Content‑Length全部都是响应头 最后那一段 JSON 才是响应 body。常见响应头以及作用响应头 key作用Content‑Type告诉客户端返回的数据是什么格式application/json/text/htmlContent‑Length响应体字节长度Date服务器时间Set‑Cookie服务端返回 cookie 给浏览器保存token/Authorization业务自定义头返回令牌微服务之间鉴权用traceId链路追踪 IDSkyWalking 输出排查日志链路用Location重定向地址302 跳转的时候返回响应头分两类HTTP 标准头自定义响应头业务自己定义比如返回 token、traceId。代码实操 getForEntity 拿响应头ResponseEntityUser entity restTemplate.getForEntity(http://user-service/user/{id}, User.class,1L); //1.状态码 System.out.println(状态码entity.getStatusCode()); //200 OK //2.获取全部响应头 HttpHeaders headers entity.getHeaders(); //拿某个头比如Content‑Type MediaType mediaType headers.getContentType(); System.out.println(mediaType); //拿自定义头比如服务返回token ListString tokenList headers.get(token); if(tokenList ! null){ String token tokenList.get(0); } //3.响应体body User user entity.getBody();HttpHeaders本质就是一个MultiValueMapString,String一个 key 可以对应多个 value所以 get 返回 List。❓什么时候必须用 getForEntity不能用 getForObject需要判断 http 状态码200/404/500做分支逻辑需要读取服务返回的响应头数据token、cookie、traceId需要拿到重定向 Location 头getForObject 坑点 如果返回 4xx、5xxgetForObject 直接抛出异常你拿不到响应头、拿不到错误返回报文。 getForEntity不会抛异常不管 200/400/500全部封装进 ResponseEntity你自己判断状态码。举个对比// 如果下游返回500直接抛RestClientException程序直接异常拿不到返回内容 User user restTemplate.getForObject(url, User.class); //下游返回500不会抛异常你可以拿到状态码、拿到错误响应头、错误body ResponseEntityUser res restTemplate.getForEntity(url, User.class, 1L); if(res.getStatusCode().is2xxSuccessful()){ //成功 }else{ //失败可以打印响应头、打印错误body }请求头 VS 响应头别搞混面试常问请求头 RequestHeaders我客户端发给服务端比如我带 token告诉服务端我是谁。响应头 ResponseHeaders服务端返回给我客户端服务返回给我的附加信息。RestTemplateexchange()方法既可以设置【请求头】又可以读取【响应头】万能。面试一句话总结响应头就是 HTTP 响应里除了业务 body 以外的元信息描述这次响应的附加属性 getForObject 直接丢掉头信息getForEntity 把状态码、响应头、响应体完整封装返回。