前言

在前面的文章中,我们已经实现了一个功能完整的 HTTP 客户端框架。但是在实际的企业级应用中,大部分 Java 项目都使用 Spring Boot 作为基础框架。为了让我们的 Atlas HTTP Client 更好地融入 Spring 生态,我们需要提供 Spring Boot 的集成支持。

Spring Boot 集成的核心价值在于:

  • 自动配置:零配置或最少配置即可使用
  • 依赖注入:HTTP 客户端可以直接注入到 Spring Bean 中
  • 配置管理:通过 application.properties/yml 管理配置
  • 生命周期管理:与 Spring 容器生命周期集成
  • 监控集成:与 Spring Boot Actuator 集成

本文将详细介绍如何实现一个完整的 Spring Boot Starter。

Spring Boot Starter 设计原理

什么是 Spring Boot Starter?

Spring Boot Starter 是一个依赖描述符,它包含了一组相关的依赖和自动配置类。当用户添加 Starter 依赖时,Spring Boot 会自动配置相关的组件。

Starter 的组成部分

  1. 依赖管理:定义需要的第三方库
  2. 自动配置类:根据条件自动创建和配置 Bean
  3. 配置属性类:绑定配置文件中的属性
  4. 条件注解:控制自动配置的生效条件

自动配置原理

Spring Boot 通过 @EnableAutoConfiguration 注解启动自动配置机制:

1. 扫描 META-INF/spring.factories 文件
2. 加载 EnableAutoConfiguration 指定的配置类
3. 根据条件注解决定是否生效
4. 创建和注册相关的 Bean

项目结构设计

我们创建一个独立的 Spring Boot Starter 模块:

atlas-httpclient-spring-boot-starter/
├── src/
│   └── main/
│       ├── java/
│       │   └── io/github/nemoob/httpclient/spring/
│       │       ├── autoconfigure/
│       │       │   ├── AtlasHttpClientAutoConfiguration.java
│       │       │   ├── AtlasHttpClientProperties.java
│       │       │   └── HttpClientBeanPostProcessor.java
│       │       ├── annotation/
│       │       │   └── EnableAtlasHttpClient.java
│       │       └── factory/
│       │           └── SpringHttpClientFactory.java
│       └── resources/
│           └── META-INF/
│               ├── spring.factories
│               └── spring-configuration-metadata.json
└── pom.xml

配置属性类实现

首先定义配置属性类,用于绑定配置文件中的属性:

package io.github.nemoob.httpclient.spring.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.HashMap;
import java.util.Map;

/**
 * Atlas HTTP Client 配置属性
 */
@ConfigurationProperties(prefix = "atlas.httpclient")
public class AtlasHttpClientProperties {
    
    /**
     * 是否启用 Atlas HTTP Client
     */
    private boolean enabled = true;
    
    /**
     * 默认连接超时时间(毫秒)
     */
    private int defaultConnectTimeout = 5000;
    
    /**
     * 默认读取超时时间(毫秒)
     */
    private int defaultReadTimeout = 10000;
    
    /**
     * 是否启用日志拦截器
     */
    private boolean loggingEnabled = true;
    
    /**
     * 是否启用性能监控拦截器
     */
    private boolean metricsEnabled = true;
    
    /**
     * 客户端特定配置
     */
    private Map<String, ClientConfig> clients = new HashMap<>();
    
    /**
     * 拦截器配置
     */
    private InterceptorConfig interceptors = new InterceptorConfig();
    
    // Getter and Setter methods
    public boolean isEnabled() {
        return enabled;
    }
    
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
    
    public int getDefaultConnectTimeout() {
        return defaultConnectTimeout;
    }
    
    public void setDefaultConnectTimeout(int defaultConnectTimeout) {
        this.defaultConnectTimeout = defaultConnectTimeout;
    }
    
    public int getDefaultReadTimeout() {
        return defaultReadTimeout;
    }
    
    public void setDefaultReadTimeout(int defaultReadTimeout) {
        this.defaultReadTimeout = defaultReadTimeout;
    }
    
    public boolean isLoggingEnabled() {
        return loggingEnabled;
    }
    
    public void setLoggingEnabled(boolean loggingEnabled) {
        this.loggingEnabled = loggingEnabled;
    }
    
