评论

收藏

[Java] Spring-cloud Feign 的深入理解

编程语言 编程语言 发布于:2021-09-18 19:35 | 阅读数:345 | 评论:0

这篇文章主要介绍了Spring-cloud Feign 的深入理解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
feign的调用流程
读取注解信息:enablefeignclients-->feignclientsregistrar-->feignclientfactorybean
feigh流程:reflectivefeign-->contract-->synchronousmethodhandler
相关configuration:feignclientsconfiguration,feignautoconfiguration,defaultfeignloadbalancedconfiguration,feignribbonclientautoconfiguration(ribbon)
在feignclientsregistrar中:
@override
public void registerbeandefinitions(annotationmetadata metadata,
  beandefinitionregistry registry) {
  //注册feign配置信息
  registerdefaultconfiguration(metadata, registry);
  //注册feign client
  registerfeignclients(metadata, registry);
}
 
private void registerfeignclient(beandefinitionregistry registry,
  annotationmetadata annotationmetadata, map<string, object> attributes) {
  string classname = annotationmetadata.getclassname();
  //准备注入feignclientfactorybean
  beandefinitionbuilder definition = beandefinitionbuilder
    .genericbeandefinition(feignclientfactorybean.class);
  ...
}
查看feignclientfactorybean:
@override
public object getobject() throws exception {
  feigncontext context = applicationcontext.getbean(feigncontext.class);
  //构建feign.builder
  feign.builder builder = feign(context);
  //如果注解没有指定url
  if (!stringutils.hastext(this.url)) {
  string url;
  if (!this.name.startswith("http")) {
    url = "http://" + this.name;
  }
  else {
    url = this.name;
  }
  url += cleanpath();
  return loadbalance(builder, context, new hardcodedtarget<>(this.type,
    this.name, url));
  }
  //如果指定了url
  if (stringutils.hastext(this.url) && !this.url.startswith("http")) {
  this.url = "http://" + this.url;
  }
  string url = this.url + cleanpath();
  client client = getoptional(context, client.class);
  if (client != null) {
  if (client instanceof loadbalancerfeignclient) {
    // 因为指定了url且classpath下有ribbon,获取client的delegate(unwrap)
    // not load balancing because we have a url,
    // but ribbon is on the classpath, so unwrap
    client = ((loadbalancerfeignclient)client).getdelegate();
  }
  builder.client(client);
  }
  targeter targeter = get(context, targeter.class);
  return targeter.target(this, builder, context, new hardcodedtarget<>(
    this.type, this.name, url));
}
 
protected <t> t loadbalance(feign.builder builder, feigncontext context,
  hardcodedtarget<t> target) {
  //获取feign client实例
  client client = getoptional(context, client.class);
  if (client != null) {
  builder.client(client);
  //defaulttargeter或者hystrixtargeter
  targeter targeter = get(context, targeter.class);
  //调用builder的target,其中就调用了feign的newinstance
  return targeter.target(this, builder, context, target);
  }
 
  throw new illegalstateexception(
    "no feign client for loadbalancing defined. did you forget to include spring-cloud-starter-netflix-ribbon?");
}
在feignclientsconfiguration配置了feign.builder,prototype类型:
@bean
@scope("prototype")
@conditionalonmissingbean
public feign.builder feignbuilder(retryer retryer) {
  return feign.builder().retryer(retryer);
}
feign的builder.build返回了一个reflectivefeign:
public feign build() {
 synchronousmethodhandler.factory synchronousmethodhandlerfactory =
   new synchronousmethodhandler.factory(client, retryer, requestinterceptors, logger,
            loglevel, decode404);
 parsehandlersbyname handlersbyname =
   new parsehandlersbyname(contract, options, encoder, decoder,
         errordecoder, synchronousmethodhandlerfactory);
 //reflectivefeign构造参数
 //parsehandlersbyname作用是通过传入的target返回代理接口下的方法的各种信息(methodhandler)
 //contract:解析接口的方法注解规则,生成methodmetadata
 //options:request超时配置
 //encoder:请求编码器
 //decoder:返回解码器
 //errordecoder:错误解码器
 //synchronousmethodhandler.factory是构建synchronousmethodhandler的工厂
 //client:代表真正执行http的组件
 //retryer:该组决定了在http请求失败时是否需要重试
 //requestinterceptor:请求前的拦截器
 //logger:记录日志组件,包含各个阶段记录日志的方法和留给用户自己实现的log方法
 //logger.level:日志级别
 //decode404:处理404的策略,返回空还是报错
 //synchronousmethodhandlerfactory通过所有的信息去包装一个synchronousmethodhandler,在调用invoke方法的时候执行http
 return new reflectivefeign(handlersbyname, invocationhandlerfactory);
}
在调用feign.builder的target的时候,调用了reflectivefeign.newinstance:
/**
 * creates an api binding to the {@code target}. as this invokes reflection, care should be taken
 * to cache the result.
 */
