本文分析所用源代碼基於Spring Cloud 2.1.0 RELEASEjava
在Spring cloud應用中,當咱們要使用feign客戶端時,通常要作如下三件事情 :spring
使用註解@EnableFeignClients啓用feign客戶端;
示例 :bash
@SpringBootApplication @EnableFeignClients public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
使用註解@FeignClient 定義feign客戶端 ;
示例 : 該例子定義了一個feign客戶端,將遠程服務http://test-service/test/echo映射爲一個本地Java方法調用。app
@FeignClient(name = "test-service", path = "/test") public interface TestService { @RequestMapping(value = "/echo", method = RequestMethod.GET) TestModel echo(@RequestParam("parameter") String parameter); }
使用註解@Autowired使用上面所定義feign的客戶端 ; 負載均衡
@Autowired TestService testService; public void run() { // 這裏的使用本地Java API的方式調用遠程的Restful接口 TestModel dto = testService.echo("Hello,你好!"); log.info("echo : {}", dto); }
上面的三個步驟,前兩個步驟能夠理解爲定義feign客戶端,第三步是使用所定義的feign客戶端。經過調試發現,上面第三步所注入的testService是一個代理對象,以下所示 :框架
testService = {$Proxy66@5502} "HardCodedTarget(type=TestService, name=test-service, url=http://test-service/test)" h = {ReflectiveFeign$FeignInvocationHandler@6924} target = {Target$HardCodedTarget@6930} dispatch = {LinkedHashMap@6931} size = 1 0 = {LinkedHashMap$Entry@6948} "public abstract xxx.model.TestModel xxx.service.TestService.echo(java.lang.String)"
該對象會代理客戶端完成遠程服務方法的調用,那麼,該代理對象是如何生成的 ?這篇文章,咱們經過源代碼解析來回答這兩個問題。ide
源代碼解析
註解@EnableFeignClients:掃描和註冊feign客戶端bean定義
註解@EnableFeignClients用於告訴框架掃描全部經過註解@FeignClient定義的feign客戶端。它又經過註解@Import導入了類FeignClientsRegistrar( feign客戶端註冊器),以下所示:函數
@EnableFeignClients => @Import(FeignClientsRegistrar.class)
FeignClientsRegistrar : feign客戶端註冊器
FeignClientsRegistrar實現了接口 ImportBeanDefinitionRegistrar。而ImportBeanDefinitionRegistrar的設計目的,就是被某個實現類實現,配合@Configuration註解的配置類,在配置類被處理時,用於額外註冊一部分bean定義:工具
對於上面的例子,使用者配置類就是 TestApplicationpost
public interface ImportBeanDefinitionRegistrar { /** * Register bean definitions as necessary based on the given annotation metadata of * the importing @Configuration class. * 根據使用者配置類的註解元數據註冊bean定義 * @param importingClassMetadata 使用者配置類的註解元數據 * @param registry 當前bean定義註冊表,通常指當前Spring應用上下文對象,當前Spring容器 */ public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); }
#registerBeanDefinitions – 註冊feign客戶端配置和feign客戶端
方法FeignClientsRegistrar#registerBeanDefinitions實現以下:
@Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { // 註冊缺省配置到容器 registry registerDefaultConfiguration(metadata, registry); // 註冊所發現的各個 feign 客戶端到到容器 registry registerFeignClients(metadata, registry); }
#registerDefaultConfiguration– 註冊feign客戶端缺省配置
// 註冊feign客戶端的缺省配置,缺省配置信息來自注解元數據的屬性 defaultConfiguration private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { // 獲取註解@EnableFeignClients的註解屬性 Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; // 下面是對所註冊的缺省配置的的命名,格式以下 : // default.xxx.TestApplication if (metadata.hasEnclosingClass()) { // 針對註解元數據metadata對應一個內部類或者方法返回的方法本地類的情形 name = "default." + metadata.getEnclosingClassName(); } else { // name 舉例 : default.xxx.TestApplication // 這裏 xxx.TestApplication 是註解@EnableFeignClients所在配置類的長名稱 name = "default." + metadata.getClassName(); } registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } }
#registerDefaultConfiguration方法最終註冊客戶端缺省配置的動做交給方法#registerDefaultConfiguration執行。
#registerClientConfiguration – 註冊feign客戶端配置
// 將指定feign客戶端配置configuration做爲一個bean定義註冊到容器: // bean 定義對象類型 : GenericBeanDefinition // bean class : FeignClientSpecification // bean name : default.xxx.TestApplication.FeignClientSpecification (缺省配置) // bean name : test-service.FeignClientSpecification (針對某個feign client 的配置) private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); }
#registerDefaultConfiguration方法用於註冊一個feign客戶端配置bean,能夠用於註冊針對全部feign客戶端的缺省配置的註冊,也能夠用於針對每一個feign客戶端的專有配置的註冊。
針對全部feign客戶端的缺省配置的bean名稱相似於 : default.xxx.TestApplication.FeignClientSpecification,
針對某個名稱爲test-service的feign客戶端的配置的bean名稱相似於:test-service.FeignClientSpecification。#registerFeignClients – 註冊各個feign客戶端及其配置
// 參數 metadata : 註解@EnableFeignClients所在配置類的註解元數據 public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { // 定義一個基於classpath的組件掃描器,它會根據指定的掃描位置和@EnableFeignClients註解屬性 // 找出開發人員定義的全部feign客戶端,也就是那些使用了註解@FeignClient的全部接口定義 ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; // attrs 用於表示註解@EnableFeignClients所在配置類的註解元數據中註解@EnableFeignClients // 的部分 Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { // @EnableFeignClients 中沒有指定 clients 屬性的狀況 scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } else { // @EnableFeignClients 中指定了 clients 屬性的狀況 final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } // 使用 scanner 掃描每個 basePackage, 獲取其中的 feign 客戶端定義, // 也就是 @FeignClient 定義的那些接口 for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // 獲取所定義的feign客戶端接口上的註解@FeignClient屬性 Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); // 將所定義的feign客戶端上的配置屬性做爲一個bean註冊到容器 registerClientConfiguration(registry, name, attributes.get("configuration")); // 將所定義的feign客戶端做爲一個bean註冊到容器: // bean 定義類型 : GenericBeanDefinition // bean class : FeignClientFactoryBean // autowire 模式 : 根據類型綁定 // @FeignClient註解中的url,path,fallback等屬性會設置爲bean定義的屬性 registerFeignClient(registry, annotationMetadata, attributes); } } } } // 輔助工具類,從@EnableFeignClients註解屬性中獲取basePackages屬性: // 參考如下@EnableFeignClients註解屬性 : // 1. value // 2. basePackages // 3. basePackageClasses // 4. 配置類所在的包 // 參數 importingClassMetadata : 使用註解@EnableFeignClients的配置類的元數據 protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) { // 註解@EnableFeignClients的屬性 Map<String, Object> attributes = importingClassMetadata .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName()); Set<String> basePackages = new HashSet<>(); for (String pkg : (String[]) attributes.get("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : (String[]) attributes.get("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add( ClassUtils.getPackageName(importingClassMetadata.getClassName())); } return basePackages; }
#registerFeignClients 最終註冊feign客戶端配置的動做交給#registerClientConfiguration完成,註冊feign客戶端的動做交給#registerFeignClient方法完成。#registerFeignClient – 註冊一個feign客戶端
// 將所定義的feign客戶端做爲一個bean註冊到容器: // bean 定義類型 : GenericBeanDefinition // bean class : FeignClientFactoryBean -- 這是一個工廠bean,而不是最終bean實例的class // autowire 模式 : 根據類型綁定 // @FeignClient註解中的url,path,fallback等屬性會設置爲bean定義的屬性 // 參數 registry : Spring 容器 // 參數 annotationMetadata : @FeignClient所註解的接口上的註解元數據 // 參數 attributes : @FeignClient 註解屬性信息 private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = name + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
從上面的代碼分析可知,FeignClientsRegistrar的主要做用以下 :
註冊缺省feign客戶端配置bean定義;
對於每一個@FeignClient註解的feign客戶端定義 :
註冊一個針對該feign客戶端的配置bean定義;
註冊該feign客戶端bean定義,生成bean採用工廠類FeignClientFactoryBean;
並且,上述功能實如今FeignClientsRegistrar實現的接口ImportBeanDefinitionRegistrar所定義的方法registerBeanDefinitions中。而該方法會在應用的@EnableFeignClients註解被處理時被調用執行。具體的執行時調用棧以下所示:
AbstractApplicationContext#invokeBeanFactoryPostProcessors => PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors => foreach BeanDefinitionRegistryPostProcessor : #postProcessBeanDefinitionRegistry => ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry => #processConfigBeanDefinitions => ConfigurationClassBeanDefinitionReader#loadBeanDefinitions => foreach ConfigurationClass : #loadBeanDefinitionsForConfigurationClass => #loadBeanDefinitionsFromRegistrars => foreach ImportBeanDefinitionRegistrar : #registerBeanDefinitions => FeignClientsRegistrar#registerBeanDefinitions
FeignClientFactoryBean生成feign客戶端代理對象
基於上面的分析,咱們能夠得知,開發人員所定義的feign客戶端和相關配置會以bean定義的形式註冊到bean容器中,這樣當使用@Autowired注入一個feign客戶端時,容器會使用工廠類FeignClientFactoryBean爲其生成一個實例。下面咱們來看其具體工做過程。
FeignClientFactoryBean#getObject生成feign客戶端代理對象 // 該方法由接口FactoryBean約定 @Override public Object getObject() throws Exception { return getTarget(); } <T> T getTarget() { // 從應用上下文中獲取建立 feign 客戶端的上下文對象 FeignContext // FeignContext 針對每一個feign客戶端定義會生成一個不一樣的 AnnotationConfigApplicationContext, // 這些應用上下文的parent都設置爲當前應用的主應用上下文 // 參考 : FeignAutoConfiguration FeignContext context = applicationContext.getBean(FeignContext.class); // 爲目標feign客戶端對象構建一個 builder,該builder最終生成的目標feign客戶端是一個 // 動態代理,使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { // @FeignClient 屬性 url 屬性沒有指定的狀況 // 根據屬性 name , path 拼裝一個 url, // 這種一般是須要在多個服務節點之間進行負載均衡的狀況 if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } // 方法cleanPath()加工屬性path,使其以/開頭,不以/結尾 url += cleanPath(); // 這裏造成的url格式相似 : http://test-service/test // 其中 test-service 是服務名,不是服務所在節點的IP,主機名或者域名 // 函數loadBalance 作以下動做 : // 1. 將builder和一個LoadBalancerFeignClient bean實例關聯起來 // 2. 使用一個HystrixTargeter將builder和一個 HardCodedTarget bean實例關聯起來 // 這裏 HardCodedTarget 表示對應 url 爲 http://test-service/test 的遠程服務(可能 // 包含多個服務方法) // 3. 生成最終的feign client 實例 : ReflectiveFeign$FeignInvocationHandler 的動態代理對象, // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。 // 每一個遠程服務方法會對應到一個@FeignClient註解的接口方法上(依據方法上的註解進行匹配) return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } // @FeignClient 屬性 url 屬性被指定的狀況 // 這種一般是明確指出了服務節點的url的狀況,實際上不須要負載均衡 if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); // 將builder和一個LoadBalancerFeignClient bean實例關聯起來 Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap // 由於指定了明確的服務節點url,因此這裏不須要負載均衡, // 因此這裏儘管client是LoadBalancerFeignClient,因此 // 實際上能夠獲取其所代理的對象做爲最終的client, // 至關於去掉了LoadBalancerFeignClient這層的代理功能 client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } // 使用一個HystrixTargeter將builder和一個 HardCodedTarget bean實例關聯起來 Targeter targeter = get(context, Targeter.class); // 生成最終的feign client 實例 : ReflectiveFeign$FeignInvocationHandler 的動態代理對象, // 使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler。 // 每一個遠程服務方法會對應到 一個@FeignClient註解的接口方法上(依據方法上的註解進行匹配) return (T) targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }
方法FeignClientFactoryBean#feign – 建立feign客戶端構建器
protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); // 從上下文獲取一個 Feign.Builder 上, // 並從上下文得到 Encoder, Decoder, Contract 設置到該 builder 上 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)); // 對 builder 進行其餘屬性設置 configureFeign(context, builder); return builder; }
方法FeignClientFactoryBean#loadBalance – 生成具有負載均衡能力的feign客戶端
爲feign客戶端構建器綁定負載均衡客戶端,綁定目標服務端點,並生成最終的feign客戶端實例。
// 對builder設置負載均衡客戶端,綁定到目標服務端點,構建最終的feign客戶端對象 protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { // 從上下文context獲取一個Client,缺省是 LoadBalancerFeignClient Client client = getOptional(context, Client.class); if (client != null) { // 將client設置到builder上 builder.client(client); // 從上下文中獲取一個 targeter,缺省是一個 HystrixTargeter Targeter targeter = get(context, Targeter.class); // 上面獲取獲得的 targeter 會根據 builder 的類型決定如何將 target // 綁定到 builder 並設置有關的其餘屬性和功能,而後生成最終的feign客戶端對象 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?"); }
從上面分析能夠看出,缺省狀況下,所使用的feign客戶端構建器類爲Feign.Builder,而且Targeter是一個HystrixTargeter。HystrixTargeter#target方法的參數builder爲Feign.Builder時,會直接調用該builder的target方法,以下所示 :
class HystrixTargeter implements Targeter { @Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } // ... 省略其餘代碼 } }
接下來再來看Feign.Builder#target是如何工做的。
// 執行構建而且建立相應的feign客戶端實例 public <T> T target(Target<T> target) { return build().newInstance(target); } // 構建過程,最終根據各類配置生成一個 ReflectiveFeign 對象 public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); } }
而後再看ReflectiveFeign#newInstance方法。
// 建立最終的feign客戶端實例 : 一個 ReflectiveFeign$FeignInvocationHandler 的動態代理對象 @Override 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 DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { // 對於每一個對應服務功能端點的方法,缺省使用nameToHandler獲取的MethodHandler,缺省是 // SynchronousMethodHandler methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } // 建立feign客戶端實例 ReflectiveFeign$FeignInvocationHandler, // 該對象包含了上面所建立的methodToHandler,用於對應各個開發者定義的@FeignClient接口方法 InvocationHandler handler = factory.create(target, methodToHandler); // 建立feign客戶端實例的動態代理對象 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); // 將缺省方法處理器綁定到feign客戶端實例的動態代理對象上 for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
從上面的分析咱們不難看出,爲何最終注入的testService最終是一個ReflectiveFeign$FeignInvocationHandler動態代理實例了。
從上面的分析能夠看出,當咱們使用註解@EnableFeignClients 時,至關於啓用了feign客戶端定義的掃描和註冊機制,從而能夠發現開發人員經過註解@FeignClient定義的feign客戶端,並最終做爲bean定義註冊到容器中。而經過@Autowired自動裝配註解,這些feign客戶端會以ReflectiveFeign$FeignInvocationHandler動態代理的形式被注入到使用方。該feign客戶端包含了對每一個接口方法的處理器MethodHandler,接口缺省方法對應DefaultMethodHandler,服務功能端點方法對應SynchronousMethodHandler。