毛线颜色拼图
70.92MB · 2025-12-04
在前面的文章中,我们已经实现了一个功能完整的 HTTP 客户端框架。但是在实际的企业级应用中,大部分 Java 项目都使用 Spring Boot 作为基础框架。为了让我们的 Atlas HTTP Client 更好地融入 Spring 生态,我们需要提供 Spring Boot 的集成支持。
Spring Boot 集成的核心价值在于:
本文将详细介绍如何实现一个完整的 Spring Boot Starter。
Spring Boot Starter 是一个依赖描述符,它包含了一组相关的依赖和自动配置类。当用户添加 Starter 依赖时,Spring Boot 会自动配置相关的组件。
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 后处理器负责扫描和创建 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);
}
}
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);
}
}
}
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
io.github.nemoob.httpclient.spring.autoconfigure.AtlasHttpClientAutoConfiguration
{
"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
}
]
}
<dependency>
<groupId>io.github.nemoob</groupId>
<artifactId>atlas-httpclient-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
# 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
@SpringBootApplication
@EnableAtlasHttpClient(basePackages = "com.example.client")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
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);
}
@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);
}
}
@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);
}
}
@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;
}
}
@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");
}
}
本文详细介绍了 Atlas HTTP Client 框架的 Spring Boot 集成实现。关键要点包括:
通过 Spring Boot 集成,我们的框架可以无缝融入 Spring 生态,为用户提供更好的开发体验。