New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support declarative HTTP clients #31337
Comments
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();
}
|
|
What would an additional annotation bring beyond |
|
The underlying client may need to be configured with different base URL, codecs, etc. That means detecting Given an Currently |
|
I played around with it here. The auto-configuration supplies a bean of type |
|
Thanks, this helps me to move my thought process forward. I see now it is necessary to separate more formally the I've an experiment locally where The Boot auto-config could then declare a single |
|
After a few different experiments, I think trying to have one @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));
}A couple of extra shortcuts on @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"));
}If we settle on the above as the expected configuration, then I think it's not essential to have any Boot auto-config to start, although some ideas may still come along. Perhaps, specifying baseUrl's in properties, which would allow Boot to create the above beans? |
|
Thanks, Rossen.
We don't yet support specifying multiple property values to auto-configure multiple beans anywhere in Boot. It's something that we'd like to do, but it's a complex and wide-ranging topic. #15732 is tracking auto-configured multiple DataSources, for example. Having discussed this today, we don't think there's anything to do in Boot at this time. We can revisit this if the picture changes for auto-configuring multiple beans. |
|
Note that there is now spring-projects/spring-framework#29296, which will likely give us a better model for dealing with multiple |
|
Reopening because of changes made in spring-projects/spring-framework#29296 |
|
With regard to this nice tutorial on HTTP Interface https://softice.dev/posts/introduction_to_spring_framework_6_http_interfaces/, I don't quite understand. Why do developer need to manually write a Also why would one use Http Interface over |
I think we need an annotation like We can already know whether an interface is a http client through Here‘s my workaround. |
|
I think people have gotten used to using the Feign client approach. Here you can find very similar aproach: Exchange client It is really simple to use. |
|
I have prototyped following approach, that reduces the boilerplate to minimum:
|
Spring Framework 6.0 introduces declarative HTTP clients. We should add some auto-configuration in Boot which supplies for example the
HttpServiceProxyFactory.We could even think about an annotation with which the HTTP interface can be annotated and then directly injected into a consumer (like
@FeignClient).The text was updated successfully, but these errors were encountered: