环环相扣 —— 责任链模式

327 阅读7分钟

拦截器的责任链模式实现是一种在多步骤请求/响应流程中组织处理逻辑的经典模式,在 Flutter 的 Dio、OkHttp、Android 插件架构 AOP(面向切面编程)中都有广泛应用;

本文对责任链模式进行梳理解析,并通过 dio 实现一个拦截器场景


🧱 一、基础理念:

🎯 定义

责任链模式(Chain of Responsibility) :将请求的发送者与接收者解耦,多个对象都有机会处理请求。将这些对象连成一条链,请求沿着链传递,直到某个对象处理为止。

核心收益

  • 单一职责:每个拦截器只做一件事(例如:记录日志/刷新 Token/缓存命中)
  • 可插拔:增删拦截器无需大改业务代码
  • 可短路:遇到特定条件直接返回,阻止后续执行
graph TD
    classDef sender fill:#FFE4E1,stroke:#FF6B6B,stroke-width:2px
    classDef handler fill:#B9E9FF,stroke:#64B5F6,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
    
    A((请求发送者)):::sender --> B[发起请求]:::sender
    B --> C[拦截器1]:::handler
    C --> D{处理或传递?}:::decision
    D -->|处理| E[执行逻辑]:::handler
    D -->|传递| F[拦截器2]:::handler
    E --> G{是否短路?}:::decision
    G -->|是| H([返回结果]):::handler
    G -->|否| F
    F --> ...([...]):::handler
    ... --> I[最终处理者]:::handler
    I --> J[返回结果]:::handler

每个拦截器既可以处理请求/响应,又可以决定是否将流程传递给下一个拦截器。


🧠 二、Dio 拦截器责任链模型(通用结构)

abstract class Interceptor {
  Future onRequest(RequestOptions options, RequestHandler handler);
  Future onResponse(Response response, ResponseHandler handler);
  Future onError(DioError err, ErrorHandler handler);
}
  • onRequest:请求发出前
  • onResponse:收到响应后
  • onError:请求出错时

每个拦截器只专注一层职责(如鉴权、日志、缓存等),最终通过链式调用传递给下一个处理者:

request => [拦截器1] -> [拦截器2] -> ... -> [发送请求] -> [拦截器N] -> ... -> [最终响应]

📦 三、适用场景:有哪些场合适合责任链+拦截器?

使用场景说明
网络请求生命周期控制日志记录、重试、缓存、Token 刷新、重定向等逻辑分离并可插拔
插件架构/可插拔扩展每个功能可独立实现处理逻辑,不影响其他逻辑,如 UI 注入、性能统计
日志与异常收集拦截异常流程,统一处理、上报、兜底 UI 提示
动态规则校验(策略链)比如请求参数校验、权限校验、IP 拦截等
消息过滤 / 消息总线处理IM、MQ、数据流转换中逐层处理和过滤

登录验证功能

classDiagram
    class Interceptor {
        <<Dio Abstract Class>>
        +onRequest(RequestOptions, RequestInterceptorHandler)
        +onResponse(Response, ResponseInterceptorHandler)
        +onError(DioException, ErrorInterceptorHandler)
    }
    
    class AuthInterceptor {
        -static String? _token
        +static void setToken(String)
        +static void clearToken()
        +static String? getToken()
        +onRequest(RequestOptions, RequestInterceptorHandler)
        +onResponse(Response, ResponseInterceptorHandler)
        +onError(DioException, ErrorInterceptorHandler)
    }
    
    class ErrorInterceptor {
        +onError(DioException, ErrorInterceptorHandler)
    }
    
    class LoggingInterceptor {
        -Map<String, int> _requestStartTime
        +onRequest(RequestOptions, RequestInterceptorHandler)
        +onResponse(Response, ResponseInterceptorHandler)
        +onError(DioException, ErrorInterceptorHandler)
        -void printWrapped(String)
    }
    
    class RetryInterceptor {
        -int maxRetries
        -int retryInterval
        -Set<DioExceptionType> retryableErrors
        -Map<String, int> _retryCountMap
        +RetryInterceptor(int, int, Set<DioExceptionType>)
        +onError(DioException, ErrorInterceptorHandler)
        -bool _shouldRetryError(DioException)
        -bool _isIdempotentRequest(RequestOptions)
        -String _getRequestId(RequestOptions)
    }
    
    Interceptor <|-- AuthInterceptor
    Interceptor <|-- ErrorInterceptor
    Interceptor <|-- LoggingInterceptor
    Interceptor <|-- RetryInterceptor

Dio 的拦截器系统遵循一个特定的调用顺序,下面是详细的调用时序:

1. 请求阶段 (Request Phase)

