feign 是目前微服務間通訊的主流方式,是springCloud中一個很是重要的組件。他涉及到了負載均衡、限流等組件。真正意義上掌握了feign能夠說就掌握了微服務。spring
feign 的使用和dubbo的使用本質上很是類似。dubbo的理念是:像調用本地方法同樣調用遠程方法。那麼套在feign上一樣適用:像調用本地接口同樣調用遠程接口。
使用feign只須要2步:定義一個接口並用FeignClient註解說明接口所在服務和路徑,服務啓動類上添加@EnableFeignClients。以下所示app
@FeignClient(contextId = "order", name = "order", path = "/app") public interface OrderApiFeignClient { /** * 獲取訂單列表 * @return */ @RequestMapping("order/list") BaseResponse<List<OrderVO>> obtaining(@PathVariable("userId") Long userId); }
@EnableSwagger2 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients("com.xxx.*") @ComponentScan(value={"com.xxx"}) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication .class, args); } }
首先按照通常的思路,咱們會猜想基於接口生成代理類,而後對接口的調用實際上調的是代理對象,那真的是這樣麼? 咱們帶着猜測往下看。負載均衡
能夠看到註解自己主要定義了要掃描的feign接口包路徑以及配置,可是註解自己又有註解Import ,能夠看到他引入了FeignClientsRegistrar到容器。從名字看這個類就應該是在將feign接口註冊到容器中,接下來咱們具體看一下這個類幹了些什麼。框架
/** * @author Spencer Gibb * @author Jakub Narloch * @author Venil Noronha * @author Gang Li */ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
能夠看到FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar接口,但凡是實現了這個接口的類被注入到容器後,spring容器在啓用過程當中都會去調用它的void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)方法,能夠肯定的是FeignClientsRegistrar確定重寫了此方法,咱們接下來看一下該方法的實現。ide
能夠看到在這個方法中作了兩件事: 1)註冊feign配置, 2)註冊feign接口。咱們這裏抓一下重點,看一下feign接口是怎麼註冊的?微服務
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; 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) { // 限定只掃描FeingClient註解 scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } else { 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))); } 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"); Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); // 這裏生成bean而且註冊到容器 registerFeignClient(registry, annotationMetadata, attributes); } } } }
上面這段代碼歸納起來就是: 先找了包路徑basePackages , 而後在從這些包路徑中查找帶有FeignClient註解的接口,最後將註解的信息解析出來做爲屬性手動構建beanDefine注入到容器中。(這裏有一個類ClassPathScanningCandidateComponentProvider,它能夠根據filter掃描指定包下面的class對象,十分好用,建議收藏)。包路徑的獲取以及掃描feign相對簡單,這裏不作闡述,咱們看一下它生成bean的過程,關注上面代碼中的registerFeignClient方法。ui
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); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); 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); // 這裏省略部分代碼 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
代碼中經過BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class)生成的BeanDefine(請記住這裏設置得FeignClientFactoryBean.type就是feign接口對應得class對象)。那麼全部的feign接口最終註冊到容器中的都是FeignClientFactoryBean對應的一個實例(注意實際上註冊到容器中壓根就不是FeignClientFactoryBean對應的實例化對象,具體緣由看下文),到此feign接口對應的實例註冊過程已經完成。那麼回到一開始的問題爲何咱們調用接口的方法最終發起了請求? 是否有代理類的生成呢? 咱們接下來看看FeignClientFactoryBean類的特殊之處this
由上文知,每個feign接口實際上最終都會生成FeignClientFactoryBean ,最終由FeignClientFactoryBean生成具體的bean實例註冊到容器中。url
/** * @author Spencer Gibb * @author Venil Noronha * @author Eko Kurniawan Khannedy * @author Gregor Zurowski */ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware
能夠看到該類實現了FactoryBean接口,這意味着當Spring註冊該bean實例到容器中時,實際是調用其getObject方法,那麼FeignClientFactoryBean必定是重寫了getObject()方法,接下來咱們看一下getObject()幹了什麼事情:代理
public Object getObject() throws Exception { return getTarget(); }
咱們繼續追蹤getTarget()方法:
<T> T getTarget() { FeignContext context = this.applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); // 省略部分代碼... Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url)); }
顯然最終的bean是經過target.target()方法生成,咱們繼續往下看:
@Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { return feign.target(target); }
顯然最終的bean是經過feign.target(target)生成。咱們繼續往下看:
public <T> T target(Target<T> target) { return build().newInstance(target); }
顯然最終得bean是經過build().newInstance(target)生成。咱們繼續往下看:
public <T> T newInstance(Target<T> target) { // 省略部分代碼 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這個熟悉得身影了,沒錯他就是基於JDK原生得動態代理生成了FeignClientFactoryBean.type屬性對應得class對應得代理類。從前文咱們知道FeignClientFactoryBean.type就是feign接口得class對象。因此最終咱們調用feign接口得方法實際上調用得是InvocationHandler方法。
總結起來,就是經常咱們掛在口頭的東西就是將feign接口生成代理類,而後調用代理接口方法其實調用的代理類得方法,具體是爲何?不知道你們是否清楚。但願經過本文的閱讀能讓你們閱讀源碼的能力獲得提高,也不在對feign有一種黑盒子的感受。可能篇幅看起來較少,其實feign的註冊過程牽涉到框架層面的知識仍是蠻多的,包括springIoc、BeanDefine、動態代理等等,仔細看明白的話收穫應該仍是有蠻多的。哈哈,懂得都懂。順手提一句:讀源碼必定要對SPI等等特別熟悉,要否則你會無從下手,沒有方向,抓不到重點。後續會更新文章講feign怎麼實現負載均衡、熔斷等。
(本文原創、轉載請註明出處)