通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质
前言
FeignClient的实现原理网上一搜一大把,此处我就不详细再说明,比如:Feign原理 (图解) - 疯狂创客圈 - 博客园 (cnblogs.com),而且关于FeignClient的使用技巧我之前文章《feignclient各种使用技巧说明》已经讲过,此处仅说一下核心步骤:
启动时:@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】;
实际注入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】
执行时:(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的本质的更多相关文章
- ReactNative之结合具体示例来看RN中的的Timing动画
今天继续更新RN相关的博客.上篇博客详细的聊了RN中关于Flex布局的相关东西,具体请参见<ReactNative之参照具体示例来看RN中的FlexBox布局>.本篇博客继续更新RN的动画 ...
- ReactNative之参照具体示例来看RN中的FlexBox布局
今天是重阳节,祝大家节日快乐,今天继续更新RN相关的博客.上篇博客<ReactNative之从HelloWorld中看环境搭建.组件封装.Props及State>中我们通过一个HelloW ...
- Dubbo远程调用服务框架原理与示例
Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成. 主要核心部件: Remoting: 网络通信框架 ...
- 转: Dubbo远程调用服务框架原理与示例
Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成. 主要核心部件: Remoting: 网络通 ...
- Spring Framework框架解析(1)- 从图书馆示例来看xml文件的加载过程
引言 这个系列是我阅读Spring源码后的一个总结,会从Spring Framework框架的整体结构进行分析,不会先入为主的讲解IOC或者AOP的原理,如果读者有使用Spring的经验再好不过.鉴于 ...
- 第3章2节《MonkeyRunner源码剖析》脚本编写示例: MonkeyDevice API使用示例(原创)
天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...
- 第3章1节《MonkeyRunner源码剖析》脚本编写示例: MonkeyRunner API使用示例(原创)
天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...
- 老李推荐: 第3章1节《MonkeyRunner源码剖析》脚本编写示例: MonkeyRunner API使用示例
老李推荐: 第3章1节<MonkeyRunner源码剖析>脚本编写示例: MonkeyRunner API使用示例 MonkeyRunner这个类可以说是编写monkeyrunner脚 ...
- Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装项目其它需要包 清除冗余文件并重新规划项目目录 配置文件 规划示例路由,并新建相关文件 实现数据访问和业务逻辑相关方法 编写mys ...
随机推荐
- SVN--代码状态检查(图文并茂)
接下来,我们用客户端去检出代码,在桌面空白处单击右键,选择SVN检出(check out),在弹出的对话框中填写版本库URL(具体获取方式,上面讲上传项目到版本库的时候讲过),选择检出目录,点击确定. ...
- Python3入门系列之-----元组
元组 Python 的元组与列表类似,不同之处在于元组的元素不能修改 元组使用小括号,列表使用方括号 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可 实例 tup1 = (1,2,3,4, ...
- mybatis 配置问题查找
mybatis配置问题记录 org.apache.ibatis.binding.BindingException: Type interface com.xx.dao.UserDao(自己项目的文件) ...
- iOS实现XMPP通讯(二)XMPP编程
项目概述 这是一个可以登录jabber账号,获取好友列表,并且能与好友进行聊天的项目. 使用的是第三方库XMPPFramework框架来实现XMPP通讯. 项目地址:XMPP-Project 项目准备 ...
- WinForm 控件 DataGridView 常用操作
1.取消列自动生成 在窗体load事件里面设置表格dataGridView的AutoGenerateColumns为 false dataGridView.AutoGenerateColumns = ...
- 这几种Java异常处理方法,你会吗?
摘要:我们在软件开发的过程中,任何语言的开发过程中都离不开异常处理. 本文分享自华为云社区<Java异常处理学习总结>,作者: zekelove . 我们在软件开发的过程中,任何语言的开发 ...
- 新一代数据科学ide平台DataSpell提前发行版体验
1 简介 PyCharm开发公司jetbrains专门面向数据科学的ide项目DataSpell在前不久发布了其EAP版本(早期预览版本),为我们带来了诸多趋于成熟的功能特性,本文就将为大家介绍其使用 ...
- Setoolkit部署
禁止使用本文的知识进行违法犯罪活动!!学习这些内容是为了更好的防范钓鱼网站 详见我的github仓库 Setoolkit : Social-Engineer Toolkit(社会工程学工具包) 其作为 ...
- Django Model字段加密的优雅实现
早前的一篇文章Django开发密码管理表实例有写我们写了个密码管理工具来实现对密码的管理,当时加密解密的功能在view层实现,一直运行稳定所以也没有过多关注实现是否优雅的问题.最近要多加几个密码表再次 ...
- ORB_SLAM3 -- 配置安装
安装环境 Ubuntu20.04 ORB_SLAM3依赖项安装 opencv3 ORB_SLAM3可用opencv3或opencv4编译,作者这里安装测试了opencv3 Step1: 安装openc ...