一,提前准备
提前准备几个接口,也可以自己提供,我这里直接在网上找了几个公开的api。本文提供的所有代码都已经公开,请参考https://gitee.com/zengmoss/exchange
二,新建项目
然后新建一个springboot的项目,这里只需要spring-web模块就行了,完整的pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>exchange</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>exchange</name>
<description>springboot3 内置HTTP</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2.0版本不仅支持WebClient,也支持RestClient和RestTemplate,本文使用RestClient,更多可以参考spring官网关于http interfacehttps://docs.spring.io/spring-framework/reference/integration/rest-clients.html
三,建立对应接口
public interface VvhanClient {
@GetExchange("/api/joke")
String apiJoke();
}
四,配置对应的域名
在配置文件application.yml配置好域名,这里仅仅展示,就不区分环境了,生产使用就在对应环境里面配置
client:
url:
vvhan: https://api.vvhan.com/
五,配置http interface
@Configuration
public class HttpExchangeConfig {
@Bean
public VvhanClient vvhanClient(RestClient.Builder restClientBuilder, @Value("${client.url.vvhan}") String url){
RestClient restClient = restClientBuilder.baseUrl(url).build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(VvhanClient.class);
}
}
六,所有事情已经处理好
现在,可以把接口直接注入使用了。
@SpringBootTest
class ExchangeApplicationTests {
@Autowired
private VvhanClient vvhanClient;
@Test
void apiJoke() {
System.out.println(vvhanClient.apiJoke());
}
}
看下运行结果:

七,再对接其它系统
操作参考前面的步骤,这里不再演示,嫌麻烦的人直接去gitee下载源码,最后再配置http interface,就可以注入接口使用了。再看看配置文件:
@Configuration
public class HttpExchangeConfig {
@Bean
public VvhanClient vvhanClient(RestClient.Builder restClientBuilder, @Value("${client.url.vvhan}") String url){
RestClient restClient = restClientBuilder.baseUrl(url).build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(VvhanClient.class);
}
@Bean
public XyGengClient xyGengClient(RestClient.Builder restClientBuilder, @Value("${client.url.xygeng}") String url){
RestClient restClient = restClientBuilder.baseUrl(url).build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(XyGengClient.class);
}
}
这里的restClientBuilder是原型的,不用多虑单例覆盖问题。从springboot自动配置RestClientAutoConfiguration里面可以很清楚地看到。
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
RestClient.Builder builder = RestClient.builder()
.requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS));
return restClientBuilderConfigurer.configure(builder);
}
官方的指导到此结束,一般项目对接个10个系统就差不多了,所以写下配置文件也不复杂,这里的所有代码可以从分支看到exchange: springboot3 内置http客户端 取代feign - Gitee.com
八,再对比下feign
最大的区别就是feign把域名放在@FeignClient里,然后通过@EnableFeignClients发现,自动注册成代理,而内置的http,则需要在配置文件里面手动注册成代码。
九,模仿下feign
我们发现,想要自动注册成代理也是很容易的事情,所以模仿下吧。
我们先写一个factoryBean,这样spring实例化的时候会调用getObject(),这样才有了操作空间。
@Getter
@Setter
public class HttpExchangeFactoryBean<T> implements FactoryBean<T> {
private Class<T> mapperInterface;
private String baseUrl;
@Autowired
private RestClient.Builder restClientBuilder;
public HttpExchangeFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() {
RestClient restClient = restClientBuilder.baseUrl(baseUrl).build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(mapperInterface);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
给一个自定义注解承担域名吧,类似@FeignClient,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ExchangeClient {
/**
* 声明式接口的基础地址
* baseUrl
* @return
*/
String value();
}
给一个发现注解的入口吧,类似@EnableFeignClients,
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ExchangeClientsRegistrar.class)
public @interface EnableExchangeClients {
}
这里引入了一个配置文件,ExchangeClientsRegistrar,就是在这里实现自动注册。
public class ExchangeClientsRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathExchangeScanner scanner = new ClassPathExchangeScanner(registry);
//扫描带@ExchangeClient的类
scanner.addIncludeFilter(new AnnotationTypeFilter(ExchangeClient.class));
scanner.scan(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
@Slf4j
private static class ClassPathExchangeScanner extends ClassPathBeanDefinitionScanner{
public ClassPathExchangeScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (!beanDefinitions.isEmpty()) {
for(BeanDefinitionHolder holder : beanDefinitions){
AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
//把类名注入
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//指定FactoryBean生成
definition.setBeanClass(HttpExchangeFactoryBean.class);
if (definition instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) definition;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(ExchangeClient.class.getCanonicalName());
String baseUrl = (String)attributes.get("value");
//把域名注入 这里的spel不用解释好 spring会自动解释
definition.getPropertyValues().add("baseUrl", baseUrl);
}
}
}
return beanDefinitions;
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
}
然后手动注册代理的文件HttpExchangeConfig 可以删除了。在接口里加上域名,
@ExchangeClient("${client.url.vvhan}")
public interface VvhanClient {
@GetExchange("/api/joke")
String apiJoke();
}
在入口加入发现,
@SpringBootApplication
@EnableExchangeClients
public class ExchangeApplication {
public static void main(String[] args) {
SpringApplication.run(ExchangeApplication.class, args);
}
}
这样,就跟feign的用法一模一样了,就是注解的名称改了改,看起来,是时候跟feign说88了。

2889

被折叠的 条评论
为什么被折叠?