    public boolean isMetricsEnabled() {
        return metricsEnabled;
    }
    
    public void setMetricsEnabled(boolean metricsEnabled) {
        this.metricsEnabled = metricsEnabled;
    }
    
    public Map<String, ClientConfig> getClients() {
        return clients;
    }
    
    public void setClients(Map<String, ClientConfig> clients) {
        this.clients = clients;
    }
    
    public InterceptorConfig getInterceptors() {
        return interceptors;
    }
    
    public void setInterceptors(InterceptorConfig interceptors) {
        this.interceptors = interceptors;
    }
    
    /**
     * 单个客户端配置
     */
    public static class ClientConfig {
        private String baseUrl;
        private int connectTimeout = -1; // -1 表示使用默认值
        private int readTimeout = -1;
        private boolean async = false;
        private String executor = "";
        
        // Getter and Setter methods
        public String getBaseUrl() {
            return baseUrl;
        }
        
        public void setBaseUrl(String baseUrl) {
            this.baseUrl = baseUrl;
        }
        
        public int getConnectTimeout() {
            return connectTimeout;
        }
        
        public void setConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
        }
        
        public int getReadTimeout() {
            return readTimeout;
        }
        
        public void setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
        }
        
        public boolean isAsync() {
            return async;
        }
        
        public void setAsync(boolean async) {
            this.async = async;
        }
        
        public String getExecutor() {
            return executor;
        }
        
        public void setExecutor(String executor) {
            this.executor = executor;
        }
    }
    
    /**
     * 拦截器配置
     */
    public static class InterceptorConfig {
        private LoggingConfig logging = new LoggingConfig();
        private RetryConfig retry = new RetryConfig();
        
        public LoggingConfig getLogging() {
            return logging;
        }
        
        public void setLogging(LoggingConfig logging) {
            this.logging = logging;
        }
        
        public RetryConfig getRetry() {
            return retry;
        }
        
        public void setRetry(RetryConfig retry) {
            this.retry = retry;
        }
    }
    
    /**
     * 日志拦截器配置
     */
    public static class LoggingConfig {
        private boolean logHeaders = true;
        private boolean logBody = true;
        private int maxBodyLength = 1024;
        
        public boolean isLogHeaders() {
            return logHeaders;
        }
        
        public void setLogHeaders(boolean logHeaders) {
            this.logHeaders = logHeaders;
        }
        
        public boolean isLogBody() {
            return logBody;
        }
        
        public void setLogBody(boolean logBody) {
            this.logBody = logBody;
        }
        
        public int getMaxBodyLength() {
            return maxBodyLength;
        }
        
        public void setMaxBodyLength(int maxBodyLength) {
            this.maxBodyLength = maxBodyLength;
        }
    }
    
    /**
     * 重试拦截器配置
     */
    public static class RetryConfig {
        private boolean enabled = false;
        private int maxRetries = 3;
        private long retryDelay = 1000;
        
        public boolean isEnabled() {
            return enabled;
        }
        
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
        
        public int getMaxRetries() {
            return maxRetries;
        }
        
        public void setMaxRetries(int maxRetries) {
            this.maxRetries = maxRetries;
        }
        
        public long getRetryDelay() {
            return retryDelay;
        }
        
        public void setRetryDelay(long retryDelay) {
            this.retryDelay = retryDelay;
        }
    }
}

自动配置类实现

接下来实现核心的自动配置类:

package io.github.nemoob.httpclient.spring.autoconfigure;

import io.github.nemoob.httpclient.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Atlas HTTP Client 自动配置类
 */
@Configuration
@ConditionalOnClass({HttpClientFactory.class})
@ConditionalOnProperty(prefix = "atlas.httpclient", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(AtlasHttpClientProperties.class)
public class AtlasHttpClientAutoConfiguration {
    
    @Autowired
    private AtlasHttpClientProperties properties;
    
    /**
     * 注册 HTTP 客户端 Bean 后处理器
     */
    @Bean
    @ConditionalOnMissingBean
    public HttpClientBeanPostProcessor httpClientBeanPostProcessor() {
        return new HttpClientBeanPostProcessor(properties);
    }
    
    /**
     * 配置日志拦截器
     */
    @Bean
    @ConditionalOnProperty(prefix = "atlas.httpclient", name = "logging-enabled", havingValue = "true", matchIfMissing = true)
    @Order(100)
    public LoggingInterceptor loggingInterceptor() {
        AtlasHttpClientProperties.LoggingConfig config = properties.getInterceptors().getLogging();
        return new LoggingInterceptor(config.isLogHeaders(), config.isLogBody(), config.getMaxBodyLength());
    }
    
    /**
     * 配置性能监控拦截器
     */
    @Bean
    @ConditionalOnProperty(prefix = "atlas.httpclient", name = "metrics-enabled", havingValue = "true", matchIfMissing = true)
    @Order(200)
    public MetricsInterceptor metricsInterceptor() {
        return new MetricsInterceptor();
    }
    
    /**
     * 配置重试拦截器
     */
    @Bean
    @ConditionalOnProperty(prefix = "atlas.httpclient.interceptors.retry", name = "enabled", havingValue = "true")
    @Order(300)
    public RetryInterceptor retryInterceptor() {
        AtlasHttpClientProperties.RetryConfig config = properties.getInterceptors().getRetry();
        return new RetryInterceptor(config.getMaxRetries(), config.getRetryDelay());
    }
    
    /**
     * 配置默认执行器
     */
    @Bean(name = "atlasHttpClientExecutor")
    @ConditionalOnMissingBean(name = "atlasHttpClientExecutor")
    public ExecutorService atlasHttpClientExecutor() {
        return Executors.newCachedThreadPool(r -> {
            Thread thread = new Thread(r, "atlas-httpclient-");
            thread.setDaemon(true);
            return thread;
        });
    }
}

Bean 后处理器实现

Bean 后处理器负责扫描和创建 HTTP 客户端 Bean:

package io.github.nemoob.httpclient.spring.autoconfigure;

import io.github.nemoob.httpclient.HttpClientFactory;
import io.github.nemoob.httpclient.RequestInterceptor;
import io.github.nemoob.httpclient.annotation.HttpClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * HTTP 客户端 Bean 后处理器
 * 负责扫描和注册 HTTP 客户端接口
 */
public class HttpClientBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware, EnvironmentAware {
    
    private final AtlasHttpClientProperties properties;
    private BeanFactory beanFactory;
    private Environment environment;
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
    
    public HttpClientBeanPostProcessor(AtlasHttpClientProperties properties) {
        this.properties = properties;
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
    
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 注册全局拦截器
        registerGlobalInterceptors();
        
        // 扫描并注册 HTTP 客户端
        scanAndRegisterHttpClients(registry);
    }
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 不需要实现
    }
    
    /**
     * 注册全局拦截器
     */
    private void registerGlobalInterceptors() {
        if (beanFactory instanceof ConfigurableListableBeanFactory) {
            ConfigurableListableBeanFactory configurableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
            
            // 获取所有拦截器 Bean
            Map<String, RequestInterceptor> interceptors = configurableBeanFactory.getBeansOfType(RequestInterceptor.class);
            
            // 注册为全局拦截器
            for (RequestInterceptor interceptor : interceptors.values()) {
                HttpClientFactory.addGlobalInterceptor(interceptor);
            }
        }
    }
    
    /**
     * 扫描并注册 HTTP 客户端
     */
    private void scanAndRegisterHttpClients(BeanDefinitionRegistry registry) {
        // 这里简化实现,实际应该扫描指定包下的所有 @HttpClient 接口
        // 可以通过 @EnableAtlasHttpClient 注解指定扫描包
        
        // 示例:手动注册一些已知的客户端接口
        // 在实际实现中,应该使用 ClassPathScanningCandidateComponentProvider 进行扫描
    }
    
    /**
     * 注册单个 HTTP 客户端
     */
    private void registerHttpClient(BeanDefinitionRegistry registry, Class<?> clientClass) {
        if (!clientClass.isAnnotationPresent(HttpClient.class)) {
            return;
        }
        
        String beanName = generateBeanName(clientClass);
        
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(HttpClientFactoryBean.class);
        beanDefinition.getPropertyValues().add("clientInterface", clientClass);
        beanDefinition.getPropertyValues().add("properties", properties);
        beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        
        registry.registerBeanDefinition(beanName, beanDefinition);
    }
    
    /**
     * 生成 Bean 名称
     */
    private String generateBeanName(Class<?> clientClass) {
        String simpleName = clientClass.getSimpleName();
        return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
    }
}

