从可插拔拦截器出发:自定义、注入 Spring Boot、到生效路径的完整实践(Demo 版)

1. 拦截器要解决什么问题?

  • 把签名、打点、鉴权、灰度、审计等横切逻辑从业务请求中抽离;

  • 低侵入接入任意 OkHttpClient;

  • 能通过运行时配置(如开关、密钥、目标地址等)动态生效;

  • 支持"是否命中某个条件才生效"的灰度能力。


2. 自定义一个 OkHttp Interceptor

下方仅为示例:拦截器在命中某个"特征"时,将请求"包一层"再转发;未命中则直通

java 复制代码
public final class DemoInterceptor implements Interceptor {
​
  private final DemoRuntimeSwitch runtime; // 运行时开关 & 配置
​
  public DemoInterceptor(DemoRuntimeSwitch runtime) {
    this.runtime = runtime;
  }
​
  @Override
  public Response intercept(Chain chain) throws IOException {
    Request original = chain.request();
​
    // 1) 未开启或配置不完整:放行
    if (!runtime.isOn() || !runtime.isConfigReady()) {
      return chain.proceed(original);
    }
​
    // 2) 命中某个触发条件(Demo:URL 上带 trigger=true)
    boolean hit = "true".equalsIgnoreCase(original.url().queryParameter("trigger"));
    if (!hit) return chain.proceed(original);
​
    // 3) 包装:示意将原请求信息转成一个 JSON 文本
    String wrapped = DemoJson.wrap(original); // {method, path, headers, query, body}
​
    // 4) 生成一个示意"签名"字符串(真实场景请用更安全算法与协议)
    String sign = DemoSign.simple(runtime.getAppKey(), runtime.getSecret(), wrapped);
​
    // 5) 构造"第二跳"请求(Demo:POST 表单提交到 runtime.getSecondHopUrl())
    Request secondHop = new Request.Builder()
      .url(runtime.getSecondHopUrl())
      .post(new FormBody.Builder()
              .add("appKey", runtime.getAppKey())
              .add("payload", wrapped)
              .add("sign", sign)
              .build())
      .build();
​
    try {
      return chain.proceed(secondHop);
    } catch (Exception ex) {
      // 6) 兜底:返回一个 Demo 错误响应(生产可返回统一错误结构)
      return DemoResponse.errorJson(500, "second-hop failed: " + ex.getMessage());
    }
  }
}

要点回顾:

  • 触发条件外置,真实可换成 token、Header、白名单等;

  • 直通优先:未命中一律放行,降低对主链路影响;

  • 二跳独立配置:URL、超时、重试策略等在配置中统一管理。


3. 运行时配置开关(Demo)

java 复制代码
//只展示结构与意图
public final class DemoRuntimeSwitch {
  private volatile boolean on;
  private volatile String appKey;
  private volatile String secret;
  private volatile String secondHopUrl;
​
  public boolean isOn() { return on; }
  public boolean isConfigReady() {
    return appKey != null && !appKey.isEmpty() &&
           secret != null && !secret.isEmpty() &&
           secondHopUrl != null && !secondHopUrl.isEmpty();
  }
​
  // 可由配置中心/管理接口更新
  public String getAppKey() { return appKey; }
  public String getSecret() { return secret; }
  public String getSecondHopUrl() { return secondHopUrl; }
}
  • 实际可用 AtomicReference 持有完整配置快照;

  • 更新方式可来自配置中心/Redis/管理接口,做到秒级生效

  • 配置校验应集中在一个地方(例如 normalizeAndValidate())。


4. 在 Spring Boot 中注入拦截器(Demo)

方式 A:直接注册为 Bean

java 复制代码
@Configuration
public class DemoInterceptorConfig {
​
  @Bean
  public DemoRuntimeSwitch demoRuntimeSwitch() { return new DemoRuntimeSwitch(); }
​
  @Bean
  public Interceptor demoInterceptor(DemoRuntimeSwitch runtime) {
    return new DemoInterceptor(runtime);
  }
}

方式 B:自动收集所有拦截器,再统一挂载(可插拔)

