news 2026/8/28 7:24:21

详解spring cloud的RestTemplate

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
详解spring cloud的RestTemplate

RestTemplate 详解

RestTemplateSpring 提供的同步 HTTP 客户端,Spring Cloud 早期用来做微服务之间的服务调用,发送 HTTP 请求(Get/Post/Put/Delete)。

⚠️注意:现在微服务开发优先用OpenFeign;RestTemplate 是底层原生工具,很多底层、第三方接口调用仍然会用;Spring6 之后官方推荐WebClient(响应式)。

RestTemplate ≠ RPC,走的就是标准 HTTP,JSON,本质封装 HttpClient(默认 JDK HttpURLConnection,也可替换 OkHttp、Apache HttpClient)。

1. RestTemplate 在 SpringCloud 中的两种用法

微服务场景,有两种使用模式:

  1. 普通 RestTemplate:写死 IP 地址调用,不经过注册中心,不做负载均衡。
  2. @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);

底层原理

  1. 拦截 RestTemplate 的 http 请求;
  2. 解析 url 中的主机名user‑service
  3. 向注册中心 (Nacos) 拉取该服务全部实例列表;
  4. LoadBalancer 负载均衡算法(轮询 / 随机)选出一个实例 ip+port;
  5. 替换 url,真正发起 http 调用。

SpringCloud2020 之后移除 Ribbon,默认使用Spring‑Cloud‑LoadBalancer作为负载均衡组件,替代 Ribbon。

2. RestTemplate 常用 API

5 大类方法方法组说明
GETgetForObject()GET 请求,直接拿到响应对象,拿不到响应头、状态码
GETgetForEntity()GET,返回 ResponseEntity<T>,可以获取状态码、响应头、body
POSTpostForObject()POST 提交,获取返回对象
POSTpostForEntity()POST 提交,返回 ResponseEntity
通用万能exchange()万能方法!支持 GET/POST/PUT/DELETE,自定义请求头、请求体
PUT/DELETEput() / delete()put、delete 操作
总结:
  1. getForObject/postForObject:只拿响应体,丢失 http 状态码、响应头
  2. getForEntity/postForEntity:封装为ResponseEntity,可以拿到 status、headers、body;
  3. exchange():复杂请求首选,可以自己设置 header、请求方式,适用 PUT、DELETE 等;
  4. RestTemplate 搭配@LoadBalanced注解,才支持写服务名调用(LoadBalancer)。(不加:就是普通 RestTemplate,会把user‑service当成域名去 DNS 解析,解析失败报错。/ 加上@LoadBalanced:Spring Cloud 会给这个 RestTemplate 自动装配一个拦截器:LoadBalancerInterceptor。)

示例 1 getForObject

// 返回实体 User user = restTemplate.getForObject("http://user-service/user/{id}", User.class, 1L);

示例 2 getForEntity(拿状态码、响应头)

ResponseEntity<User> 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)

微服务经常要传递请求头(token,traceId),getForObject/postForObject 不方便设置 header,用 exchange

HttpHeaders headers = new HttpHeaders(); headers.set("token","abc123456"); headers.setContentType(MediaType.APPLICATION_JSON); UserDTO body = new UserDTO(); HttpEntity<UserDTO> httpEntity = new HttpEntity<>(body, headers); ResponseEntity<User> res = restTemplate.exchange( "http://user-service/user/add", HttpMethod.POST, httpEntity, User.class ); User result = res.getBody();

3. @LoadBalanced 原理源码简述(面试)

@LoadBalanced是一个标记注解; Spring Cloud 自动装配LoadBalancerInterceptor拦截器,加到 RestTemplate 拦截器链。

执行流程:

  1. restTemplate 发起 http 请求;
  2. 进入LoadBalancerInterceptor拦截;
  3. 解析 URI 的 host,当做服务名称;
  4. LoadBalancerClient调用注册中心获取实例列表;
  5. 负载均衡挑选实例,替换 URI 为真实ip:port
  6. 继续执行 http 请求。

坑:同一个配置类,不要同时存在普通 RestTemplate 和 @LoadBalanced RestTemplate。如果要两个,需要分开两个 Bean,区分名字。

4. RestTemplate 常见坑点

坑 1:@LoadBalanced RestTemplate 不能直接访问外部 http 地址

比如调用百度、第三方接口,url 写http://www.baidu.com会报错。

