前言

FeignClient的实现原理网上一搜一大把,此处我就不详细再说明,比如:Feign原理 (图解) - 疯狂创客圈 - 博客园 (cnblogs.com),而且关于FeignClient的使用技巧我之前文章《feignclient各种使用技巧说明》已经讲过,此处仅说一下核心步骤:

  1. 启动时:@EnableFeignClients注解-->@Import(FeignClientsRegistrar.class)-->FeignClientsRegistrar.registerBeanDefinitions-->org.springframework.cloud.netflix.feign.FeignClientsRegistrar#registerFeignClients-->扫描有添加了@FeignClient注解的接口类注解BEAN元信息列表【即:AnnotatedBeanDefinition】-->org.springframework.cloud.netflix.feign.FeignClientsRegistrar#registerFeignClient-->构建一个FeignClientFactoryBean的BeanDefinitionBuilder,并将type等相关信息设置给FeignClientFactoryBean,-->BeanDefinitionReaderUtils.registerBeanDefinition【即注册成FactoryBean】;

  2. 实际注入FeignClient接口类依赖时:根据FeignClient接口类class找到FeignClientFactoryBean对象实例-->org.springframework.cloud.netflix.feign.FeignClientFactoryBean#getObject-->org.springframework.cloud.netflix.feign.FeignClientFactoryBean#feign【得到Feign.Builder】-->targeter = get(context, Targeter.class);-->targeter.target-->feign.target(target)-->feign.Feign.Builder#build-->feign.ReflectiveFeign#newInstance-->handler = factory.create(target, methodToHandler)【得到InvocationHandler】

  3. 执行时:(feign.hystrix.HystrixInvocationHandler【feign.hystrix.enabled=true时】 OR feign.ReflectiveFeign.FeignInvocationHandler#)#invoke -->dispatch.get(method).invoke(args);【得到代理方法SynchronousMethodHandler并执行该方法】-->Client#execute【Client的实现类,其中:LoadBalancerFeignClient 是使用ribbon组件时默认实现的】

上面核心步骤其实也还是很多,我这里一句概括核心:将@FeignClient标注的接口类通过FeignClientFactoryBean生成代理类(InvocationHandler,注意有多种实现子类),再执行InvocationHandler.invoke方法,间接执行内部的MethodHandler(SynchronousMethodHandler实现类之一)invoke方法,最后由实际的Client来完成远程URL请求及响应结果转换;其中最重要也是复杂的是InvocationHandler的实现类、MethodHandler的实现类;

FeignClient的扩展点非常多,比如:FeignClientsConfiguration 类中所有默认配置均可以自行替换自定义的实现类,若需单个FeignClient生效,则可通过@FeignClient注解的configuration属性指明对应这个FeignClient的特有配置类(如:MyFeignClientConfiguration)【注意自定义的配置类此处不能使用@Configuration注解,否则将导致全局生效,不加@Configuration注解时,则会由对应的contextId的FeignClientContext单独创建】

那么说了这么多,为了大家能够理解FeignClient的核心实现原理,同时因为我项目中也要实现类似的功能(目的让开发人员对复杂部份透明,调用远程BEAN的方法就像调本地一样,即RPC的初衷),我(梦在旅途 www.zuowenjun.cn)通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质,代码全部贴出来了,大家应该一看就懂,不懂复制到DEMO项目中DEBUG起来就也就明白了。实际运行的结果符合预期;

1. 定义注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DemoClient {
//定义相关的注解属性
}

2. 定义标注了@DemoClient注解接口对应的真实代理类FactoryBean

public class DemoClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

    private Class<?> type;

    private ApplicationContext context;

    //可添加其它属性字段(同时记得至少添加setter方法)

    @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
} @Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(RemoteTestClientFactoryBean.class.getClassLoader(), new Class<?>[]{this.type}, 自定义代理对象处理类【需继承自InvocationHandler,也可以直接采用lambda表达式】,这里也是真正的执行业务逻辑的核心);
} @Override
public Class<?> getObjectType() {
return this.type;
} @Override
public boolean isSingleton() {
return true;
} @Override
public void afterPropertiesSet() throws Exception { } /**
* 设置当前工厂要生成的BEAN Type,由BeanWrapper动态赋值,内部支持根据class String直接转换为Class对象
*
* @param type
*/
public void setType(Class<?> type) { this.type = type;
}
}

3. 定义扫描标注了@DemoClient注解接口并自动注册为如上第2步定义的FactoryBean的Bean

public class DemoClientsRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;

    @Override
public void setEnvironment(Environment environment) {
this.environment=environment;
} @Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathScanningCandidateComponentProvider scanner=getScanner();
scanner.addIncludeFilter(new AnnotationTypeFilter(RemoteTestClient.class));
Set<BeanDefinition> candidateComponents=scanner.findCandidateComponents(ClassUtils.getPackageName(Application.class));
if (CollectionUtils.isEmpty(candidateComponents)){
return;
} for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata metadata = beanDefinition.getMetadata();
Assert.isTrue(metadata.isInterface(),
"@DemoClient can only be specified on an interface"); registerDemoClient(beanDefinitionRegistry,metadata);
}
} } private void registerDemoClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata){
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(DemoClientFactoryBean.class);
//这里将类名传给class<?>属性,看似类型不匹配,其实赋值时会由BeanWrapper进行自动转换
definition.addPropertyValue("type",className);
//这里还可以赋值更多属性,具体依据DemoClientFactoryBean的属性定义
registry.registerBeanDefinition(className + "DemoClient",definition.getBeanDefinition());
} protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) { @Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
//必需是独立的 且 是接口 才是符合扫描条件的【可以更严格的过滤判断】
if (beanDefinition.getMetadata().isIndependent() && beanDefinition.getMetadata().isInterface()) {
if (beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata()
.getInterfaceNames().length == 1
&& Annotation.class.getName().equals(beanDefinition
.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(
beanDefinition.getMetadata().getClassName(),
RemoteTestClientsRegistrar.class.getClassLoader());
return !target.isAnnotation();
}
catch (Exception ex) {
this.logger.error(
"Could not load target class: "
+ beanDefinition.getMetadata().getClassName(),
ex); }
}
return true;
}
return false; }
};
} }

4.定义配置启动扫描并注册代理Bean的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DemoClientsRegistrar.class)
public @interface EnableDemoClient { }

5.最后将第4步定义的注解@EnableDemoClient添加到Spring Applcation入口类上即可

@SpringBootApplication
@EnableDemoClient
public class Application { public static void main(String[] args){
SpringApplication.run(Application.class,args);
}
}

实际用法示例如下:

@DemoClient
public interface Demo1Client{
String getRemoteResult(Long id);
} @Service
public class Demo1Service{
@Autowired
private Demo1Client demo1Client;//此处实际注入的是DemoClientFactoryBean.getObject方法返回的InvocationHandler的代理类实例 public String doMany(Long id){
return demo1Client.getRemoteResult(id);//实际调用的是:InvocationHandler的代理类实例的invoke方法
}
}

