介绍
随着划时代版本Spring Framework 6、Spring Boot 3 的发布,Spring 团队自建了一套声明式 Http 客户端:@HttpExchange,目标直指 OpenFeign。
Spring 6 带来了一个新的特性——HTTP Interface。这个新特性,可以让开发者将 HTTP 服务,定义成一个包含特定注解标记的方法的 Java 接口,然后通过对接口方法的调用,完成 HTTP 请求。看起来很像使用 Feign 来完成远程服务调用。
自定义 starter
此实现参考 mybatis 的实现方式, 可以添加一些处理以实现 feign client 微服务架构的命名式路由
依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
自定义 FactoryBean
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ExchangeFactoryBean<T> implements FactoryBean<T> {
private boolean async;
private Class<?> exchangeInterface;
private Class<?> exchangeInterface;
@Override
public T getObject() throws Exception {
return async ? createAsyncClient() : createSyncClient();
}
@Override
public Class<?> getObjectType() {
return exchangeInterface;
}
@SuppressWarnings("unchecked")
public T createSyncClient() {
RestClient restClient = RestClient.builder().build();
RestClientAdapter restClientAdapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restClientAdapter).build();
return (T) factory.createClient(exchangeInterface);
}
@SuppressWarnings("unchecked")
public T createAsyncClient() {
WebClient webClient = WebClient.builder().build();
WebClientAdapter webClientAdapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(webClientAdapter).build();
return (T) factory.createClient(exchangeInterface);
}
}
自定义扫描逻辑
@Setter
public class ExchangeScannerConfigurer implements BeanDefinitionRegistryPostProcessor {
private boolean async;
private String basePackage;
private Class<? extends Annotation> annotationClass;
private Class<? extends ExchangeFactoryBean<?>> exchangeFactoryBeanClass;
@Override
public void postProcessBeanDefinitionRegistry(@NonNull BeanDefinitionRegistry registry) throws BeansException {
ClassPathExchangeScanner scanner = new ClassPathExchangeScanner(registry);
scanner.setAnnotationClass(this.annotationClass);
scanner.setExchangeFactoryBeanClass(this.exchangeFactoryBeanClass);
scanner.setAsync(this.async);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
@Slf4j
@Setter
@SuppressWarnings("rawtypes")
public class ClassPathExchangeScanner extends ClassPathBeanDefinitionScanner {
private Class<? extends Annotation> annotationClass;
private Class<? extends ExchangeFactoryBean> exchangeFactoryBeanClass = ExchangeFactoryBean.class;
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
public ClassPathExchangeScanner(BeanDefinitionRegistry registry) {
super(registry);
}
public void setExchangeFactoryBeanClass(Class<? extends ExchangeFactoryBean> exchangeFactoryBeanClass) {
this.exchangeFactoryBeanClass = exchangeFactoryBeanClass == null ? ExchangeFactoryBean.class : exchangeFactoryBeanClass;
}
public void registerFilters() {
if (this.annotationClass != null) {
this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
}
this.addIncludeFilter(new AnnotationTypeFilter(HttpExchange.class));
this.addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
@Override
protected boolean isCandidateComponent(@NonNull AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
@Override
protected @NonNull Set<BeanDefinitionHolder> doScan(@NonNull String... basePackages) {
Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
for (BeanDefinitionHolder holder : holders) {
ScannedGenericBeanDefinition definition = (ScannedGenericBeanDefinition) holder.getBeanDefinition();
try {
Class<?> beanClass = Class.forName(definition.getBeanClassName());
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass);
definition.getPropertyValues().add("exchangeInterface", beanClass);
} catch (ClassNotFoundException ignore) {
// ignore
}
definition.setBeanClass(this.exchangeFactoryBeanClass);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
return holders;
}
}
注册 Bean 的逻辑
public class ExchangeScannerRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) {
AnnotationAttributes exchangeScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ExchangeScan.class.getName()));
if (exchangeScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, exchangeScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ExchangeScannerConfigurer.class);
Class<? extends ExchangeFactoryBean<?>> exchangeFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!ExchangeFactoryBean.class.equals(exchangeFactoryBeanClass)) {
builder.addPropertyValue("exchangeFactoryBeanClass", exchangeFactoryBeanClass);
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).toList());
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).toList());
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
builder.addPropertyValue("annotationClass", HttpExchange.class);
builder.addPropertyValue("async", environment.getProperty("exchange.async", Boolean.class, true));
// for spring-native
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + ExchangeScannerRegistrar.class.getSimpleName() + "#" + index;
}
private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
return ClassUtils.getPackageName(importingClassMetadata.getClassName());
}
@Override
public void setEnvironment(@NonNull Environment environment) {
this.environment = environment;
}
// 这个用于类似 @MapperScans 的注解 @ExchangeScans
public static class RepeatingRegistrar extends ExchangeScannerRegistrar {
@Override
public void registerBeanDefinitions(@NonNull AnnotationMetadata metadata, @NonNull BeanDefinitionRegistry registry) {
AnnotationAttributes exchangeScanAttrs = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(ExchangeScans.class.getName()));
if (exchangeScanAttrs == null) {
return;
}
AnnotationAttributes[] annotations = exchangeScanAttrs.getAnnotationArray("value");
for (int i = 0; i < annotations.length; i++) {
registerBeanDefinitions(metadata, annotations[i], registry, generateBaseBeanName(metadata, i));
}
}
}
}
相关注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ExchangeScannerRegistrar.class)
public @interface ExchangeScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends ExchangeFactoryBean> factoryBean() default ExchangeFactoryBean.class;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ExchangeScannerRegistrar.RepeatingRegistrar.class)
public @interface ExchangeScans {
ExchangeScan[] value();
}