Skip to content

自定义和扩展 ​

概述 ​

CoApi 的 HTTP 客户端不是黑盒。该库公开了分层自定义 SPI,允许在三个时间点拦截和修改客户端构建器:(1)用于过滤器和拦截器的每客户端 YAML 配置,(2)用于负载均衡和协议特定调整的每类型构建器自定义器,(3)应用于所有客户端的全局自定义器 bean。这种设计意味着通用关注点(连接池、指标、追踪)可以全局应用,而特定于客户端的覆盖(认证头、超时)可以针对各个接口。

一览 ​

自定义点接口范围关键文件来源
基础 SPIHttpClientBuilderCustomizer<Builder>所有客户端HttpClientBuilderCustomizer.ktHttpClientBuilderCustomizer.kt
响应式自定义器WebClientBuilderCustomizerWebClient 客户端WebClientBuilderCustomizer.ktWebClientBuilderCustomizer.kt
同步自定义器RestClientBuilderCustomizerRestClient 客户端RestClientBuilderCustomizer.ktRestClientBuilderCustomizer.kt
每客户端端点ClientProperties各个客户端ClientProperties.ktClientProperties.kt
每客户端过滤器ReactiveClientProperties → ComponentDefinition<ExchangeFilterFunction>WebClient 客户端ReactiveClientProperties.ktReactiveClientProperties.kt
每客户端拦截器SyncClientProperties → ComponentDefinition<ClientHttpRequestInterceptor>RestClient 客户端SyncClientProperties.ktSyncClientProperties.kt

自定义器类层次结构 ​

mermaid
classDiagram
    class HttpClientBuilderCustomizer~Builder~ {
        <<fun interface>>
        +customize(CoApiDefinition, Builder)
    }
    class WebClientBuilderCustomizer {
        <<fun interface>>
        +customize(CoApiDefinition, WebClient.Builder)
        +NoOp
    }
    class RestClientBuilderCustomizer {
        <<fun interface>>
        +customize(CoApiDefinition, RestClient.Builder)
        +NoOp
    }
    class ClientProperties {
        <<interface>>
        +getBaseUri(String) String
        +getLoadBalanced(String) Boolean?
        +resolve(CoApiDefinition) CoApiDefinition
    }
    class ReactiveClientProperties {
        <<fun interface>>
        +getFilter(String) ComponentDefinition
    }
    class SyncClientProperties {
        <<fun interface>>
        +getInterceptor(String) ComponentDefinition
    }
    class ComponentDefinition~T~ {
        +names: List~String~
        +types: List~Class~
    }

    HttpClientBuilderCustomizer <|-- WebClientBuilderCustomizer
    HttpClientBuilderCustomizer <|-- RestClientBuilderCustomizer
    ReactiveClientProperties --> ComponentDefinition
    SyncClientProperties --> ComponentDefinition

自 v3.0.0 起,模式相关的配置拆到了各自的角色接口中:只用同步模式的应用不会在 ClientProperties 里看到任何响应式类型。三个接口都是可选 Bean:Spring Boot 下由 CoApiProperties 统一实现;不使用 Spring Boot 时,各自回退到 Empty 实现。

自定义器调用顺序 ​

创建 WebClient 或 RestClient bean 时,自定义器按严格顺序应用:

mermaid
sequenceDiagram
    autonumber
    participant FB as WebClientFactoryBean
    participant CTX as ApplicationContext
    participant Builder as WebClient.Builder
    participant LB as LoadBalancedWebClientBuilderCustomizer
    participant Global as WebClientBuilderCustomizer beans

    FB->>CTX: ClientProperties.resolve(definition)
    CTX-->>FB: 生效的定义
    FB->>CTX: getBean(WebClient.Builder)
    CTX-->>Builder: builder instance
    FB->>Builder: baseUrl(effective.baseUrl)
    FB->>CTX: ReactiveClientProperties.getFilter(name)
    FB->>Builder: 应用过滤器(先名称,后类型)
    opt effective.loadBalanced
        FB->>LB: customize(effective, builder)
        LB->>Builder: 未存在时添加 LoadBalancedExchangeFilterFunction
    end
    loop 按顺序遍历每个定制器 Bean
        FB->>Global: customize(effective, builder)
    end
    FB->>Builder: build()

WebClientFactoryBean.getObject() 中的调用顺序(RestClientFactoryBean 与之对称,使用拦截器):

顺序步骤内容是否可配置
1解析生效的定义ClientProperties.resolve(definition)——coapi.clients.<name>.* 覆盖注解通过 YAML
2获取 Builder从 ApplicationContext 获取 WebClient.Builder否
3设置基础 URLeffective.baseUrl通过 coapi.clients.<name>.base-url
4应用过滤器来自 ReactiveClientProperties 的 ComponentDefinition通过 YAML
5负载均衡仅当 effective.loadBalanced 时自动
6定制器 Bean所有 WebClientBuilderCustomizer Bean,按顺序注册为 Spring Bean

定制器收到的是生效的定义(自 v3.0.0 起):coApiDefinition.baseUrl 和 coApiDefinition.loadBalanced 已经反映了 coapi.clients.<name>.* 的覆盖配置。

自定义器决策流程 ​

mermaid
flowchart TD
    A["FactoryBean.getObject()"] --> A2["Resolve effective definition"]
    A2 --> B["Get Builder from Context"]
    B --> C[Set baseUrl]
    C --> D[Apply per-client filters/interceptors]
    D --> E{Load balanced?}
    E -->|Yes| F[Add LB filter/interceptor]
    E -->|No| H[Apply customizer beans]
    F --> H
    H --> I[Build client]

