欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Feign源码解析系列-核心初始化

程序员文章站 2022-10-06 14:36:13
开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么。 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient,都会组装一个FactoryBean即FeignClientFactoryBean注册到spring容 ......

开始

初始化feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么。

内容

从上一篇中,我们已经知道,对于扫描到的每一个有@feignclient,都会组装一个factorybean即feignclientfactorybean注册到spring容器中,如此在spring 容器初始化的时候,创建feignclient的bean时都会调用feignclientfactorybean的getobject方法。
feignclientfactorybean是spring的factorybean,在spring的世界里可以通过xml定义bean,也可以通过@bean注解的方法组装bean,但如果我们要的bean产生过程比较复杂,使用配置或单纯的new不好解决,这时候使用factorybean就比较合适了,在spring中想要找某个类型的bean时,如果是factorybean定义的,就会调用它的getobject获取这个bean。
feignclientfactorybean的getobject方法:

public object getobject() throws exception {
   feigncontext context = applicationcontext.getbean(feigncontext.class);
   // 构建feign.builder
   feign.builder builder = feign(context);
   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));
   }
   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) {
         // not lod 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));
}

构建feign.builder时会向feigncontext获取配置的encoder,decoder等各种信息。feigncontext在上篇中已经提到会为每个feign客户端分配了一个容器,它们的父容器就是spring容器,凡是在子容器中找不到的对象,再从父容器中找。
我们可以在feign.builder中看全部的可配置的属性,会发现有些信息在feignclient注解上有可以直接通过注解属性字段进行设置,比如ecode404,而有些属性是只能通过注解属性configuration配置configuration类来注入配置信息,比如:retryer。另外除了通过在注解属性上进行配置信息外,也可以通过feignclientproperties来配置这些信息。
在configurefeign方法中看到可以通通过defaulttoproperties属性来控制两者的优先级,默认为true,比如defaulttoproperties设置为false时,则会先向feign.builder放配置文件配置的信息,然后再放注解上配置的,后放的当然可以覆盖先放的,所以注解配置的优先级就算高的(除了requestinterceptor,这个是没有什么优先级的,是add上去的)。

protected feign.builder feign(feigncontext context) {
   feignloggerfactory loggerfactory = get(context, feignloggerfactory.class);
   logger logger = loggerfactory.create(this.type);
   // @formatter:off
   feign.builder builder = get(context, feign.builder.class)
         // required values
         .logger(logger)
         .encoder(get(context, encoder.class))
         .decoder(get(context, decoder.class))
         .contract(get(context, contract.class));
   // @formatter:on
   configurefeign(context, builder);
   return builder;
}
protected void configurefeign(feigncontext context, feign.builder builder) {
   feignclientproperties properties = applicationcontext.getbean(feignclientproperties.class);
   if (properties != null) {
      if (properties.isdefaulttoproperties()) {
         configureusingconfiguration(context, builder);
         configureusingproperties(properties.getconfig().get(properties.getdefaultconfig()), builder);
         configureusingproperties(properties.getconfig().get(this.name), builder);
      } else {
         configureusingproperties(properties.getconfig().get(properties.getdefaultconfig()), builder);
         configureusingproperties(properties.getconfig().get(this.name), builder);
         configureusingconfiguration(context, builder);
      }
   } else {
      configureusingconfiguration(context, builder);
   }
}
protected void configureusingconfiguration(feigncontext context, feign.builder builder) {
   logger.level level = getoptional(context, logger.level.class);
   if (level != null) {
      builder.loglevel(level);
   }
   retryer retryer = getoptional(context, retryer.class);
   if (retryer != null) {
      builder.retryer(retryer);
   }
   errordecoder errordecoder = getoptional(context, errordecoder.class);
   if (errordecoder != null) {
      builder.errordecoder(errordecoder);
   }
   request.options options = getoptional(context, request.options.class);
   if (options != null) {
      builder.options(options);
   }
   map<string, requestinterceptor> requestinterceptors = context.getinstances(
         this.name, requestinterceptor.class);
   if (requestinterceptors != null) {
      builder.requestinterceptors(requestinterceptors.values());
   }
   if (decode404) {
      builder.decode404();
   }
}

无论是通过配置文件还是注解属性,能够控制的都是一个feignclient整体的配置。而我们在写feign接口的方法是,还需要定义这个接口方法的http描述信息,比如请求路径,请求方式,参数定义等等。也就是说,对于一个单独的请求来说,完整配置的粒度要到feign接口里的方法级别。
在getobject方法的最后会调用targeter.target方法来组装对象,targeter是可以被扩展的,先不展开了,在默认的实现中会调用前面组装好的feign.builder的target方法:

class defaulttargeter implements targeter {
   @override
   public <t> t target(feignclientfactorybean factory, feign.builder feign, feigncontext context,
                  target.hardcodedtarget<t> target) {
      return feign.target(target);
   }
}

