「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。
引言
Hello 大家好,这里是Anyin。
在上一篇 关于OpenFeign那点事儿 - 使用篇 我们聊了关于OpenFeign的使用以及在某些特殊场景下的解决方案。
今天我们来聊一聊,OpenFeign的底层是怎么实现的,为什么一个接口
加 一个注解
就可以实现远程的接口调用。
基础知识:Java动态代理
在我们日常开发中,最常见到的代码就是一个接口,一个实现类,通过实现类来执行具体的逻辑。如下:
public interface FeignApi {
ApiResponse infoByMobile(String mobile);
}
public class FeignApiImpl implements FeignApi{
public ApiResponse infoByMobile(String mobile){
log.info("this is execute impl method");
return ApiResponse.success("impl method");
}
}
而OpenFeign
的使用方法却是只使用了一个接口和一个注解,并没有实现类,即可实现了远程调用。神奇吗 ?
其实在我们的Java基础当中,Java动态代理即可实现该功能。这里,我们来自己手动实现下。
首先,我们先创建一个接口:
public interface FeignApi {
ApiResponse infoByMobile(String mobile);
}
接着,创建一个实现类FeignApiProxy
,并实现InvocationHandler
接口,在这个实现类来实现我们具体要执行的操作,其实就是用来代替FeignApi
接口的实现类。
@Slf4j
public static class FeignApiProxy implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果进来是一个实现的具体类,直接调用
if(Object.class.equals(method.getDeclaringClass())){
return method.invoke(this, args);
}else{ // 如果是一个接口
log.info("this is execute proxy method");
return ApiResponse.success("proxy method");
}
}
}
然后,我们再创建一个创建动态代理对象的类ProxyFactory
,它根据传递进来的类型,进行动态代理,创建代理对象。
public class ProxyFactory {
public <T> T getInstance(Class<T> clazz){
// 自定义实现的代理类
FeignApiProxy proxy = new FeignApiProxy();
// 返回代理对象
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, proxy);
}
}
最后,我们来测试下:
@Test
public void test(){
// 创建代理工厂
ProxyFactory factory = new ProxyFactory();
// 创建代理对象
FeignApi api = factory.getInstance(FeignApi.class);
// 执行方法
ApiResponse response = api.infoByMobile("anyin");
log.info("response: {}", JSONUtil.toJsonStr(response));
}
结果显示:
通过以上方法,我们就实现了一个简单的代理功能,只有接口,没有实现类的情况下,可以通过代理对象来执行具体的逻辑。
有人可能会疑问,虽然我们没有FeignApi
接口的实现类,但是我们却额外实现了InvocationHandler
接口的实现类,类的数量并没有变少,好像也没有省事多少。其实不然,FeignApi
接口的实现类是具体的某个类型类,而InvocationHandler
实现类是抽象的,我们可以在实现方法中处理各种逻辑。
例如:具体的使用场景就是我们场景的Mybatis
的Dao
层接口,OpenFeign
组件的FeignClien
接口。
基础知识:Spring Cloud NamedContextFactory
在了解了Java动态代理之后,我们再了解下Spring Cloud 的父子容器机制。
Spring Cloud 中它为了实现不同的微服务具有不同的配置,例如不同的FeignClient
会使用不同的ApplicationContext
,从各自的上下文中获取不同配置进行实例化。在什么场景下我们会需要这种机制呢? 例如,认证服务是会高频访问的服务,它的客户端超时时间应该要设置的比较小;而报表服务因为涉及到大量的数据查询和统计,它的超时时间就应该设置的比较大。
在Spring Cloud 中NamedContextFactory
就是为了实现该机制而设计的。我们可以自己手动实现下这个机制。
首先,创建AnyinContext
类和AnyinSpecification
类。AnyinContext
就是我们的上下文类,或者容器类,它是一个子容器。AnyinSpecification
类是对应的配置类保存类,根据不通过的上下文名称(name
字段)来获取配置类。
public static class AnyinSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configurations;
public AnyinSpecification(String name, Class<?>[] configurations) {
this.name = name;
this.configurations = configurations;
}
@Override
public String getName() {
return name;
}
@Override
public Class<?>[] getConfiguration() {
return configurations;
}
}
public static class AnyinContext extends NamedContextFactory<AnyinSpecification>{
private static final String PROPERTY_SOURCE_NAME = "anyin";
private static final String PROPERTY_NAME = PROPERTY_SOURCE_NAME + ".context.name";
public AnyinContext(Class<?> defaultConfigType) {
super(defaultConfigType, PROPERTY_SOURCE_NAME, PROPERTY_NAME);
}
}
接着,我们创建三个bean类,它们会分别置于父容器配置、子容器公共配置、子容器配置类中。如下:
public static class Parent {}
public static class AnyinCommon{}
@Getter
public static class Anyin {
private String context;
public Anyin(String context) {
this.context = context;
}
}
然后,再创建三个配置类,如下:
// 父容器配置类
@Configuration(proxyBeanMethods = false)
public static class ParentConfig {
@Bean
public Parent parent(){
return new Parent();
}
@Bean
public Anyin anyin(){
return new Anyin("anyin parent=============");
}
}
// 子容器公共配置类
@Configuration(proxyBeanMethods = false)
public static class AnyinCommonCnofig{
@Bean
public AnyinCommon anyinCommon(){
return new AnyinCommon();
}
}
// 子容器1配置类
@Configuration(proxyBeanMethods = false)
public static class Anyin1Config{
@Bean
public Anyin anyin(){
return new Anyin("anyin1=============");
}
}
// 子容器2配置类
@Configuration(proxyBeanMethods = false)
public static class Anyin2Config{
@Bean
public Anyin anyin(){
return new Anyin("anyin2=============");
}
}
最后,我们来做下代码测试。
@Test
public void test(){
// 创建父容器
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
// 注册父容器配置类
parent.register(ParentConfig.class);
parent.refresh();
// 创建子容器,并且注入默认的功能配置类
AnyinContext context = new AnyinContext(AnyinCommonCnofig.class);
// 子容器1配置类
AnyinSpecification spec1 = new AnyinSpecification("anyin1", new Class[]{Anyin1Config.class});
// 子容器2配置类
AnyinSpecification spec2 = new AnyinSpecification("anyin2", new Class[]{Anyin2Config.class});
// 子容器和父容器绑定关系
context.setApplicationContext(parent);
// 子容器注入子容器1/2配置类
context.setConfigurations(Lists.newArrayList(spec1, spec2));
// 获取子容器1的Parent实例
Parent parentBean1 = context.getInstance("anyin1", Parent.class);
// 获取子容器2的Parent实例
Parent parentBean2 = context.getInstance("anyin2", Parent.class);
// 获取父容器的Parent实例
Parent parentBean3 = parent.getBean(Parent.class);
// true
log.info("parentBean1 == parentBean2: {}", parentBean1.equals(parentBean2));
// true
log.info("parentBean1 == parentBean3: {}", parentBean1.equals(parentBean3));
// true
log.info("parentBean2 == parentBean3: {}", parentBean2.equals(parentBean3));
// 获取子容器1的AnyinCommon实例
AnyinCommon anyinCommon1 = context.getInstance("anyin1", AnyinCommon.class);
// 获取子容器2的AnyinCommon实例
AnyinCommon anyinCommon2 = context.getInstance("anyin1", AnyinCommon.class);
// true
log.info("anyinCommon1 == anyinCommon2: {}", anyinCommon1.equals(anyinCommon2));
// 报错,没有找到对应的bean
// AnyinCommon anyinCommon3 = parent.getBean(AnyinCommon.class);
// 获取子容器1的Anyin对象
Anyin anyin1 = context.getInstance("anyin1", Anyin.class);
// anyin1 context: anyin1=============
log.info("anyin1 context: {}", anyin1.getContext());
// anyin2 context: anyin2=============
Anyin anyin2 = context.getInstance("anyin2", Anyin.class);
log.info("anyin2 context: {}", anyin2.getContext());
// false
log.info("anyin1 == anyin2: {}", anyin1.equals(anyin2));
// anyinParent: anyin parent=============
Anyin anyinParent = parent.getBean(Anyin.class);
log.info("anyinParent: {}", anyinParent.getContext());
}
以上代码可能会比较长,请详细查阅。通过以上测试,我们可以总结以下几点:
- 子容器可以拿到父容器的实例
- 父容器无法拿到子容器的实例
- 实例优先从公共配置类中获取(这点需要在
AnyinCommonCnofig
配置类添加Anyin
实例配置)
源码解析
了解了以上2个基本的机制,这时候我们再来看源码,可能会容易理解一些。
OpenFeign
组件的入口就是从@EnableFeignClients
注解开始的。它导入了一个FeignClientsRegistrar
类,具体的FeignClient
就是在这个类内进行初始化和注册的。
在FeignClientsRegistrar
内,它先注册了默认的配置类,然后再注册具体的FeignClient
。这个默认的配置类是需要在@EnableFeignClients
注解内进行配置的,一般我们都不会配置,所以这里就暂时不管它。
接下来,我们看看注册具体的FeignClient
是如何实现的。
我们主要看看registerFeignClient
方法,该方法内通过FeignClientFactoryBean
来进行实例的创建,主要代码如下:
接着,我们看看FeignClientFactoryBean
的getObject()
方法,该方法返回了代理对象。
通过feign
方法获取了Feign.Builder
实例,它是在FeignClientsConfiguration
类中进行配置的。
在loadBalance
方法中,获取Client
实例,该实例用于做客户端负载均衡,Targeter
实例是DefaultTargeter
,它同样是在FeignClientsConfiguration
中进行装配的。
接着代码会回到Feign
类的target
方法。
我们直接看下ReflectiveFeign
类的newInstance
方法。
到这里,是不是看到了熟悉的场景,Java动态代理
就是在这里处理的。target.type()
返回的就是我们的定义的FeignClient
接口。InvocationHandler
的实现类就是ReflectiveFeign#FeignInvocationHandler
,它的invoke
方法就是我们在调用FeignClient
接口的方法代理的具体实现。
根据方法实例,拿到对应的MethodHandler
进行调用。dispatch
其实就是一个map数据结构,如下:
返回的MethodHandler
接口的实现类就是SynchronousMethodHandler
,它的invoke
方法就是开始执行具体的http调用逻辑。
最后
以上,就是根据个人理解梳理的OpenFeign
源码流程,如果有什么问题欢迎大家指正。
个人认为,只要理解底层的Java动态代理
和Spring Cloud NamedContextFactory
机制,然后再回过头过来梳理源码,整个流程会顺很多。
相关测试用例源码地址:Anyin Cloud