当应用程序通过 HttpClient 发起网络请求时,请求会按照拦截器添加的顺序依次经过各个拦截器的 onRequest 方法:

  1. AuthInterceptor.onRequest

    • 检查是否存在 token

    • 如果存在,将 token 添加到请求头中 (Authorization: Bearer <token>)

    • 调用 handler.next(options)将请求传递给下一个拦截器

  2. ErrorInterceptor.onRequest (未实现,使用默认实现)

    • 直接调用 handler.next(options)将请求传递给下一个拦截器
  3. RetryInterceptor.onRequest (未实现,使用默认实现)

    • 直接调用 handler.next(options)将请求传递给下一个拦截器
  4. LoggingInterceptor.onRequest

    • 记录请求开始时间

    • 打印请求信息(URL、方法、头信息、请求体等)

    • 调用 handler.next(options)将请求传递给下一个拦截器

  5. 请求发送到服务器

2. 响应阶段 (Response Phase)

当服务器返回响应时,响应会按照拦截器添加的相反顺序依次经过各个拦截器的 onResponse 方法:

  1. LoggingInterceptor.onResponse

    • 计算请求耗时

    • 打印响应信息(状态码、头信息、响应数据等)

    • 调用 handler.next(response)将响应传递给下一个拦截器

  2. RetryInterceptor.onResponse (未实现,使用默认实现)

    • 直接调用 handler.next(response)将响应传递给下一个拦截器
  3. ErrorInterceptor.onResponse (未实现,使用默认实现)

    • 直接调用 handler.next(response)将响应传递给下一个拦截器
  4. AuthInterceptor.onResponse

    • 检查响应中是否包含新的 token

    • 如果包含,更新存储的 token

    • 调用 handler.next(response)将响应传递给下一个拦截器

  5. 响应返回给应用程序

3. 错误阶段 (Error Phase)

当请求发生错误时,错误会按照拦截器添加的相反顺序依次经过各个拦截器的 onError 方法:

  1. LoggingInterceptor.onError

    • 计算请求耗时

    • 打印错误信息(错误类型、状态码、错误消息等)

    • 调用 handler.next(err)将错误传递给下一个拦截器

  2. RetryInterceptor.onError

    • 获取当前重试次数

    • 判断是否满足重试条件(未超过最大重试次数、是可重试的错误类型、是幂等请求)

    • 如果满足重试条件:

      • 增加重试计数

      • 等待指定的重试间隔

      • 创建新的请求选项,继承原始请求的所有参数

      • 通过新的 Dio 实例重新发送请求

      • 如果重试成功,调用 handler.resolve(response)返回成功响应

      • 如果重试失败,调用handler.next(err) 将错误传递给下一个拦截器

    • 如果不满足重试条件,调用 handler.next(err) 将错误传递给下一个拦截器

  3. ErrorInterceptor.onError

    • 根据错误类型和状态码生成友好的错误消息

    • 打印详细的错误信息(URL、方法、请求数据等)

    • 调用 handler.next(err) 将错误传递给下一个拦截器

  4. AuthInterceptor.onError

    • 检查是否是 401 未授权错误

    • 如果是 401 错误,清除 token

    • 调用 handler.next(err) 将错误传递给下一个拦截器

  5. 错误返回给应用程序

拦截器职责分析

  1. AuthInterceptor
classDiagram
    class Interceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class AuthInterceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class TokenStore {
        +static token
        +static save(token)
        +static clear()
    }
    
    Interceptor <|-- AuthInterceptor : 继承
    AuthInterceptor --> TokenStore : 使用
  • 负责处理认证相关逻辑
  • 在请求发送前添加认证信息(token)
  • 在响应返回时检查并更新 token
  • 在收到 401 未授权错误时清除 token
  1. ErrorInterceptor
classDiagram
    class Interceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class ErrorInterceptor {
        +onError(err, handler)
        +formatError(err): String
        +reportError(err): void
        +shouldRetry(err): bool
    }
    
    class ErrorReporter {
        +logError(message, stackTrace)
        +reportToServer(error)
    }
    
    class RetryPolicy {
        +maxRetries: int
        +delay: Duration
        +shouldRetry(error): bool
    }
    
    Interceptor <|-- ErrorInterceptor : 继承
    ErrorInterceptor --> ErrorReporter : 使用
    ErrorInterceptor --> RetryPolicy : 使用
  • 负责统一处理网络请求错误
  • 根据错误类型和状态码生成友好的错误消息
  • 可以在这里统一显示错误提示或发送错误日志到服务器
  1. RetryInterceptor
classDiagram
    class Interceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class RetryInterceptor {
        -maxRetries: int
        +RetryInterceptor(maxRetries)
        +onError(err, handler)
        -_isRetryable(err): bool
    }
    
    Interceptor <|-- RetryInterceptor : 继承
  • 负责在网络请求失败时自动重试
  • 支持配置最大重试次数、重试间隔和可重试的错误类型
  • 只对幂等请求(GET、HEAD、OPTIONS、PUT、DELETE)进行重试
  1. LoggingInterceptor