每客户端过滤器配置 ​

过滤器和拦截器通过 YAML 属性按客户端配置;ReactiveClientProperties 和 SyncClientProperties 提供类型化访问:

响应式(WebClient)过滤器:

yaml
coapi:
  clients:
    MyApiClient:
      reactive:
        filter:
          names:
            - myAuthFilter
          types:
            - com.example.LoggingExchangeFilterFunction

同步(RestClient)拦截器:

yaml
coapi:
  clients:
    MyApiClient:
      sync:
        interceptor:
          names:
            - myAuthInterceptor
          types:
            - com.example.LoggingInterceptor

AbstractHttpClientFactoryBean 中的解析(先全部名称,后全部类型):

  • names → 按名称从 ApplicationContext 解析为 bean
  • types → 按类类型从 ApplicationContext 解析为 bean

示例:连接池自定义器 ​

消费者服务器中的一个真实示例演示了每客户端连接池:

kotlin
@Service
class ConsumerWebClientBuilderCustomizer : WebClientBuilderCustomizer {
    override fun customize(
        coApiDefinition: CoApiDefinition,
        builder: WebClient.Builder
    ) {
        val connectionProvider = ConnectionProvider.builder(coApiDefinition.name)
            .maxConnections(500)
            .maxIdleTime(Duration.ofSeconds(20))
            .maxLifeTime(Duration.ofSeconds(60))
            .pendingAcquireTimeout(Duration.ofSeconds(60))
            .evictInBackground(Duration.ofSeconds(120))
            .build()
        val httpClient = HttpClient.create(connectionProvider)
        builder.clientConnector(ReactorClientHttpConnector(httpClient))
    }
}

关键要点:

  • 注册为 @Service,以便 Spring 将其发现为全局自定义器
  • 使用 coApiDefinition.name 为每个客户端创建命名连接池
  • 通过 getBeanProvider().orderedStream() 应用于所有 @CoApi 客户端

示例:每客户端认证过滤器 ​

为特定客户端配置过滤器而不影响其他客户端:

yaml
coapi:
  clients:
    SecureApiClient:
      base-url: https://api.example.com
      reactive:
        filter:
          types:
            - com.example.BearerTokenFilter

或按 bean 名称注册过滤器:

yaml
coapi:
  clients:
    SecureApiClient:
      reactive:
        filter:
          names:
            - bearerTokenFilter

自定义 HttpExchangeAdapterFactory ​

HttpExchangeAdapterFactory 决定如何把 HTTP 客户端 bean(WebClient 或 RestClient)转换为驱动接口代理的 HttpExchangeAdapter。你可以用自己的 bean 替换默认工厂——例如为适配器包装指标或链路追踪:

kotlin
@Configuration(proxyBeanMethods = false)
class MyCoApiConfiguration {
    @Bean
    fun customHttpExchangeAdapterFactory(): HttpExchangeAdapterFactory =
        HttpExchangeAdapterFactory { beanFactory, httpClientName ->
            val webClient = beanFactory.getBean(httpClientName, WebClient::class.java)
            MetricsWebClientAdapter.wrap(WebClientAdapter.create(webClient))
        }
}

CoApiFactoryBean 中的解析顺序(自 v2.1.1 起):

场景使用的工厂
只有一个 HttpExchangeAdapterFactory bean该 bean
多个候选且其中一个标了 @Primary@Primary bean
多个候选且无 primary按标准 bean 名 CoApi.HttpExchangeAdapterFactory 注册的 bean(注册器默认值)

以标准 bean 名注册自定义工厂可直接整体替换默认工厂;以其他名称注册的自定义工厂只有在它是唯一候选或标了 @Primary 时才会生效。在 v2.1.1 之前,多个非 primary 候选会在启动时抛出 NoUniqueBeanDefinitionException。

YAML 配置参考 ​

属性类型默认描述
coapi.clients.<name>.base-urlString""覆盖注解的 baseUrl
coapi.clients.<name>.load-balancedBooleannull覆盖负载均衡(true 启用 / false 禁用;未设置时回退注解)
coapi.clients.<name>.reactive.filter.namesList[]过滤器 bean 名称
coapi.clients.<name>.reactive.filter.typesList[]过滤器类类型
coapi.clients.<name>.sync.interceptor.namesList[]拦截器 bean 名称
coapi.clients.<name>.sync.interceptor.typesList[]拦截器类类型

相关页面 ​

参考资料 ​

  1. HttpClientBuilderCustomizer.kt — spring/src/main/kotlin/me/ahoo/coapi/spring/client/HttpClientBuilderCustomizer.kt
  2. WebClientBuilderCustomizer.kt — spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientBuilderCustomizer.kt
  3. RestClientBuilderCustomizer.kt — spring/src/main/kotlin/me/ahoo/coapi/spring/client/sync/RestClientBuilderCustomizer.kt
  4. ClientProperties.kt — spring/src/main/kotlin/me/ahoo/coapi/spring/client/ClientProperties.kt
  5. WebClientFactoryBean.kt — spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientFactoryBean.kt
  6. RestClientFactoryBean.kt — spring/src/main/kotlin/me/ahoo/coapi/spring/client/sync/RestClientFactoryBean.kt
  7. ConsumerWebClientBuilderCustomizer.kt — example/example-consumer-server/src/main/kotlin/.../ConsumerWebClientBuilderCustomizer.kt