@suppresswarnings("unchecked")
@override
//接收target参数(包含feign代理接口的类型class,名称,http url)
public <t> t newinstance(target<t> target) {
 //首先通过**parsehandlersbyname**解析出接口中包含的方法,包装requesttemplate,组装成<name, methodhandler>
 map<string, methodhandler> nametohandler = targettohandlersbyname.apply(target);
 map<method, methodhandler> methodtohandler = new linkedhashmap<method, methodhandler>();
 //接口default方法list
 list<defaultmethodhandler> defaultmethodhandlers = new linkedlist<defaultmethodhandler>();
 
 for (method method : target.type().getmethods()) {
  if (method.getdeclaringclass() == object.class) {
   continue;
  } else if(util.isdefault(method)) {
   defaultmethodhandler handler = new defaultmethodhandler(method);
   defaultmethodhandlers.add(handler);
   methodtohandler.put(method, handler);
  } else {
   methodtohandler.put(method, nametohandler.get(feign.configkey(target.type(), method)));
  }
 }
 //invocationhandlerfactory.default()返回了一个reflectivefeign.feigninvocationhandler对象,通过传入的methodhandler map 调用目标对象的对应方法
 invocationhandler handler = factory.create(target, methodtohandler);
 //生成jdk代理对象
 t proxy = (t) proxy.newproxyinstance(target.type().getclassloader(), new class<?>[]{target.type()}, handler);
 //绑定接口的默认方法到代理对象
 for(defaultmethodhandler defaultmethodhandler : defaultmethodhandlers) {
  defaultmethodhandler.bindto(proxy);
 }
 return proxy;
}
生成feign代理对象的基本流程图:
DSC0000.png

当调用接口方法时,实际上就是调用代理对象invoke方法:
@override
public object invoke(object[] argv) throws throwable {
 //工厂创建请求模版
 requesttemplate template = buildtemplatefromargs.create(argv);
 //每次克隆一个新的retryer
 retryer retryer = this.retryer.clone();
 while (true) {
  try {
   //这里调用实际的feign client execute
   return executeanddecode(template);
  } catch (retryableexception e) {
   //失败重试
   retryer.continueorpropagate(e);
   if (loglevel != logger.level.none) {
  logger.logretry(metadata.configkey(), loglevel);
   }
   continue;
  }
 }
}
在defaultfeignloadbalancedconfiguration里实例化了loadbalancerfeignclient
@override
public response execute(request request, request.options options) throws ioexception {
  try {
  uri asuri = uri.create(request.url());
  string clientname = asuri.gethost();
  uri uriwithouthost = cleanurl(request.url(), clientname);
  //delegate这里是client.default实例,底层调用的是java.net原生网络访问
  feignloadbalancer.ribbonrequest ribbonrequest = new feignloadbalancer.ribbonrequest(
    this.delegate, request, uriwithouthost);
 
  iclientconfig requestconfig = getclientconfig(options, clientname);
  //executewithloadbalancer会根据ribbon的负载均衡算法构建url,这里不展开
  return lbclient(clientname).executewithloadbalancer(ribbonrequest,
    requestconfig).toresponse();
  }
  catch (clientexception e) {
  ioexception io = findioexception(e);
  if (io != null) {
    throw io;
  }
  throw new runtimeexception(e);
  }
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家
原文链接:https://segmentfault.com/a/1190000018077675

关注下面的标签,发现更多相似文章