feign.builder的target方法会触发建造者的构建操作:

public <t> t target(target<t> target) {
  return build().newinstance(target);
}  
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);
    return new reflectivefeign(handlersbyname, invocationhandlerfactory);
  }

可以想象,我们只是定义了接口,通过接口的方法我们需要达成一个请求应用的操作,肯定是需要产生一个类来实现这些接口的,这里使用动态代理非常合适,那么事情就变得简单了,通过jdk自带的动态代理方式为接口产生一个代理实现类。这个实现思路可以借鉴到其他的场景,比如比较熟悉的mybatis定义的mapper接口,也是不需要实现的,实现的方式和这里是一模一样。
这个实现从reflectivefeign的newinstance(target)方法开始:

public <t> t newinstance(target<t> target) {
  map<string, methodhandler> nametohandler = targettohandlersbyname.apply(target);
  map<method, methodhandler> methodtohandler = new linkedhashmap<method, methodhandler>();
  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)));
    }
  }
  invocationhandler handler = factory.create(target, methodtohandler);
  t proxy = (t) proxy.newproxyinstance(target.type().getclassloader(), new class<?>[]{target.type()}, handler);
  for(defaultmethodhandler defaultmethodhandler : defaultmethodhandlers) {
    defaultmethodhandler.bindto(proxy);
  }
  return proxy;
}

从实现的代码中可以看到熟悉的proxy.newproxyinstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个methodhandler的概念,就是对应方法级别的invocationhandler。
for循环是在过滤不必要的方法,有意思的一个地方:util.isdefault(method)这个方法展开看一下:

/**
 * identifies a method as a default instance method.
 */
public static boolean isdefault(method method) {
  // default methods are public non-abstract, non-synthetic, and non-static instance methods
  // declared in an interface.
  // method.isdefault() is not sufficient for our usage as it does not check
  // for synthetic methods.  as a result, it picks up overridden methods as well as actual default methods.
  final int synthetic = 0x00001000;
  return ((method.getmodifiers() & (modifier.abstract | modifier.public | modifier.static | synthetic)) ==
          modifier.public) && method.getdeclaringclass().isinterface();
}

注释说,没有使用method.isdefault()是因为嫌弃它不够全面的识别,说应该过滤掉合成(synthetic)方法,synthetic methods是编译时自动加入的方法。

另外,map<string, methodhandler>的key是用feign.configkey(target.type(), method)生成的,我觉得是可以通用:

public static string configkey(class targettype, method method) {
  stringbuilder builder = new stringbuilder();
  builder.append(targettype.getsimplename());
  builder.append('#').append(method.getname()).append('(');
  for (type param : method.getgenericparametertypes()) {
    param = types.resolve(targettype, targettype, param);
    builder.append(types.getrawtype(param).getsimplename()).append(',');
  }
  if (method.getparametertypes().length > 0) {
    builder.deletecharat(builder.length() - 1);
  }
  return builder.append(')').tostring();
}

targettohandlersbyname.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个synchronousmethodhandler
然后需要维护一个<method,methodhandler>的map,放入invocationhandler的实现feigninvocationhandler中。
在feigninvocationhandler中的的invoke方法实现:

public object invoke(object proxy, method method, object[] args) throws throwable {
  if ("equals".equals(method.getname())) {
    try {
      object
          otherhandler =
          args.length > 0 && args[0] != null ? proxy.getinvocationhandler(args[0]) : null;
      return equals(otherhandler);
    } catch (illegalargumentexception e) {
      return false;
    }
  } else if ("hashcode".equals(method.getname())) {
    return hashcode();
  } else if ("tostring".equals(method.getname())) {
    return tostring();
  }
  return dispatch.get(method).invoke(args);
}

当代理类接到执行请求时, 通过一个map分发给对应的methodhandler执行,如此就实现了针对每个方法的个性化代理实现。
所以,结构就是一个invocationhandler对应多个methodhandler:
Feign源码解析系列-核心初始化

methodhandler的实现这里是使用synchronousmethodhandler,它实现的invoke方法如下:

public object invoke(object[] argv) throws throwable {
  requesttemplate template = buildtemplatefromargs.create(argv);
  retryer retryer = this.retryer.clone();
  while (true) {
    try {
      return executeanddecode(template);
    } catch (retryableexception e) {
      retryer.continueorpropagate(e);
      if (loglevel != logger.level.none) {
        logger.logretry(metadata.configkey(), loglevel);
      }
      continue;
    }
  }
}

到这里就会创建http请求模版,这部分后续再深入。

结束

可以看到产生的feignclient的代理对象,代理了接口方法,实际会生成一个http请求模版,进行请求操作。
回到前面触发的地方是spring调用feignclientfactorybean的getobject方法,所以产生的这个feignclient的代理对象会在spring容器中,我们直接可以从spring容器中拿来使用。