开发者

SpringBoot自定义RestTemplate的拦截器链的实战指南

目录
  • 引言
  • 一、为什么需要拦截器链?
  • 二、RestTemplate拦截器基础
    • 2.1 核心接口:ClientHttpRequestInterceptor
    • 2.2 第一个拦截器:日志拦截器
  • 三、拦截器链实战:从单拦截器到多拦截器协同
    • 3.1 注册拦截器链
    • 3.2 核心拦截器实现
      • (1)认证拦截器:自动添加Token
      • (2)重试拦截器:失败自动重试
  • 四、实战踩坑指南
    • 4.1 响应流只能读取一次问题
      • 4.2 拦截器顺序不当导致功能异常
        • 4.3 线程安全问题
        • 五、测试示例
          • 5.1 测试接口
            • 5.2 依赖与配置
            • 六、总结与扩展
              • 核心要点

              引言

              在项目开发中,RestTemplate作为Spring提供的HTTP客户端工具,经常用于访问内部或三方服务。

              但在实际项目中,我们往往需要对请求进行统一处理:比如添加认证Token、记录请求日志、设置超时时间、实现失败重试等。

              如果每个接口调用都手动处理这些逻辑,不仅代码冗余,还容易出错。

              一、为什么需要拦截器链?

              先看一个场景:假设我们的支付服务需要调用第三方支付接口,每次请求都要做这些事

              1. 添加Authorization请求头(认证Token)
              2. 记录请求URL、参数、耗时(日志审计)
              3. 设置3秒超时时间(防止阻塞)
              4. 失败时重试2次(网络抖动处理)

              如果没有拦截器,代码会写成这样:

              // 调用第三方支付接口
              public String createOrder(String orderId) {
                  // 1. 创建RestTemplate(重复10次)
                  RestTemplate restTemplate = new RestTemplate();
                  
                  // 2. 设置超时(重复10次)
                  SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
                  factory.setConnectTimeout(3000);
                  factory.setReadTimeout(3000);
                  restTemplate.setRequestFactory(factory);
                  
                  // 3. 添加认证头(重复10次)
                  HttpHeaders headers = new HttpHeaders();
                  headers.set("Authorization", "Bearer " + getToken());
                  HttpEntity<String> entity = new HttpEntity<>(headers);
                  
                  // 4. 记录日志(重复10次)
                  log.info("请求支付接口: {}", orderId);
                  try {
                      String result = restTemplate.postForObject(PAY_URL, entity, String.class);
                      log.info("请求成功: {}", result);
                      return result;
                  } catch (Exception e) {
                      log.error("请求失败", e);
                      // 5. 重试(重复10次)
                      for (int i = 0; i < 2; i++) {
                          try {
                              return restTemplate.postForObject(PAY_URL, entity, String.class);
                          } catch (Exception ex) { /* 重试逻辑 */ }
                      }
                      throw e;
                  }
              }
              

              这段代码的问题很明显:重复逻辑散落在各个接口调用中,维护成本极高。如果有10个接口需要调用第三方服务,就要写10遍相同的代码。

              而拦截器链能把这些通用逻辑抽离成独立组件,按顺序串联执行,实现“一次定义,处处复用”。

              // 重构后:一行搞定所有通用逻辑
              return restTemplate.postForObject(PAY_URL, entity, String.class);
              

              二、RestTemplate拦截器基础

              2.1 核心接口:ClientHttpRequestInterceptor

              Spring提供了ClientHttpRequestInterhttp://www.devze.comceptor接口,所有自定义拦截器都要实现它

              public interface ClientHttpRequestInterceptor {
                  // request:请求对象(可修改请求头、参数)
                  // body:请求体字节数组
                  // execution:执行器(用于调用下一个拦截器)
                  ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
              }
              

              拦截器工作流程

              1. 拦截器可以修改请求(如添加头信息、修改参数)
              2. 通过execution.execute(request, body)调用下一个拦截器
              3. 最终执行器会发送实际HTTP请求,返回响应
              4. 拦截器可以处理响应(如记录日志、解析响应体)

              2.2 第一个拦截器:日志拦截器

              先实现一个打印请求响应日志的拦截器,这是项目中最常用的功能:

              import lombok.extern.slf4j.Slf4j;
              import org.springframework.http.HttpRequest;
              import org.springframework.http.client.ClientHttpRequestExecution;
              import org.springframework.http.client.ClientHttpRequestInterceptor;
              import org.LHdwgqrlQAspringframework.http.client.ClientHttpResponse;
              import org.springframework.util.StreamUtils;
              
              import Java.io.IOException;
              import java.nio.charset.StandardCharsets;
              
              @Slf4j
              public class LoggingInterceptor implements ClientHttpRequestInterceptor {
                  @Override
                  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                      // 1. 记录请求信息
                      long start = System.currentTimeMillis();
                      log.info("===== 请求开始 =====");
                      log.info("URL: {}", request.getURI());
                      log.info("Method: {}", request.getMethod());
                      log.info("Headers: {}", request.getHeaders());
                      log.info("Body: {}", new String(body, StandardCharsets.UTF_8));
              
                      // 2. 执行下一个拦截器(关键!不调用会中断链条)
                      ClientHttpResponse response = execution.execute(request, body);
              
                      // 3. 记录响应信息
                      String responseBody = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
                      log.info("===== 请求结束 =====");
                      log.info("Status: {}", response.getStatusCode());
                      log.info("Headers: {}", response.getHeaders());
                      log.info("Body: {}", responseBody);
                      log.info("耗时: {}ms", System.currentTimeMillis() - start);
              
                      return response;
                  }
              }
              

              关键点

              • execution.execute(...)是调用链的核心,必须调用才能继续执行
              • 响应流response.getBody()只能读取一次,如需多次处理需缓存(后面会讲解决方案)

              三、拦截器链实战:从单拦截器到多拦截器协同

              3.1 注册拦截器链

              SpringBoot中通过RestTemplate.setInterceptors()注册多个拦截器,形成调用链:

              import org.springframework.context.annotation.Bean;
              import org.springframework.context.annotation.Configuration;
              import org.springframework.http.client.BufferingClientHttpRequestFactory;
              import org.springframework.http.client.ClientHttpRequestInterceptor;
              import org.springframework.http.client.SimpleClientHttpRequestFactory;
              import org.springframework.web.client.RestTemplate;
              
              import java.util.ArrayList;
              import java.util.List;
              
              @Configuration
              public class RestTemplateConfig {
              
                  @Bean
                  public RestTemplate customRestTemplate() {
                      // 1. 创建拦截器列表(顺序很重要!)
                      List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
                      interceptors.add(new AuthInterceptor());       // 1. 认证拦截器(先加请求头)
                      interceptors.add(new LoggingInterceptor());    // 2. 日志拦截器(记录完整请求)
                      interceptors.add(new RetryInterceptor(2));     // 3. 重试拦截器(最后处理失败)
              
                      // 2. 创建RestTemplate并设置拦截器
                      RestTemplate restTemplate = new RestTemplate();
                      restTemplate.setInterceptors(interceptors);
              
                      // 3. 解决响应流只能读取一次的问题(关键配置!)
                      SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
                      factory.setConnectTimeout(3000);  // 连接超时3秒
                      factory.setReadTimeout(3000);     // 读取超时3秒
                      restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(factory));
              
                      return restTemplate;
                  }
              }
              

              拦截器执行顺序:按添加顺序执行(像排队一样),上述示例的执行流程是:

              AuthInterceptor → LoggingInterceptor → RetryInterceptor → 实际请求 → RetryInterceptor → LoggingInterceptor → AuthInterceptor
              

              3.2 核心拦截器实现

              (1)认证拦截器:自动添加Token

              import org.springframework.http.HttpHeaders;
              import org.springframework.http.HttpRequest;
              import org.springframework.http.client.ClientHttpRequestExecution;
              import org.springframework.http.client.ClientHttpRequestInterceptor;
              import org.springframework.http.client.ClientHttpResponse;
              
              import java.io.IOException;
              
              public class AuthInterceptor implements ClientHttpRequestInterceptor {
                  @Override
                  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                      // 1. 从缓存获取Token(实际项目可能从Redis或配置中心获取)
                      http://www.devze.comString token = getToken();
                      
                      // 2. 添加Authorization请求头
                      HttpHeaders headers = request.getHeaders();
                      headers.set("Authorization", "Bearer " + token);  // Bearer认证格式
                      
                      // 3. 传递给下一个拦截器
                      return execution.execute(request, body);
                  }
              
                  // 模拟从缓存获取Token
                  private String getToken() {
                      return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";  // 实际项目是真实Token
                  }
              }
              

              (2)重试拦截器:失败自动重试

              package com.example.rest;
              
              import lombok.extern.slf4j.Slf4j;
              import org.springframework.http.HttpRequest;
              import org.springframework.http.client.ClientHttpRequestExecution;
              import org.springframework.http.client.ClientHttpRequestInterceptor;
              import org.springframework.http.client.ClientHttpResponse;
              import org.springframework.web.client.HttpStatusCodeException;
              
              import java.io.IOException;
              
              @Slf4j
              public class RetryInterceptor implements ClientHttpRequestInterceptor {
                  private final int maxRetries;  // 最大重试次数
              
                  public RetryInterceptor(int maxRetries) {
                      this.maxRetries = maxRetries;
                  }
              
                  @Override
                  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                      int retryCount = 0;
                      while (true) {
                          try {
                              // 执行请求(调用下一个拦截器或实际请求)
                              return execution.execute(request, body);
                          } catch (HttpStatusCodeException e) {
                              // 只对5xx服务器错误重试(业务错误不重试)
                              if (e.getStatusCode().is5xxServerError() && retryCount < maxRetries) {
                                  retryCount++;
                                  log.warn("服务器错误,开始第{}次重试,状态码:{}", retryCount, e.getStatusCode());
                                  continue;
                              }
                              throw e;  // 非5xx错误或达到最大重试次数
                          } catch (IOException e) {
                              // 网络异常重试(如连接超时、DNS解析失败)
                              if (retryCount < maxRetries) {
                                  retryCount++;
                                  log.warn("网络异常,开始第{}次重试", retryCount, e);
                                  continue;
                              }
                              throw e;
                          }
                      }
                  }
              }
              

              四、实战踩坑指南

              4.1 响应流只能读取一次问题

              现象:日志拦截器读取响应体后,后续拦截器再读会读取到空数据。

              原因:响应流默认是一次性的,读完就关闭了。

              解决方案:用BufferingClientHttpRequestFactory包装请求工厂,缓存响应流:

              // 创建支持响应缓存的工厂(已在3.1节配置中体现)
              SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
              BufferingClientHttpRequestFactory bufferingFactory = new BufferingClientHttpRequestFactory(factory);
              restTemplate.setRequestFactory(bufferingFactory);
              

              4.2 拦截器顺序不当导致功能异常

              反例:把LoggingInterceptor放在AuthInterceptor前面,日志中会看不到Authorization头,因为日志拦截器先执行时还没添加认证头。

              正确顺序(按职责划分):

              • 1. 前置处理拦截器:认证、加密、参数修改
              • 2. 日志拦截器:记录完整请求
              • 3. 重试/降级拦截器:处理异常情况

              4.3 线程安全问题

              RestTemplate是线程安全的,但拦截器若有成员变量,需确保线程安全

              错误示例

              public class BadInterceptor javascriptimplements ClientHttpRequestInterceptor {
                  private int count = 0;  // ❌ 多线程共享变量,会导致计数错误
                  
                  @Override
                  public ClientHttpResponse intercept(...) {
                      count++;  // 危险!多线程下count值不准
                      // ...
                  }
              }
              

              解决方案

              • 拦截器避免定义可变成员变量
              • 必须使用时,用ThreadLocal隔离线程状态

              五、测试示例

              5.1 测试接口

              package com.example.rest;
              
              import org.springframework.beans.factory.annotation.Autowired;
              import org.springframework.web.bind.annotation.GetMapping;
              import org.springframework.web.bind.annotation.RestController;
              import org.springframework.web.client.RestTemplate;
              
              @RestController
              public class TestController {
              
                  @Autowired
                  private RestTemplate customRestTemplate;  // 注入自定义的RestTemplate
              
                  @GetMapping("/call-api")
                  public String callThirdApi() {
                      // 调用第三方API,拦截器自动处理认证、日志、重试
                      return customRestTemplate.getForObject("http://localhost:8080/mock-third-api", String.class);
                  }
              
                  // 模拟一个三方接口
                  @GetMapping("/mock-third-api")
                  public String mockThirdApi() {
                      return "hello";
                  }
              }
              

              5.2 依赖与配置

              只需基础的Spring Boot Starter Web:

              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifhttp://www.devze.comactId>spring-boot-starter-web</artifactId>
                  </dependency>
              </dependencies>
              

              六、总结与扩展

              通过自定义RestTemplate的拦截器链,我们可以将请求处理的通用逻辑(认证、日志、重试等)抽离成独立组件,实现代码复用和统一维护。

              核心要点

              • 1. 拦截器链顺序:按“前置处理→日志→重试”顺序注册,确保功能正确
              • 2. 响应流处理:使用BufferingClientHttpRequestFactory解决流只能读取一次问题
              • 3. 线程安全:拦截器避免定义可变成员变量,必要时使用ThreadLocal
              • 4. 异常处理:重试拦截器需明确重试条件(如只对5xx错误重试)

              以上就是SpringBoot自定义RestTemplate的拦截器链的实战指南的详细内容,更多关于SpringBoot RestTemplate拦截器链的资料请关注编程客栈(www.devze.com)其它相关文章!

              0

              上一篇:

              下一篇:

              精彩评论

              暂无评论...
              验证码 换一张
              取 消

              最新开发

              开发排行榜