HTTP 客户端工厂 Bean

package io.github.nemoob.httpclient.spring.autoconfigure;

import io.github.nemoob.httpclient.HttpClientFactory;
import io.github.nemoob.httpclient.annotation.HttpClient;
import org.springframework.beans.factory.FactoryBean;

/**
 * HTTP 客户端工厂 Bean
 * 负责创建 HTTP 客户端代理对象
 */
public class HttpClientFactoryBean<T> implements FactoryBean<T> {
    
    private Class<T> clientInterface;
    private AtlasHttpClientProperties properties;
    
    public HttpClientFactoryBean() {}
    
    public HttpClientFactoryBean(Class<T> clientInterface, AtlasHttpClientProperties properties) {
        this.clientInterface = clientInterface;
        this.properties = properties;
    }
    
    @Override
    public T getObject() throws Exception {
        return createHttpClient();
    }
    
    @Override
    public Class<?> getObjectType() {
        return clientInterface;
    }
    
    @Override
    public boolean isSingleton() {
        return true;
    }
    
    /**
     * 创建 HTTP 客户端
     */
    private T createHttpClient() {
        // 应用配置文件中的设置
        applyConfiguration();
        
        // 创建客户端
        return HttpClientFactory.create(clientInterface);
    }
    
    /**
     * 应用配置
     */
    private void applyConfiguration() {
        if (clientInterface == null || properties == null) {
            return;
        }
        
        HttpClient annotation = clientInterface.getAnnotation(HttpClient.class);
        if (annotation == null) {
            return;
        }
        
        // 获取客户端名称
        String clientName = getClientName();
        
        // 应用特定客户端配置
        AtlasHttpClientProperties.ClientConfig clientConfig = properties.getClients().get(clientName);
        if (clientConfig != null) {
            // 这里可以覆盖注解中的配置
            // 实际实现中需要修改 HttpClientFactory 支持运行时配置
        }
    }
    
    /**
     * 获取客户端名称
     */
    private String getClientName() {
        String simpleName = clientInterface.getSimpleName();
        return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
    }
    
    // Getter and Setter methods
    public Class<T> getClientInterface() {
        return clientInterface;
    }
    
    public void setClientInterface(Class<T> clientInterface) {
        this.clientInterface = clientInterface;
    }
    
    public AtlasHttpClientProperties getProperties() {
        return properties;
    }
    
    public void setProperties(AtlasHttpClientProperties properties) {
        this.properties = properties;
    }
}

启用注解实现

package io.github.nemoob.httpclient.spring.annotation;

import io.github.nemoob.httpclient.spring.autoconfigure.AtlasHttpClientAutoConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * 启用 Atlas HTTP Client 注解
 * 用于手动启用 HTTP 客户端功能并指定扫描包
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AtlasHttpClientAutoConfiguration.class, HttpClientScannerRegistrar.class})
public @interface EnableAtlasHttpClient {
    
    /**
     * 扫描的基础包
     */
    String[] basePackages() default {};
    
    /**
     * 扫描的基础包类
     */
    Class<?>[] basePackageClasses() default {};
}

包扫描注册器

package io.github.nemoob.httpclient.spring.annotation;

import io.github.nemoob.httpclient.annotation.HttpClient;
import io.github.nemoob.httpclient.spring.autoconfigure.HttpClientFactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * HTTP 客户端扫描注册器
 * 负责扫描指定包下的 @HttpClient 接口并注册为 Bean
 */
