Spring Framework 6.0引入了声明式 HTTP 客户端。我们应该在 Boot 中添加一些自动配置,例如提供HttpServiceProxyFactory
。
我们甚至可以考虑使用注释来注释 HTTP 接口,然后将其直接注入到消费者中(如@FeignClient
)。
Spring Framework 6.0引入了声明式 HTTP 客户端。我们应该在 Boot 中添加一些自动配置,例如提供HttpServiceProxyFactory
。
我们甚至可以考虑使用注释来注释 HTTP 接口,然后将其直接注入到消费者中(如@FeignClient
)。
我觉得这个可以自己尝试注入IOC 例如我这样做
public class HttpServiceFactory implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private final HttpServiceProxyFactory proxyFactory;
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
public HttpServiceFactory() {
WebClient client = WebClient.builder().build();
this.proxyFactory = HttpServiceProxyFactory.builder(new WebClientAdapter(client)).build();
}
@Override
public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata,
@NonNull BeanDefinitionRegistry registry) {
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
Set<Class<?>> typesAnnotatedClass = findByAnnotationType(HttpExchange.class, resourceLoader,
packages.toArray(String[]::new));
for (Class<?> exchangeClass : typesAnnotatedClass) {
BeanName name = AnnotationUtils.getAnnotation(exchangeClass, BeanName.class);
String beanName = name != null ? name.value()
: CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, exchangeClass.getSimpleName());
registry.registerBeanDefinition(beanName, getBeanDefinition(exchangeClass));
}
}
private <T> BeanDefinition getBeanDefinition(Class<T> exchangeClass) {
return new RootBeanDefinition(exchangeClass, () -> proxyFactory.createClient(exchangeClass));
}
@Override
public void setResourceLoader(@NonNull ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public Set<Class<?>> findByAnnotationType(Class<? extends Annotation> annotationClass,
ResourceLoader resourceLoader, String... packages) {
Assert.notNull(annotationClass, "annotation not null");
Set<Class<?>> classSet = new HashSet<>();
if (packages == null || packages.length == 0) {
return classSet;
}
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
try {
for (String packageStr : packages) {
packageStr = packageStr.replace(".", "/");
Resource[] resources = resolver.getResources("classpath*:" + packageStr + "/**/*.class");
for (Resource resource : resources) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
String className = metadataReader.getClassMetadata().getClassName();
Class<?> clazz = Class.forName(className);
if (AnnotationUtils.findAnnotation(clazz, annotationClass) != null) {
classSet.add(clazz);
}
}
}
}
catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
return classSet;
}
}
/**
* Just used to set the BeanName
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeanName {
String value();
}
你对此有何看法
额外的注释会带来什么呢@HttpMapping
?
底层客户端可能需要配置不同的基本 URL、编解码器等。这意味着,@HttpExchange
如果基于相同的底层客户端设置,检测带注释的接口和为它们声明 bean 可能会变得不灵活。
给定一个HttpServiceProxyFactory
,创建代理很简单,因此自动化创建代理不会带来太多好处。不过,我在想,Boot 应该可以创建并自动配置一个HttpServiceProxyFactory
实例,然后将其与不同的客户端设置相结合。
目前HttpServiceProxyFactory
采用HttpClientAdapter
构造函数,但我们可以进行更改以允许将其传递给重载createClient(Class<?>, HttpClientAdapter)
方法。因此,您可以HttpServiceProxyFactory
在任何地方注入相同的内容,并将其与默认客户端设置一起使用(基于WebClientCustomizer
和WebClient.Builder
),或者也可以选择注入WebClient.Builder
并使用与默认设置不同的客户端设置。
我在这里玩了一下。
自动配置提供 类型的 bean HttpServiceProxyFactory
,然后用户可以使用该 bean 从接口 创建代理。客户端的基本 URL 可通过 进行设置,@HttpExchange
而无需在工厂中进行配置。
谢谢,这有助于我推进我的思考过程。我现在明白有必要更正式地将HttpServiceProxyFactory
与底层客户端分开。
我在本地进行了一个实验,每次调用时都HttpServiceProxyFactory
需要HttpClientAdapter
传入createClient
。另外,还有一个使用和WebClientServiceProxyFactory
创建的,并且只使用代理接口公开的。HttpServiceProxyFactory
WebClient
createClient
然后,Boot 自动配置可以声明单个HttpServiceProxyFactory
bean,应用程序将创建任意数量的WebClientServiceProxyFactory
bean,每个 bean 委托给相同的beanHttpServiceProxyFactory
并WebClient
为特定的远程进行配置。
经过几次不同的实验,我认为尝试HttpServiceProxyFactory
为多个客户端实例设置一个实例会带来额外的复杂性,而收获甚微。最容易理解的模型仍然是一对一HttpServiceProxyFactory
的模型。即使没有 Boot 的任何帮助,它也相当简单:
@Bean
HttpServiceProxyFactory httpServiceProxyFactory1(WebClient.Builder clientBuilder) {
WebClient client = clientBuilder.baseUrl("http://host1.com").build();
return new HttpServiceProxyFactory(new WebClientAdapter(client));
}
@Bean
HttpServiceProxyFactory httpServiceProxyFactory2(WebClient.Builder clientBuilder) {
WebClient client = clientBuilder.baseUrl("http://host2.com").build();
return new HttpServiceProxyFactory(new WebClientAdapter(client));
}
一些额外的快捷方式WebClientAdapter
可以使这成为一行:
@Bean
HttpServiceProxyFactory httpServiceProxyFactory1(WebClient.Builder clientBuilder) {
return WebClientAdapter.createProxyFactory(clientBuilder.baseUrl("http://host1.com"));
}
@Bean
HttpServiceProxyFactory httpServiceProxyFactory2(WebClient.Builder clientBuilder) {
return WebClientAdapter.createProxyFactory(clientBuilder.baseUrl("http://host2.com"));
}
如果我们将上述内容作为预期配置,那么我认为启动任何 Boot 自动配置都不是必需的,尽管可能仍会出现一些想法。也许,在属性中指定 baseUrl,这将允许 Boot 创建上述 bean?
谢谢,罗森。
也许,在属性中指定 baseUrl,这将允许 Boot 创建上述 bean?
我们尚不支持在 Boot 的任何地方指定多个属性值来自动配置多个 bean。这是我们想要做的事情,但这是一个复杂且范围广泛的话题。例如,https://github.com/spring-projects/spring-boot/issues/15732正在跟踪自动配置的多个数据源。
今天讨论过这个问题后,我们认为 Boot 目前没有什么可做的。如果自动配置多个 bean 的情况发生变化,我们可以重新讨论这个问题。
请注意,现在有https://github.com/spring-projects/spring-framework/issues/29296,它可能会为我们提供一个更好的模型来处理HttpServiceProxyFactory
不同远程的多个实例。
由于 spring-projects/spring-framework#29296 中的更改而重新开放,Spring Boot 可以为HttpServiceProxyFactory.Builder
上下文提供预配置,以便开发人员可以从中构建自己的客户端。
关于这个关于 HTTP 接口的精彩教程https://softice.dev/posts/introduction_to_spring_framework_6_http_interfaces/,
我不太明白。为什么开发人员需要手动编写一个@Bean
返回代理 bean(实现接口)的方法,尤其是在我们使用 spring boot 的情况下?我记得使用@FeignClient
,我不必为它定义任何代理 bean,所以我假设 spring boot 可以为我们完成。
另外为什么要使用 Http 接口@FeignClient
?
我们甚至可以考虑使用注释来注释 HTTP 接口,然后将其直接注入到消费者中(如
@FeignClient
)。
我认为我们需要一个像@EnableFeignClients
而不是 这样的注释@FeignClient
。
我们已经可以通过 知道一个接口是否是http客户端@HttpExchange
,我们需要一个注解来扫描接口并注册bean(如@EnableFeignClients
)。
这是我的解决方法。
我已经制作了以下方法的原型,将样板文件减少到最低限度:
@HttpClient
注释将接口标记为 http 客户端,并添加选项来设置WebClient
要使用的 bean 名称。
@HttpClient("todo-client")
public interface TodoClient {
@GetExchange("/todos")
List<Todo> get();
}
该注释由一个注册器实现处理,ImportBeanDefinitionRegistrar
它为每个 http 客户端注册 bean 定义,HttpServiceProxyFactory
并WebClientAdapter
使用注释中的名称为 WebClient 创建一个适配器。
WebClient
从环境创建实例
考虑到许多 Web 客户端相对简单,有一组可以使用简单属性设置的通用属性:url、basic auth、timeouts 等。
鉴于此,可以选择通过 yaml/properties 创建 WebClient,如下所示:
http.clients:
todo-client:
url: https://jsonplaceholder.typicode.com
bar:
url: http://foo/bar
如果您认为这有意义,我可以准备一个 PR,或者如果说这还为时过早,我可以将其作为一个单独的项目发布,一旦 Spring Boot 内置了类似的功能,它就会被弃用。
更新:
该库可在 Maven Central 上找到:https://github.com/maciejwalkowiak/spring-boot-http-clients