Feign源码解析系列-核心初始化
开始
初始化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容器中拿来使用。
上一篇: .Net Project 常规结构
推荐阅读
-
Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
-
死磕 java同步系列之Phaser源码解析
-
Feign源码解析系列-那些注解们
-
死磕 java同步系列之CyclicBarrier源码解析——有图有真相
-
Asp.Net Core2.2 源码阅读系列——控制台日志源码解析
-
SpringBoot 源码解析 (三)----- Spring Boot 精髓:启动时初始化数据
-
Java 并发系列(一) ThreadPoolExecutor源码解析及理解
-
Glide源码解析一,初始化
-
SpringBoot 源码解析 (一)----- SpringBoot核心原理入门
-
死磕 java同步系列之ReentrantLock源码解析(二)——条件锁