Spring Cloud OpenFeign的原理(六)

經過上篇咱們瞭解OpenFeign他也能夠完成遠程通訊,可是它並非真正義意上的RPC通訊,由於他是經過封裝代理來實現的,下面和之前同樣,知道了怎麼用就來看下他是怎麼實現的。spring

1、思考Feign要作的事情

有了ribbon的鋪墊如今看OpenFeign應該很清楚的知道,這玩意就是經過註解拿到服務名,而後經過服務名獲取服務列表,進行解析和負載最終拼接出一個URI路徑進行代理請求,那麼他要完成這一系列動做他就要作下面幾件事。app

  • 參數的解析和裝載
  • 針對指定的FeignClient,生成動態代理
  • 針對FeignClient中的方法描述進行解析
  • 組裝出一個Request對象,發起請求

2、源碼分析

看過我寫的ribbon的應該清楚,若是想要找到進入源碼的入口那麼應該要找的是FeignClient,可是FeignClient是在哪裏被解析的呢,在應用篇中我在啓動類中加了個@EnableFeignClients註解,這 個註解的做用其實就是開啓了一個FeignClient的掃描,那麼點擊啓動類的@EnableFeignClients註解看下他是怎麼開啓FeignClient的掃描的,進去後發現裏面有個@Import(FeignClientsRegistrar.class)這個FeignClientsRegistrar跟Bean的動態裝載有關負載均衡

 

 

 點擊進去有個registerBeanDefinitions方法經過名稱能夠知道是一個Bean的注入方法框架

 

 

 下面我寫一個簡單的例子來描述他是如何實現動態加載的,學FeignClientsRegistrar類 implements ImportBeanDefinitionRegistrar接口並實現registerBeanDefinitions方法ide

 

 

 這一步搞完後,定義一個註解,把@EnableFeignClients註解上的註解都抄過來並把@Import註解裏面的類改爲咱們本身定義的類源碼分析

 

 

 而後在啓動類上用上自定義的註解,那麼在啓動類時就能夠進行一個Bean的動態裝載了ui

 

 

 

 經過這個概念已經很清楚源碼中FeignClientsRegistrar類的FeignClientsRegistrar是怎麼完成Bean的動態加載了this

  • registerDefaultConfifiguration 方法內部從 SpringBoot 啓動類上檢查是否有@EnableFeignClients, 有該註解的話, 則完成 Feign 框架相關的一些配置內容註冊
  • registerFeignClients 方法內部從 classpath 中, 掃描得到 @FeignClient 修飾的類, 將類的內容解析爲 BeanDefifinition , 最終經過調用 Spring 框架中的BeanDefifinitionReaderUtils.resgisterBeanDefifinition 將解析處理過的 FeignClientBeanDeififinition 添加到 spring 容器中. 
@Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
//註冊默認配置信息 registerDefaultConfiguration(metadata, registry);
//註冊FeignClients(可能有多個) registerFeignClients(metadata, registry); }

進入registerFeignClients(metadata, registry);這玩意是幹啥的呢,在啓動類中的@EnableFeignClients是能夠定義多個basePackers的若是定義了多個那就要掃描FeignClients,下面就是掃描處理過程,看過spring源碼的人就知道前面是什麼註解解析編碼

    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) {
            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)));
        }
         //bssePackages是解析不一樣basePackage路徑下的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");

                    Map<String, Object> attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName()); 

                    String name = getClientName(attributes);
//若是每一個包路徑下都有一個或多個FeignClient的話就要加載 一次動態configuration配置因此這就是爲何前面已經加載過了這裏還要加載一次的緣由 registerClientConfiguration(registry, name, attributes.
get("configuration")); //註冊Feignclient registerFeignClient(registry, annotationMetadata, attributes); } } } }

點擊registerFeignClient(registry, annotationMetadata, attributes);看下作了啥事,這裏面的邏輯其實就幹了一件事,就是經過BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);注入一個Bean;這個注入的過程當中有個比較重要的代碼BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);這是一個構造者,構造一個BeanDefinition,裏面把FeignClientFactoryBean.class給傳了進去url

 

 

 

 下面進入.genericBeanDefinition(FeignClientFactoryBean.class);看把 FeignClientFactoryBean.class類傳進去幹嗎,發現註冊的Bean就是參數中本身傳進來的beanClass,這個傳進去的beanClass是工廠Bean

