这几天在开发一个内部SDK的过程中遇到一个问题:SDK需要请求很多的HTTP接口,如果在我们的逻辑代码中直接进行HTTP接口的请求会使得代码的耦合性非常高,同时也需要写大量的代码来进行HTTP请求的配置、响应的处理。这促使我去寻找更加通用的工具来满足需求。
因为我们的项目中一直在使用Spring Cloud,自然而然地我想到了Feign。Feign(https://github.com/OpenFeign/feign)是一个基于注解来生成HTTP请求,并且能自动处理请求返回的工具。
1 | interface GitHub { |
正如上面的例子所示,我们只需要在接口上加上相应的注解,然后通过调用Feign生成的代理类,我们就可以得到接口返回的数据。中间一系列的请求过程都是由Feign自动完成的。可以看到,Feign能够大幅度地简化我们的代码。
美中不足的是,原生的Feign是不支持Spring的,这意味着我们无法享受到Spring依赖注入的便利。幸运的是Spring Cloud提供了spring-cloud-starter-openfeign
组件,不幸的是spring-cloud-starter-openfeign
与Spring Cloud以及Spring Boot结合地比较深,这意味着只有使用Spring Cloud才能使用spring-cloud-starter-openfeign
组件来请求服务。
在我们的SDK中只需要简单地对http接口进行请求,不需要也不能够依赖服务注册提供的服务地址。于是我们需要对spring-cloud-starter-openfeign
做一个简化,剥离出其中有用的部分。
本文侧重分析如何在Spring中实现动态注册bean。
动态注册bean
在Spring中动态注册bean的通用做法是实现ImportBeanDefinitionRegistrar
接口。
ImportBeanDefinitionRegistrar
需要配合@Import
注解,@Import
注解导入实现了ImportBeanDefinitionRegistrar
接口的类。
假设我们定义了如下的接口:
1 | "http://127.0.0.1") (url = |
目标是调用test1()
方法的时候请求GET http://127.0.0.1/index
,调用test2()
方法的时候请求POST http://127.0.0.1/post
。为了实现这个目标,核心是实现ImportBeanDefinitionRegistrar
。
主要的思路是:利用ClassPathScanningCandidateComponentProvider
获取标注了HttpUtil
注解的接口,使用BeanDefinitionReaderUtils
将一个实现了FactoryBean
的工厂方法的BeanDefinition
注册到容器中。获取Bean
的时候会调用工厂方法的getObject()
方法返回一个代理类。ImportBeanDefinitionRegistrar
实现类的代码如下:
1 | public class HttpRequestRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { |
有了ImportBeanDefinitionRegistrar
的实现类,如何让这个实现类被Spring发现呢?我们需要编写一个注解,并在其中使用@Import
导入前面的HttpRequestRegistrar
。
1 | (RetentionPolicy.RUNTIME) |
接着将@EnableHttpUtil
添加到@Configuration
注解下,这样Spring在启动过程中就会执行HttpRequestRegistrar
注册动态bean的定义。
1 |
|
我们在HttpRequestRegistrar
中注册了HttpFactoryBean
的定义。HttpFactoryBean
实现了FactoryBean
,是一个工厂Bean,即HttpFactoryBean
的目的是创建一个实际的bean。代码如下:
1 | public class HttpFactoryBean implements FactoryBean<Object>, InitializingBean { |
简单起见,我们在代理类中仅仅返回了http请求的url,省略了http请求的过程,对于说明动态注册bean的方法问题不大。
1 | public class DemoHttpHandler implements HttpHandler { |
有了前面的铺垫,我们就可以来使用我们的Http请求工具了。
首先新建一个接口来定义http接口:
1 | "http://127.0.0.1") (url = |
然后我们就可以直接通过这个接口来进行http请求了:
1 | public class Main { |
结果输出:
1 | GET http://127.0.0.1/index |
可以看到我们完成了根据接口定义来动态生成一个bean的目的。
完整代码参考:https://github.com/wangqifox/spring-demo/tree/master/m3
spring与feign的整合
有了前面对动态注册bean的了解,我们在脑海里对spring与feign的整合已经有了大体的轮廓了。我们可以参考Spring Cloud对feign的整合来裁剪我们需要的代码。
完整代码参考:https://github.com/wangqifox/spring-feign
https://zhuanlan.zhihu.com/p/30070328
https://zhuanlan.zhihu.com/p/30123517