java 复制代码
// 一个简单的"桥",把容器中的拦截器收集起来,统一挂载到 OkHttp
public final class DemoInterceptorBridge {
  private static final CopyOnWriteArrayList<Interceptor> LIST = new CopyOnWriteArrayList<>();
  public static void registerAll(Collection<Interceptor> its) { LIST.addAll(its); }
  public static void attachAll(OkHttpClient.Builder b){
    for (Interceptor it : LIST) {
      boolean exists = b.interceptors().stream()
        .anyMatch(x -> x.getClass().getName().equals(it.getClass().getName()));
      if (!exists) b.addInterceptor(it);
    }
  }
}
​
@Configuration
public class DemoAutoCollectConfig {
  @Bean
  public ApplicationRunner collectInterceptors(List<Interceptor> interceptors){
    return args -> DemoInterceptorBridge.registerAll(interceptors);
  }
}

这样,业务侧无需感知有哪些拦截器,统一通过"桥"完成补挂,真正做到可插拔


5. 让拦截器在 OkHttp 中真正生效(Demo)

java 复制代码
public final class DemoOkHttpFactory {
  public static OkHttpClient newClient(){
    OkHttpClient.Builder b = new OkHttpClient.Builder()
      .connectTimeout(Duration.ofSeconds(5))
      .readTimeout(Duration.ofSeconds(10));
​
    // 统一补挂所有已注册的拦截器
    DemoInterceptorBridge.attachAll(b);
    return b.build();
  }
}

生效路径

  1. Spring 启动:扫描并注册所有 Interceptor Bean → 交给 DemoInterceptorBridge

  2. 构建客户端:使用 DemoOkHttpFactory.newClient() 获取 OkHttpClient

  3. 发起请求:OkHttp 按拦截器顺序依次调用 intercept() → 命中条件的拦截器执行逻辑,否则放行;

  4. 运行期:通过 DemoRuntimeSwitch 更新开关与参数,无重启生效。


6. 端到端流程示意


7. 最佳实践

  • 直通优先:非命中条件一律放行,降低链路抖动;

  • 独立配置:把"是否启用""目标地址""签名策略"都放到运行时配置中;

  • 安全策略:签名/加密算法做成可替换的策略接口;敏感字段统一脱敏;

  • 顺序管理:有依赖关系的拦截器(例如"鉴权 → 访问日志")要明确注册顺序;

  • 测试清单:命中/未命中、配置缺失、远端超时/5xx、请求体不可重复读取等场景;

  • 可观测:埋点 traceId、耗时、命中率、错误码分布,日志注意截断与采样。


8. Demo 模板

java 复制代码
//自定义拦截器骨架(最小实现)

public class MyDemoInterceptor implements Interceptor {
  @Override public Response intercept(Chain chain) throws IOException {
    Request req = chain.request();
    // 前置:校验/打点/改写(按需)
    Response resp = chain.proceed(req);
    // 后置:统计/日志(按需)
    return resp;
  }
}
//Spring 注入 + 统一补挂

@Configuration
public class MyDemoOkHttpConfig {
  @Bean public Interceptor myDemoInterceptor(){ return new MyDemoInterceptor(); }
  @Bean public OkHttpClient httpClient(){
    OkHttpClient.Builder b = new OkHttpClient.Builder();
    DemoInterceptorBridge.attachAll(b);
    return b.build();
  }
}
//运行时开关(示意)

public final class MySwitch {
  private final AtomicBoolean on = new AtomicBoolean(false);
  public boolean isOn(){ return on.get(); }
  public void setOn(boolean v){ on.set(v); }
}
相关推荐
Huangmiemei9114 小时前
Spring Boot项目的常用依赖有哪些?
java·spring boot·后端
天天摸鱼的java工程师4 小时前
接口联调总卡壳?先问自己:真的搞清楚 HTTP 的 Header 和 Body 了吗?
java·后端
真的想不出名儿4 小时前
上传头像到腾讯云对象存储-前端基于antdv
java·数据库·腾讯云
Nan_Shu_6144 小时前
学习SpringBoot
java·spring boot·后端·学习·spring
间彧4 小时前
微服务架构中@Data注解在DTO与实体类中的最佳实践
后端
间彧4 小时前
Spring Boot中@Data注解的深度解析与实战应用
后端
计算机毕业设计小帅4 小时前
【2026计算机毕业设计】基于Springboot的广西美食宣传系统
spring boot·毕业设计·课程设计·美食
数据库知识分享者小北4 小时前
Qoder + ADB Supabase :5分钟GET超火AI手办生图APP
数据库·后端
疯癫的老码农4 小时前
【Linux环境下安装】SpringBoot应用环境安装(二)-Redis安装
linux·spring boot·redis