Spring Cloud FengnClient其實是利用Spring的代理工廠來生成代理類,因此在這裏地方纔會把全部的FeignClient的BeanDefifinition設置爲FeignClientFactoryBean類型,而FeignClientFactoryBean繼承自FactoryBean,它是一個工廠Bean。在Spring中,FactoryBean是一個工廠Bean,用來建立代理Bean。工廠 Bean 是一種特殊的 Bean, 對於 Bean 的消費者來講, 他邏輯上是感知不到這個 Bean 是普通的 Bean 仍是工廠 Bean, 只是按照正常的獲取 Bean 方式去調用, 但工廠bean 最後返回的實例不是工廠Bean 自己, 而是執行工廠 Bean 的 getObject 邏輯返回的示例。

 

 

 點擊這個工廠Bean的FeignClientFactoryBean類中發現裏面有個getObject()方法,這個工廠Bean就是經過這個getTarget();返回一個真正的實例

 

 

 畫下時序圖

 

 

 

 前面說到了在啓動時會經過@EnableFeignClients去掃描全部指定路徑下的@FeignClient註解聲明的一個接口,而後在掃描到之後要去生成一個動態代理的類,這個動態代理的生成就是在調用getObject()時完成 ,並且getObject()又會調用他方法裏面的getTarget()去完成這件事,

它從applicationContext取出FeignContext,FeignContext繼承了NamedContextFactory,它是用來來統一維護feign中各個feign客戶端相互隔離的上下文。他只因此能完成隔離跟他父類中的contexts()方法有很大關係

<T> T getTarget() {
//FeignContext註冊到容器是在FeignAutoConfiguration上完成的
//在初始化FeignContext時,會把configurations在容器中放入FeignContext中。configurations的
//來源就是在前面registerFeignClients方法中將@FeignClient的配置configuration。
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);//構建Builder對象
//若是url爲空,則走負載均衡,生成有負載均衡功能的代理類
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
//若是指定了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 load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}//生成默認代理類
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}

 

 

 

 上面有段代碼Feign.Builder builder = feign(context);是構建Builder對象

 

    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; }

上面的builder構造完後繼續向下走,配置完Feign.Builder以後,再判斷是否須要LoadBalance,若是須要,則經過LoadBalance的方法來設置。實際上他們最終調用的是Target.target()方法。 

loadBalance這玩意比較重要由於他是生成具有負載均衡能力的feign客戶端,爲feign客戶端構建起綁定負載均衡客戶端Client client = (Client)this.getOptional(context, Client.class); 從上下文中獲取一個Client,默認是LoadBalancerFeignClient。
它是在FeignRibbonClientAutoConfifiguration這個自動裝配類中,經過Import實現的
@Import({ HttpClientFeignLoadBalancedConfiguration.class, 
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
//針對某一個服務的client Client client
= getOptional(context, Client.class); if (client != null) {
//將client設置進去至關於增長了客戶端負載均衡解析的機制 builder.client(client); Targeter targeter
= get(context, Targeter.class); 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?"); }

點擊上圖的targeter.target(this, builder, context, target);由於熔斷準備在後面講,因此在選tartget的實現時選擇DefaultTarget.target 

 

 點擊feign.target()往下走,走到這裏其實就已經到了核心邏輯了,前面不一直說動態代理嗎,前面走的都是人生最長的套路,前面本身寫的控制層代碼經過@Resource註解注入的UserOpenFeign他最終會調用下面的方法返回一個實例,那麼下面看下這newInstance()方法作了啥,發現這玩意有兩個實現,至於選擇哪一個就要看build()返回的是什麼了,向下看發現build()返回的是ReflectiveFeign,因此選第二個

這個方法是用來建立一個動態代理的方法,在生成動態代理以前,會根據Contract協議(協議解析規則,解析接口類的註解信息,解析成內部的MethodHandler的處理方式。從實現的代碼中能夠看到熟悉的Proxy.newProxyInstance方法產生代理類。而這裏須要對每一個定義的接口方法進行特定的處理實現,因此這裏會出現一個MethodHandler的概念,就是對應方法級別的InvocationHandler。 

 

 public <T> T newInstance(Target<T> target) {
 //根據接口類和Contract協議解析方式,解析接口類上的方法和註解,轉換成內部的MethodHandler處理方式
    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)));
      }
    }
    // 基於Proxy.newProxyInstance 爲接口類建立動態實現,將全部的請求轉換給InvocationHandler 處理。
    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;
  }

下面經過debugger驗證下,會看到userOpenFeign的返回的是代理類,經過下圖能夠知道當調用userOpenFeign時他實際上是調用ReflectiveFeign中的handler,而經過Debugger發現這個handler是FeginInvocationHandler,

居然是走了代理那麼他必定是走了ReflectiveFeign的代理方法invoke()方法

 

相關文章
相關標籤/搜索