classDiagram
    class Interceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class LoggingInterceptor {
        -_start: Map<String, DateTime>
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    Interceptor <|-- LoggingInterceptor : 继承
  • 负责打印网络请求和响应信息,方便调试
  • 记录请求开始时间,计算请求耗时
  • 格式化打印请求和响应的详细信息

这些拦截器共同构成了一个完整的网络请求处理链,每个拦截器负责特定的功能,通过责任链模式依次处理请求和响应,实现了关注点分离和代码的模块化。

整体类图

classDiagram
    class Interceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class AuthInterceptor {
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class LoggingInterceptor {
        -_start: Map<String, DateTime>
        +onRequest(options, handler)
        +onResponse(response, handler)
        +onError(err, handler)
    }
    
    class RetryInterceptor {
        -maxRetries: int
        +RetryInterceptor(maxRetries)
        +onError(err, handler)
        -_isRetryable(err): bool
    }
    
    class ErrorInterceptor {
        +onError(err, handler)
        +formatError(err): String
        +reportError(err): void
        +shouldRetry(err): bool
    }
    
    class TokenStore {
        +static token
        +static save(token)
        +static clear()
    }
    
    class ErrorReporter {
        +logError(message, stackTrace)
        +reportToServer(error)
    }
    
    class RetryPolicy {
        +maxRetries: int
        +delay: Duration
        +shouldRetry(error): bool
    }
    
    Interceptor <|-- AuthInterceptor : 继承
    Interceptor <|-- LoggingInterceptor : 继承
    Interceptor <|-- RetryInterceptor : 继承
    Interceptor <|-- ErrorInterceptor : 继承
    
    AuthInterceptor --> TokenStore : 使用
    LoggingInterceptor --> TokenStore : 使用
    RetryInterceptor --> TokenStore : 使用
    ErrorInterceptor --> ErrorReporter : 使用
    ErrorInterceptor --> RetryPolicy : 使用

请求时序

sequenceDiagram
    participant App as Flutter App
    participant HttpClient as HttpClient
    participant Auth as AuthInterceptor
    participant Error as ErrorInterceptor
    participant Retry as RetryInterceptor
    participant Log as LoggingInterceptor
    participant Server as Server

    App->>HttpClient: 发起网络请求 (get/post/put/delete)
    
    %% 请求阶段
    HttpClient->>Auth: onRequest
    Note over Auth: 添加认证信息 (Bearer Token)
    Auth->>Error: onRequest
    Error->>Retry: onRequest
    Retry->>Log: onRequest
    Note over Log: 记录请求开始时间<br/>打印请求信息
    Log->>Server: 发送请求到服务器
    
    alt 请求成功
        Server->>Log: 响应数据
        Note over Log: 计算请求耗时<br/>打印响应信息
        Log->>Retry: onResponse
        Retry->>Error: onResponse
        Error->>Auth: onResponse
        Note over Auth: 检查并更新Token
        Auth->>HttpClient: 返回响应
        HttpClient->>App: 返回处理后的响应
    else 请求失败
        Server->>Log: 错误响应
        Note over Log: 计算请求耗时<br/>打印错误信息
        Log->>Retry: onError
        Note over Retry: 判断是否需要重试<br/>满足条件则重试请求
        
        alt 需要重试
            Retry-->>Server: 重新发送请求
            
            alt 重试成功
                Server-->>Retry: 响应数据
                Retry-->>HttpClient: 解析响应并返回
                HttpClient-->>App: 返回处理后的响应
            else 重试失败
                Server-->>Retry: 错误响应
                Retry->>Error: onError (继续传递错误)
            end
        else 不需要重试
            Retry->>Error: onError
        end
        
        Note over Error: 统一处理错误<br/>格式化错误信息
        Error->>Auth: onError
        Note over Auth: 处理401认证错误<br/>清除Token
        Auth->>HttpClient: 返回错误
        HttpClient->>App: 抛出异常
    end

🏗 四、责任链下的拦截器设计思想

设计原则说明
单一职责每个拦截器只做一件事(如日志、Token、重试),便于维护和组合
可插拔拦截器顺序可配置,链式组合,可动态启用或移除
不干扰原则不应阻断链条除非必须(如认证失败拦截),应明确调用 handler.next(...)
可短路某拦截器可中止整个链条(比如缓存命中直接返回,Token 不合法中止)
异常可恢复异常传递可通过 handler.reject 拦截并包装为标准错误对象
  1. 统一 Handler 接口(或封装)
    保证 next() / reject() / resolve() 方法在各个环节都可用
  2. 维护顺序链表或数组结构
    每个拦截器可将请求传递给下一个,或中止流程
  3. 支持异步链路传递(如 Future)
    确保每步处理均为 async,可组合
  4. 容错 & 异常处理封装
    保证异常在链路中可被下游拦截器捕获

🧰 五、类比与实践举例

Dio 示例:日志 + Token + 缓存

dio.interceptors.addAll([
  LoggingInterceptor(),         // 打印请求/响应信息
  AuthInterceptor(),            // 自动附加 token、处理 401
  CacheInterceptor(),           // 本地缓存命中快速返回
]);

每个拦截器:

  • 只负责一个功能
  • 保持链式可控流程
  • 出现问题可独立调试

责任链式拦截器的优势与适用性

优势说明
模块化增强功能职责解耦,便于维护与测试
动态组合能力强支持按需插拔功能链
容错机制灵活中途可拦截异常并响应兜底
保持链路状态一致每一层可对请求、响应结构加工而不破坏整体流转

demo 地址:flutter_study/interceptor_test at master · lizy-coding/flutter_study