public class HttpClientScannerRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
            importingClassMetadata.getAnnotationAttributes(EnableAtlasHttpClient.class.getName())
        );
        
        if (attributes == null) {
            return;
        }
        
        // 获取扫描包
        Set<String> basePackages = getBasePackages(importingClassMetadata, attributes);
        
        // 创建扫描器
        HttpClientScanner scanner = new HttpClientScanner(registry);
        
        // 执行扫描
        scanner.scan(basePackages.toArray(new String[0]));
    }
    
    /**
     * 获取基础扫描包
     */
    private Set<String> getBasePackages(AnnotationMetadata importingClassMetadata, AnnotationAttributes attributes) {
        Set<String> basePackages = new LinkedHashSet<>();
        
        // 从注解属性获取
        basePackages.addAll(Arrays.asList(attributes.getStringArray("basePackages")));
        
        for (Class<?> clazz : attributes.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        
        // 如果没有指定,使用导入类的包
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        
        return basePackages;
    }
    
    /**
     * HTTP 客户端扫描器
     */
    private static class HttpClientScanner extends ClassPathBeanDefinitionScanner {
        
        public HttpClientScanner(BeanDefinitionRegistry registry) {
            super(registry, false);
            addIncludeFilter(new AnnotationTypeFilter(HttpClient.class));
        }
        
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            AnnotationMetadata metadata = beanDefinition.getMetadata();
            return metadata.isInterface() && metadata.hasAnnotation(HttpClient.class.getName());
        }
        
        @Override
        protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
            
            if (beanDefinitions.isEmpty()) {
                logger.warn("No HTTP client interfaces found in packages: " + Arrays.toString(basePackages));
            } else {
                processBeanDefinitions(beanDefinitions);
            }
            
            return beanDefinitions;
        }
        
        /**
         * 处理 Bean 定义
         */
        private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
            for (BeanDefinitionHolder holder : beanDefinitions) {
                GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
                String beanClassName = definition.getBeanClassName();
                
                // 修改 Bean 定义
                definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
                definition.setBeanClass(HttpClientFactoryBean.class);
                definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
                
                logger.info("Registered HTTP client: " + beanClassName);
            }
        }
    }
}

配置文件支持

META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
io.github.nemoob.httpclient.spring.autoconfigure.AtlasHttpClientAutoConfiguration

META-INF/spring-configuration-metadata.json

{
  "groups": [
    {
      "name": "atlas.httpclient",
      "type": "io.github.nemoob.httpclient.spring.autoconfigure.AtlasHttpClientProperties",
      "sourceType": "io.github.nemoob.httpclient.spring.autoconfigure.AtlasHttpClientProperties"
    }
  ],
  "properties": [
    {
      "name": "atlas.httpclient.enabled",
      "type": "java.lang.Boolean",
      "description": "Whether to enable Atlas HTTP Client.",
      "defaultValue": true
    },
    {
      "name": "atlas.httpclient.default-connect-timeout",
      "type": "java.lang.Integer",
      "description": "Default connect timeout in milliseconds.",
      "defaultValue": 5000
    },
    {
      "name": "atlas.httpclient.default-read-timeout",
      "type": "java.lang.Integer",
      "description": "Default read timeout in milliseconds.",
      "defaultValue": 10000
    },
    {
      "name": "atlas.httpclient.logging-enabled",
      "type": "java.lang.Boolean",
      "description": "Whether to enable logging interceptor.",
      "defaultValue": true
    },
    {
      "name": "atlas.httpclient.metrics-enabled",
      "type": "java.lang.Boolean",
      "description": "Whether to enable metrics interceptor.",
      "defaultValue": true
    }
  ]
}

使用示例

1. 添加依赖

<dependency>
    <groupId>io.github.nemoob</groupId>
    <artifactId>atlas-httpclient-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

2. 配置文件

# application.yml
atlas:
  httpclient:
    enabled: true
    default-connect-timeout: 5000
    default-read-timeout: 10000
    logging-enabled: true
    metrics-enabled: true
    
    # 拦截器配置
    interceptors:
      logging:
        log-headers: true
        log-body: true
        max-body-length: 1024
      retry:
        enabled: true
        max-retries: 3
        retry-delay: 1000
    
    # 客户端特定配置
    clients:
      userService:
        base-url: https://api.example.com
        connect-timeout: 3000
        read-timeout: 8000
        async: false

3. 启用 HTTP 客户端

@SpringBootApplication
@EnableAtlasHttpClient(basePackages = "com.example.client")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. 定义 HTTP 客户端接口

package com.example.client;

import io.github.nemoob.httpclient.annotation.*;

@HttpClient("https://api.example.com")
public interface UserService {
    
