初始化Feign客戶端固然是整個過程當中的核心部分,畢竟初始化完畢就等着調用了,初始化時候準備的什麼,流程就走什麼。java
從上一篇中,咱們已經知道,對於掃描到的每個有@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方法:spring
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上去的)。mybatis
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方法:app
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方法會觸發建造者的構建操做:ide
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)方法開始:ui
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)這個方法展開看一下:this
/** * 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是編譯時自動加入的方法。url
另外,Map<String, MethodHandler>的key是用Feign.configKey(target.type(), method)生成的,我以爲是能夠通用:spa
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容器中拿來使用。