原因:拦截器把域名当成服务名,去 Nacos 找叫www.baidu.com的服务,找不到报错。

✅解决方案:

  • 两套 RestTemplate:一套加@LoadBalanced用于内部微服务调用;一套不加,专门调用第三方外部接口。

坑 2:默认底层是 JDK HttpURLConnection,性能差,无连接池

默认没有 http 连接池,每次请求新建连接,高并发性能差。 生产环境替换底层实现:切换 Apache HttpClient / OkHttp,开启连接池。

pom 引入 Apache httpclient

<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>

配置 RestTemplate 使用 HttpClient

@Bean @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、traceId)

RestTemplate 默认不会把当前服务的 header 向下游传递。

OpenFeign 有拦截器可以传递 header;

RestTemplate 自己要手动拿 request,塞到 HttpHeaders 里面。

坑 5:序列化问题,泛型、时间转换异常

RestTemplate 底层使用MappingJackson2HttpMessageConverter; 遇到日期、复杂泛型,需要自定义消息转换器。

坑 6:UnknownHostException

RestTemplate 搭配@LoadBalanced注解, 才支持写服务名(例如http://user-service),否则直接报 UnknownHostException。

原理

@LoadBalanced是 Spring Cloud 的标记注解

@Bean @LoadBalanced // ✅关键注解 public RestTemplate restTemplate() { return new RestTemplate(); }
  • 不加:就是普通 RestTemplate,会把user‑service当成域名去 DNS 解析,解析失败报错。
  • 加上@LoadBalanced:Spring Cloud 会给这个 RestTemplate 自动装配一个拦截器:LoadBalancerInterceptor

拦截器执行流程

  1. 代码执行:restTemplate.getForObject("http://user‑service/xxx", String.class)
  2. 请求被LoadBalancerInterceptor拦截。
  3. 提取服务名user‑service,交给Spring Cloud LoadBalancer
  4. LoadBalancer 从 Nacos 获取该服务实例列表,本地算法选出一个实例192.168.x.x:8081
  5. 将 URI 替换成真实地址http://192.168.x.x:8081/xxx
  6. 真实 HTTP 请求发出。

⚠️注意:

  1. 只有被@LoadBalanced修饰的 RestTemplate Bean 才有这个拦截器。如果你 new RestTemplate (),没有交给 Spring 管理,就算类上写注解也无效。
  2. Feign/WebClient 的@LoadBalanced作用逻辑类似。
  3. 依赖:必须引入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 和 WebClient

  • RestTemplate:同步阻塞,每个请求占用一个线程,高并发场景线程池容易耗尽。
  • WebClient:Spring5 推出,非阻塞响应式 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 不会复制过去。

解决方案

两种思路:

  1. 每次调用手动从HttpServletRequest获取 header,设置到 RestTemplate 的HttpHeaders。 缺点:每个调用地方都写一遍,重复代码。
  2. 自定义 RestTemplate 拦截器(ClientHttpRequestInterceptor)全局透传✅推荐

注意:使用 ThreadLocal 存放需要传递的 header(token、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; }

不能直接在拦截器拿HttpServletRequest:RestTemplate 拦截器不一定在 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、常见坑点(面试高频)

  1. ❌直接在 RestTemplate 拦截器获取HttpServletRequest,大概率 null。 RestTemplate 调用可以不在 web 线程,所以不要直接拿 request,使用 ThreadLocal。
  2. ❌使用普通 ThreadLocal,异步 @Async 场景,子线程拿不到父线程上下文,traceId 丢失。 解决方案:使用阿里TransmittableThreadLocal
  3. Gateway 网关层转发丢失 header:Gateway 用 GlobalFilter 过滤器处理。
  4. Seata、Sentinel、链路追踪框架(SkyWalking)内部已经做了 traceId 透传,自己不用处理 traceId;但业务 token 需要自己处理。

对比总结

调用方式透传 header 方案
RestTemplate实现ClientHttpRequestInterceptor,配合 ThreadLocal
OpenFeign实现RequestInterceptor

一句话面试口述: RestTemplate/Feign 发起的是全新 http 请求,默认不会把上游请求的 header 往下游传递。RestTemplate 需要注册 ClientHttpRequestInterceptor,Feign 使用 RequestInterceptor,结合 ThreadLocal 存放 token、traceId 实现透传;异步场景要注意 ThreadLocal 传递问题。

响应头 Response Headers

HTTP 报文分为两大部分:

  1. 响应体 Body:真正业务数据,比如{"id":1,"name":"张三"},就是你要的 User 对象
  2. 响应头 Headers附加元数据,不是业务数据,是服务端告诉客户端的附加信息,一堆 key‑value

getForObject()只帮你拿【响应体 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/html
Content‑Length响应体字节长度
Date服务器时间
Set‑Cookie服务端返回 cookie 给浏览器保存
token/Authorization业务自定义头,返回令牌,微服务之间鉴权用
traceId链路追踪 ID,SkyWalking 输出,排查日志链路用
Location重定向地址,302 跳转的时候返回

响应头分两类:

  1. HTTP 标准头;
  2. 自定义响应头:业务自己定义,比如返回 token、traceId。

代码实操 getForEntity 拿响应头

ResponseEntity<User> 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 List<String> tokenList = headers.get("token"); if(tokenList != null){ String token = tokenList.get(0); } //3.响应体body User user = entity.getBody();

HttpHeaders本质就是一个MultiValueMap<String,String>一个 key 可以对应多个 value,所以 get 返回 List

❓什么时候必须用 getForEntity,不能用 getForObject?

  1. 需要判断 http 状态码(200/404/500)做分支逻辑;
  2. 需要读取服务返回的响应头数据:token、cookie、traceId;
  3. 需要拿到重定向 Location 头;

getForObject 坑点: 如果返回 4xx、5xx,getForObject 直接抛出异常,你拿不到响应头、拿不到错误返回报文。 getForEntity不会抛异常,不管 200/400/500,全部封装进 ResponseEntity,你自己判断状态码。

举个对比

// 如果下游返回500,直接抛RestClientException,程序直接异常,拿不到返回内容 User user = restTemplate.getForObject(url, User.class); //下游返回500不会抛异常!你可以拿到状态码、拿到错误响应头、错误body ResponseEntity<User> res = restTemplate.getForEntity(url, User.class, 1L); if(res.getStatusCode().is2xxSuccessful()){ //成功 }else{ //失败,可以打印响应头、打印错误body }

请求头 VS 响应头(别搞混,面试常问)

  • 请求头 RequestHeaders:我(客户端)发给服务端,比如我带 token,告诉服务端我是谁。
  • 响应头 ResponseHeaders:服务端返回给我(客户端),服务返回给我的附加信息。

RestTemplateexchange()方法,既可以设置【请求头】,又可以读取【响应头】,万能。

面试一句话总结

响应头就是 HTTP 响应里除了业务 body 以外的元信息,描述这次响应的附加属性; getForObject 直接丢掉头信息,getForEntity 把状态码、响应头、响应体完整封装返回。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 7:23:26

腾讯混元HyASR 3.0 preview:语音识别如何攻克方言与噪音难题

如果你的工作里经常要处理录音转写&#xff0c;大概会遇到这样的场景&#xff1a;一段十几分钟的会议录音&#xff0c;A是北方人&#xff0c;B说粤语&#xff0c;C偶尔蹦几个英文术语&#xff0c;背景里还有空调声和键盘声。丢给市面上的ASR工具&#xff0c;结果是普通话部分基…

作者头像 李华
网站建设 2026/8/28 7:22:45

springboot社区团购管理系统73456-计算机课程设计、毕业设计

前言 ✨ 博主介绍&#xff1a;一线全栈工程师&#xff0c;毕设实战引路人。技术栈覆盖Java、Python、C#、PHP、Node.js及UniApp跨端开发&#xff0c;擅长多语言项目落地与架构设计。持续分享毕设源码、开题报告、技术选型心得与职场踩坑经验。用工程化思维写代码&#xff0c;帮…

作者头像 李华
网站建设 2026/8/28 7:20:07

具身智能的“终极形态”:人机融合、自动化与社会重塑

前沿技术探索&#xff1a;TVA智能体&#xff08;简称TVA&#xff09;TVA智能体&#xff08;亦称“AI智能体视觉”或“TVA视觉智能体”&#xff09;是依托Transformer架构与“因式智能体”理论构建的通用视觉技术体系。它有机融合深度强化学习&#xff08;DRL&#xff09;、卷积…

作者头像 李华