<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          因?yàn)橐粋€(gè)bug,我掀開(kāi)了openfeign的神秘面紗

          共 8310字,需瀏覽 17分鐘

           ·

          2024-04-10 23:14

          報(bào)錯(cuò)

          最近項(xiàng)目中訪問(wèn)一個(gè)外部api報(bào)錯(cuò)了,報(bào)錯(cuò)信息如下

                
                PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

          看著像是證書(shū)問(wèn)題,這個(gè)時(shí)候我首先想到的是百度下,看看怎么解決。

          解決方案

          百度告訴我說(shuō)如果你open-feign中使用的是http client,那么可以通過(guò)下面的配置來(lái)讓跳過(guò)SSL驗(yàn)證

                
                feign:
            httpclient:
              disable-ssl-validation: false

          結(jié)果還是報(bào)同樣的錯(cuò)誤。于是我又百度,又重新找了一個(gè)解決方法,這次的方案是讓我自己重寫(xiě)Client了,具體操作如下

                
                @Configuration
          public class FeignConfiguration {

          @Bean
          public Client feignClient() throws NoSuchAlgorithmException, KeyManagementException {
              SSLContext ctx = SSLContext.getInstance("SSL");
              X509TrustManager tm = new X509TrustManager() {
                  @Override
                  public void checkClientTrusted(X509Certificate[] chain, String authType) {
                  }
                  @Override
                  public void checkServerTrusted(X509Certificate[] chain, String authType) {
                  }
                  @Override
                  public X509Certificate[] getAcceptedIssuers() {
                      return null;
                  }
              };
              ctx.init(nullnew TrustManager[]{tm}, null);


              return  new Client.Default(ctx.getSocketFactory(), (hostName, session) -> true);

          }
             
          }

          這把我感覺(jué)要起飛了, 一切盡在掌握中,重新deploy,打開(kāi)postman,測(cè)試測(cè)試我的接口。

          測(cè)試后感覺(jué)好了但是看日志又沒(méi)有完全好。這個(gè)接口倒是不報(bào)錯(cuò)了,但是我調(diào)用內(nèi)部服務(wù)給我報(bào)錯(cuò)了,比如我這里的內(nèi)部服務(wù)名稱叫做

          pro-file, 就現(xiàn)在它沒(méi)法根據(jù)我這個(gè)pro-file名字找到對(duì)應(yīng)的IP了,從而導(dǎo)致我這個(gè)服務(wù)使用不了了。

          百度誤我!

          求人不如求己

          此刻我自信的打開(kāi)了IDEA, 輸入了類(lèi)名 FeignAutoConfiguration , Spring Cloud關(guān)于某個(gè)組件的自動(dòng)注入類(lèi)大多是XXXConfiguration, 所以按照這么找準(zhǔn)沒(méi)錯(cuò)。

          然后我有自信的把斷點(diǎn)打在了這個(gè)部分 FeignAutoConfiguration:246

                
                @Configuration(proxyBeanMethods = false)
          @ConditionalOnClass(ApacheHttpClient.class)
          @ConditionalOnMissingBean(CloseableHttpClient.class)
          @ConditionalOnProperty(value 
          "feign.httpclient.enabled", matchIfMissing = true)
          @Conditional(HttpClient5DisabledConditions.class)
          protected static class HttpClientFeignConfiguration 
          {
           // 省略其他代碼

           @Bean
           @ConditionalOnMissingBean(Client.class)
           public Client feignClient(HttpClient httpClient
          {
            return new ApacheHttpClient(httpClient);
           }

          }

          重新啟動(dòng)項(xiàng)目,好家伙斷點(diǎn)沒(méi)進(jìn)來(lái)呀。沒(méi)進(jìn)來(lái)的原因大概率可能是不滿足條件,我趕緊看看這里對(duì)應(yīng)的Conditional, 發(fā)現(xiàn)了我的代碼中沒(méi)有

          設(shè)置feign.httpclient.enabled屬性的值, 而且這里也沒(méi)有設(shè)置havingValue, 根據(jù)源碼可以知道, 如果沒(méi)有設(shè)置havingValue, 那么這個(gè)屬性的值會(huì)被和false進(jìn)行比較

                
                //org.springframework.boot.autoconfigure.condition.OnPropertyCondition.Spec#isMatch
          // 這里的requiredValue是havingValue
          private boolean isMatch(String value, String requiredValue) {
           if (StringUtils.hasLength(requiredValue)) {
            return requiredValue.equalsIgnoreCase(value);
           }
           return !"false".equalsIgnoreCase(value);
          }

          搞半天這個(gè)Configurtion相當(dāng)于沒(méi)起作用。

          好好好,這么玩是吧。

          既然這個(gè)配置不生效,那肯定有其他配置生效,我就找找其他配置,最終我在spring-cloud-openfeign-core這個(gè)jar包的loadbalancer這個(gè)包下面找到了我想要的配置

                
                @ConditionalOnClass(Feign.class)
          @ConditionalOnBean(
          { LoadBalancerClient.classLoadBalancerClientFactory.class })
          @AutoConfigureBefore(FeignAutoConfiguration.class)
          @AutoConfigureAfter(
          { BlockingLoadBalancerClientAutoConfiguration.classLoadBalancerAutoConfiguration.class })
          @EnableConfigurationProperties(FeignHttpClientProperties.class)
          @Configuration(proxyBeanMethods 
          false)
          // Order is important here, last should be the default, first should be optional
          // see
          // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
          @Import({ HttpClientFeignLoadBalancerConfiguration.classOkHttpFeignLoadBalancerConfiguration.class,
            HttpClient5FeignLoadBalancerConfiguration.classDefaultFeignLoadBalancerConfiguration.class })
          public class FeignLoadBalancerAutoConfiguration 
          {

          }

          因?yàn)槲覀冺?xiàng)目是采用springcloud alibaba進(jìn)行開(kāi)發(fā),所以引入了spring-cloud-loadbalancer這個(gè)包,因此這個(gè)這個(gè)配置類(lèi)就會(huì)生效,由于我們沒(méi)有配置使用httpclient,同樣也未使用okhttp,所以生效的配置類(lèi)只有一個(gè),那就是 DefaultFeignLoadBalancerConfiguration

          這個(gè)配置類(lèi)中retryClient會(huì)被加載,因?yàn)槲覀円肓藄pring-retry.

                
                @Bean
          @ConditionalOnMissingBean
          @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
          @ConditionalOnBean(LoadBalancedRetryFactory.class)
          @ConditionalOnProperty(value 
          "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
            matchIfMissing = true)
          public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
            LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory)
           
          {
           return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(nullnull), loadBalancerClient,
             loadBalancedRetryFactory, loadBalancerClientFactory);
          }

          這也是為什么上面我們自己配置了自己的Client后,訪問(wèn)其他spring cloud服務(wù)會(huì)找不到地址,這是因?yàn)槟J(rèn)的client不會(huì)去通過(guò)LoadBalancer去獲取服務(wù)地址。

          小插曲

          期間debug的時(shí)候,還發(fā)現(xiàn)最終的Client的SeataFeignClient,我一看才發(fā)現(xiàn)某個(gè)公共包引入了Seata,但是沒(méi)有使用Seata功能,然后Seata會(huì)把我們最終使用的FeignClient在給封裝一次,所以后面我就把seata從項(xiàng)目中移除了。

          解決方案

          既然問(wèn)題找到了,那么就好修改了,修改方式有兩種,一種是創(chuàng)建自己的RetryableFeignBlockingLoadBalancerClient, 就把上面的代碼拿過(guò)來(lái)抄一遍,只是自己指定SSLContext,另一種是啟用httpclient

          方案一

                
                @Bean
          public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
                                         LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory)
           throws NoSuchAlgorithmException, KeyManagementException 
          {
              SSLContext ctx = SSLContext.getInstance("SSL");
              X509TrustManager tm = new X509TrustManager() {
                  @Override
                  public void checkClientTrusted(X509Certificate[] chain, String authType) {
                  }
                  @Override
                  public void checkServerTrusted(X509Certificate[] chain, String authType) {
                  }
                  @Override
                  public X509Certificate[] getAcceptedIssuers() {
                      return null;
                  }
              };
              ctx.init(nullnew TrustManager[]{tm}, null);


              return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(ctx.getSocketFactory(), (hostname, session) -> true), loadBalancerClient,
                      loadBalancedRetryFactory, loadBalancerClientFactory);
          }

          方案二

          另一種方案就是啟用httpclient,并且禁用ssl驗(yàn)證,配置如下

                
                feign:
            httpclient:
              enabled: true
              disable-ssl-validation: true

          自此這個(gè)問(wèn)題解決了,當(dāng)然在使用中更加傾向使用方案二,因?yàn)镕eign默認(rèn)的Client采用的是HttpURLConnection,它沒(méi)有連接池,當(dāng)然你也可以使用okhttp。

          寫(xiě)到最后

          這個(gè)問(wèn)題看起來(lái)簡(jiǎn)單,但是排查起來(lái)還是頗費(fèi)心思,很多細(xì)節(jié)隱藏到了框架之下,所以我想看源碼還是有好處的,因?yàn)榫W(wǎng)上的文章別人的情況可能和你不一樣,與其遨游在各個(gè)文章里面,還不如debug一把。


          瀏覽 32
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九点操逼网 | 99视频在线精品免费观看2 | 国产精品AV一区二区 | 99热超碰在线 | 国产片婬乱18一级毛片视頻 |