通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质的更多相关文章

  1. ReactNative之结合具体示例来看RN中的的Timing动画

    今天继续更新RN相关的博客.上篇博客详细的聊了RN中关于Flex布局的相关东西,具体请参见<ReactNative之参照具体示例来看RN中的FlexBox布局>.本篇博客继续更新RN的动画 ...

  2. ReactNative之参照具体示例来看RN中的FlexBox布局

    今天是重阳节,祝大家节日快乐,今天继续更新RN相关的博客.上篇博客<ReactNative之从HelloWorld中看环境搭建.组件封装.Props及State>中我们通过一个HelloW ...

  3. Dubbo远程调用服务框架原理与示例

    Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成. 主要核心部件: Remoting: 网络通信框架 ...

  4. 转: Dubbo远程调用服务框架原理与示例

    Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和  Spring 框架无缝集成. 主要核心部件: Remoting:  网络通 ...

  5. Spring Framework框架解析(1)- 从图书馆示例来看xml文件的加载过程

    引言 这个系列是我阅读Spring源码后的一个总结,会从Spring Framework框架的整体结构进行分析,不会先入为主的讲解IOC或者AOP的原理,如果读者有使用Spring的经验再好不过.鉴于 ...

  6. 第3章2节《MonkeyRunner源码剖析》脚本编写示例: MonkeyDevice API使用示例(原创)

    天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...

  7. 第3章1节《MonkeyRunner源码剖析》脚本编写示例: MonkeyRunner API使用示例(原创)

    天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...

  8. 老李推荐: 第3章1节《MonkeyRunner源码剖析》脚本编写示例: MonkeyRunner API使用示例

    老李推荐: 第3章1节<MonkeyRunner源码剖析>脚本编写示例: MonkeyRunner API使用示例   MonkeyRunner这个类可以说是编写monkeyrunner脚 ...

  9. Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例

    目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装项目其它需要包 清除冗余文件并重新规划项目目录 配置文件 规划示例路由,并新建相关文件 实现数据访问和业务逻辑相关方法 编写mys ...

随机推荐

  1. 超详细的Eureka源码解析

    Eureka简介 Eureka是什么? Eureka是基于REST(Representational State Transfer)服务,主要以AWS云服务为支撑,提供服务发现并实现负载均衡和故障转移 ...

  2. js--typeof 和 instanceof 判断数据类型的区别及开发中的使用

    前言 日常的开发中,我们经常会遇到判断一个变量的数据类型或者该变量是否为空值的情况,你是如何去选择判断类型的操作符的?本文来总结记录一下我们开发人员必须掌握的关于 typeof 和 instanceo ...

  3. OSI模型与TCP/IP模型

    OSI模型与TCP/IP模型 OSI参考模型: ​ ---开放式系统互联参考模型 OSI/RM ISO ---国际标准化组织 --1979 应用层 ---- 通过应用进程间的交互来完成特定网络应用 表 ...

  4. vue基础-动态样式&表单绑定&vue响应式原理

    动态样式 作用:使用声明式变量来控制class和style的值 语法: :class/:style 注意:尽可能不要把动态class和静态class一起使用,原因动态class起作用的时间会比较晚,需 ...

  5. TCP三次握手四次挥手,通俗易懂版

    三次握手四次挥手 三次握手 其实很好理解,三次握手就是保证双手都有发送和接受的能力.那么最少三次才能验证完成 即----> 客户端发送---服务端收到----服务端发送-- 1.客户端发送 -- ...

  6. 机器学习:EM算法

    EM算法 各类估计 最大似然估计 Maximum Likelihood Estimation,最大似然估计,即利用已知的样本结果,反推最有可能(最大概率)导致这样结果的参数值的计算过程. 直白来讲,就 ...

  7. 替换excel模板中的内容并使用JavaMail发送邮件

    由于在公司工作,常年出差,每天都要以日报的形式向公司汇报当天的工作内容.而日报的内容大体上就只有当天工作的主要内容时变化的,其余的都是不变 的. 而我的电脑刚打开excel有点卡,因此决定使用Java ...

  8. CSP踩被记

    本来想起个清新脱俗的标题,但碍于语文功底不行,于是光明正大嫖了LiBoyi的高端创意,把这篇博客命名为踩被记. Day -6 用假暴力把真正解拍没了,伤心.Rp有点低 Day -4 信息学考,\(py ...

  9. mbps和MB/s是怎么换算的

    Mbps即"传输速率",也叫"带宽".去营业厅开网线的时候会问开几兆的宽带,这里说的"几兆的宽带"就是指多少Mbps,但是Mbps和MB/s ...

  10. [LGP2758]编辑距离

    目录 题目 题目描述 输入格式 输出格式 输入输出样例 题目分析 状态转移方程 初始状态 结束状态 Code 题目 题目描述 设A和B是两个字符串.我们要用最少的字符操作次数,将字符串A转换为字符串B ...