    @GET("/users")
    List<User> getUsers();
    
    @GET("/users/{id}")
    User getUser(@Path("id") Long id);
    
    @POST("/users")
    User createUser(@Body User user);
}

5. 在 Spring Bean 中使用

@Service
public class UserApplicationService {
    
    @Autowired
    private UserService userService;
    
    public List<User> getAllUsers() {
        return userService.getUsers();
    }
    
    public User getUserById(Long id) {
        return userService.getUser(id);
    }
    
    public User createUser(User user) {
        return userService.createUser(user);
    }
}

6. 自定义拦截器

@Component
public class CustomAuthInterceptor extends AbstractRequestInterceptor {
    
    @Value("${app.auth.token}")
    private String authToken;
    
    @Override
    public void preHandle(RequestContext context) throws Exception {
        Request request = context.getRequest();
        request.getHeaders().put("Authorization", "Bearer " + authToken);
    }
}

监控集成

Actuator 端点

@Component
@ConditionalOnClass(Endpoint.class)
public class HttpClientMetricsEndpoint {
    
    @Autowired(required = false)
    private MetricsInterceptor metricsInterceptor;
    
    @ReadOperation
    public Map<String, Object> metrics() {
        Map<String, Object> result = new HashMap<>();
        
        if (metricsInterceptor != null) {
            MetricsInterceptor.MetricsReport report = metricsInterceptor.getMetrics();
            result.put("httpClientMetrics", report.getAllMetrics());
        }
        
        return result;
    }
}

Health Indicator

@Component
@ConditionalOnClass(HealthIndicator.class)
public class HttpClientHealthIndicator implements HealthIndicator {
    
    @Autowired(required = false)
    private MetricsInterceptor metricsInterceptor;
    
    @Override
    public Health health() {
        if (metricsInterceptor == null) {
            return Health.up().withDetail("status", "No metrics available").build();
        }
        
        MetricsInterceptor.MetricsReport report = metricsInterceptor.getMetrics();
        
        // 检查错误率
        boolean hasHighErrorRate = report.getAllMetrics().values().stream()
            .anyMatch(metric -> metric.errorRate > 0.1); // 10% 错误率阈值
        
        if (hasHighErrorRate) {
            return Health.down().withDetail("reason", "High error rate detected").build();
        }
        
        return Health.up().withDetail("totalClients", report.getAllMetrics().size()).build();
    }
}

测试支持

测试配置

@TestConfiguration
public class HttpClientTestConfiguration {
    
    @Bean
    @Primary
    public UserService mockUserService() {
        return Mockito.mock(UserService.class);
    }
}

集成测试

@SpringBootTest
@Import(HttpClientTestConfiguration.class)
class UserServiceIntegrationTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    void testGetUsers() {
        // 配置 Mock 行为
        when(userService.getUsers()).thenReturn(Arrays.asList(new User("John", 30)));
        
        // 执行测试
        List<User> users = userService.getUsers();
        
        // 验证结果
        assertThat(users).hasSize(1);
        assertThat(users.get(0).getName()).isEqualTo("John");
    }
}

最佳实践

1. 配置管理

  • 使用配置文件管理不同环境的设置
  • 敏感信息使用环境变量或配置中心
  • 提供合理的默认值

2. 错误处理

  • 配置全局异常处理器
  • 提供降级机制
  • 记录详细的错误日志

3. 性能优化

  • 合理配置连接池
  • 启用请求缓存
  • 监控性能指标

4. 安全考虑

  • 不在日志中记录敏感信息
  • 使用 HTTPS
  • 实现请求签名验证

总结

本文详细介绍了 Atlas HTTP Client 框架的 Spring Boot 集成实现。关键要点包括:

  1. 自动配置:基于条件注解的自动配置机制
  2. 配置管理:完整的配置属性绑定
  3. Bean 管理:自动扫描和注册 HTTP 客户端
  4. 拦截器集成:与 Spring 容器的拦截器集成
  5. 监控支持:与 Spring Boot Actuator 集成
  6. 测试支持:便于单元测试和集成测试

通过 Spring Boot 集成,我们的框架可以无缝融入 Spring 生态,为用户提供更好的开发体验。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]