当前位置: 移动技术网 > IT编程>开发语言>Java > Feign源码解析系列-核心初始化

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

2019年03月20日  | 移动技术网IT编程  | 我要评论

开始

初始化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:

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容器中拿来使用。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网