Spring Boot 和 Spring Cloud Feign調用服務及傳遞參數踩坑記錄


背景 :在Spring Cloud Netflix棧中,各個微服務都是以HTTP接口的形式暴露自身服務的,因此在調用遠程服務時就必須使用HTTP客戶端。我們可以使用JDK原生的URLConnection、Apache的Http Client、Netty的異步HTTP Client, Spring的RestTemplate。但是,用起來最方便、最優雅的還是要屬Feign了。Feign是一種聲明式、模板化的HTTP客戶端。

  Contronller層通過feignClient調用微服務 獲取所有任務

@Controller
@RequestMapping("tsa/task")
public class TaskController{
    @Autowired
    TaskFeignClient taskFeignClient;

    @PostMapping("/getAll")
    @ResponseBody
    public List<TaskVO> getAll() {
        List<TaskVO> all = taskFeignClient.getAll();
        return all;
    }
}

  @FeignClient用於通知Feign組件對該接口進行代理(不需要編寫接口實現),使用者可直接通過@Autowired注入。

Spring Cloud應用在啟動時,Feign會掃描標有@FeignClient注解的接口,生成代理,並注冊到Spring容器中。生成代理時Feign會為每個接口方法創建一個RequetTemplate對象,該對象封裝了HTTP請求需要的全部信息,請求參數名、請求方法等信息都是在這個過程中確定的,Feign的模板化就體現在這里。
 
@FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
public interface TaskFeignClient {
    @PostMapping(value = "taskApiController/getAll")
    List<TaskVO> getAll();

}

  

  微服務端

@Slf4j
@RestController
@RequestMapping("taskApiController")
public class TaskApiController{

    @Autowired
    private TaskService taskService;

    @PostMapping("/getAll")
    public List<TaskVO> getAll() {
        log.info("--------getAll-----");
        List<TaskVO>  all = taskService.getAll();
        return all;
    }

}

  

坑1:

首先再次強調Feign是通過http協議調用服務的,重點是要理解這句話: 如果FeignClient中的方法有@PostMapping注解,則微服務TaskApiController中對應方法的注解也應當保持一致為@PostMapping
若果不一致,則會報404的錯誤  。
調用失敗后會觸發它的熔斷機制,如果@FeignClient中不寫@FeignClient(fallback = TaskFeignClientDegraded.class),會直接報錯
    11:00:35.686 [http-apr-8086-exec-8] DEBUG c.b.p.m.b.c.AbstractBaseController - Got an exception
com.netflix.hystrix.exception.HystrixRuntimeException: TaskFeignClient#getAll() failed and no fallback available.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)

 

坑2:

這個是最慘的了,自己寫好的微服務沒有運行起來,然后自己的客戶端調用這個服務怎么也調用不成功還不知道問題在哪
當時自己微服務運行后,控制台如下
Process finished with exit code 0
我以前以為Process finished with exit code 1才是運行失敗的意思 ,
所以只要出現 Process finished with exit code 就說明運行失敗
服務成功啟動的標志為
11:29:16.483 [restartedMain] INFO  c.b.p.ms.tsa.TsaServiceApplication - Started TsaServiceApplication in 37.132 seconds (JVM running for 39.983)。
 

 坑3

報錯:RequestParam.value() was empty on parameter 0

  如果在FeignClient中的方法有參數傳遞一般要加@RequestParam(“xxx”)注解

  錯誤寫法:

@FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
public interface TaskFeignClient {
    @PostMapping(value = "taskApiController/getAll")
    List<TaskVO> getAll(String name);
}

//或者
@FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
public interface TaskFeignClient {
    @PostMapping(value = "taskApiController/getAll")
    List<TaskVO> getAll(@RequestParam String name);
}

  正確寫法:

@PostMapping(value = "taskApiController/getAll")
List<TaskVO> getAll(@RequestParam("name") String name);

  微服務那邊可以不寫這個注解 

  參考 https://blog.csdn.net/jxm007love/article/details/80109974

 
  疑問:在 SpringMVC 和 Springboot 中都可以使用 @RequestParam 注解,不指定 value 的用法,為什么到了 Spring cloud 中的 Feign 這里就不行了呢?
這是因為和 Feign 的實現有關。Feign 的底層使用的是 httpclient,在低版本中會產生這個問題,聽說高版本中已經對這個問題修復了。
 

坑四:

  FeignClient中post傳遞對象和consumes = "application/json",按照坑三的意思,應該這樣寫。

@FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
public interface TaskFeignClient {
    @PostMapping(value = "taskApiController/getAll")
    List<TaskVO> getAll(@RequestParam("vo") TaskVO vo);

}

  很意外報錯

    16:00:33.770 [http-apr-8086-exec-1] DEBUG c.b.p.a.s.PrimusCasAuthenticationFilter - proxyReceptorRequest = false
    16:00:33.770 [http-apr-8086-exec-1] DEBUG c.b.p.a.s.PrimusCasAuthenticationFilter - proxyTicketRequest = false
    16:00:33.770 [http-apr-8086-exec-1] DEBUG c.b.p.a.s.PrimusCasAuthenticationFilter - requiresAuthentication = false
    16:00:34.415 [hystrix-service-tsa-2] DEBUG c.b.p.m.b.f.PrimusSoaFeignErrorDecoder - error json:{
    "timestamp":1543564834395,
    "status":500,
    "error":"Internal Server Error",
    "exception":"org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException","message":"Failed to convert value of type 'java.lang.String' to required type 'com.model.tsa.vo.TaskVO';
    nested exception is java.lang.IllegalStateException:
    Cannot convert value of type 'java.lang.String' to required type 'com.model.tsa.vo.TaskVO':
    no matching editors or conversion strategy found","path":"/taskApiController/getAll"  }
  開着錯誤信息想了半天突然想明白了,Feign本質是通過http 請求的,http怎么能直接傳遞對象呢,一般都是把對象轉換為json通過post請求傳遞的
  正確寫法應當如下
@FeignClient(qualifier = "taskFeignClient", name = "service-tsa",fallback = TaskFeignClientDegraded.class)
public interface TaskFeignClient {
    @PostMapping(value = "taskApiController/getAll",,consumes = "application/json")
    List<TaskVO> getAll(TaskVO vo);
}

  也可以這樣寫

  @PostMapping(value = "taskApiController/getAll")
  List<TaskVO> getAll(@RequestBody TaskVO vo);

  此時不用,consumes = "application/json"

但是第一種寫法最正確的    因為FeignClient是在我們本地直接調用的,根本不需要這個注解,Controller調用方法的時候就是直接將對象傳給FeignClient,而FeignClient通過http調用服務時則需要將對象轉換成json傳遞。
 
微服務這邊如下
@Slf4j
@RestController
@RequestMapping("taskApiController")
public class TaskApiController{
    @Autowired
    private TaskService taskService;
 
    @PostMapping("/getAll")
    public List<TaskVO> getAll(@RequestBody TaskVO vo) {
        log.info("--------getAll-----");
        List<TaskVO>  all = taskService.getAll();
        return all;
    }
}

  我第一次寫這個的時候方法參數里面什么注解都沒加,可以正常跑通,但是傳過去的對象卻為初始值,實際上那是因為對象根本就沒傳

 
如果用get方法傳遞對象呢?
  參考:
  當然還是推薦使用post請求傳遞對象的
 

傳遞對象的另一種方法和多參傳遞

1、GET請求多參數的URL
假設我們請求的URL包含多個參數,例如http://microservice-provider-user/get?id=1&username=張三 ,要怎么辦呢?
我們知道Spring Cloud為Feign添加了Spring MVC的注解支持,那么我們不妨按照Spring MVC的寫法嘗試一下:
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
  @RequestMapping(value = "/get", method = RequestMethod.GET)
  public User get0(User user);
}

  然而我們測試時會發現該寫法不正確,我們將會收到類似以下的異常:

feign.FeignException: status 405 reading UserFeignClient#get0(User); content:
{"timestamp":1482676142940,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/get"}
由異常可知,盡管指定了GET方法,Feign依然會發送POST請求。
正確寫法如下:
(1) 方法一
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {

  @RequestMapping(value = "/get", method = RequestMethod.GET)
  public User get1(@RequestParam("id") Long id, @RequestParam("username") String username);

}

  這是最為直觀的方式,URL有幾個參數,Feign接口中的方法就有幾個參數。使用@RequestParam注解指定請求的參數是什么。

(2) 方法二

@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
  @RequestMapping(value = "/get", method = RequestMethod.GET)
  public User get2(@RequestParam Map<String, Object> map);

}

  多參數的URL也可以使用Map去構建

當目標URL參數非常多的時候,可使用這種方式簡化Feign接口的編寫。

 

POST請求包含多個參數

  下面我們來討論如何使用Feign構造包含多個參數的POST請求。實際就是坑四,把參數封裝成對象傳遞過去就可以了。

 

最后拓展一下

Feign的Encoder、Decoder和ErrorDecoder

  Feign將方法簽名中方法參數對象序列化為請求參數放到HTTP請求中的過程,是由編碼器(Encoder)完成的。同理,將HTTP響應數據反序列化為java對象是由解碼器(Decoder)完成的。
默認情況下,Feign會將標有@RequestParam注解的參數轉換成字符串添加到URL中,將沒有注解的參數通過Jackson轉換成json放到請求體中。
注意,如果在@RequetMapping中的method將請求方式指定為get,那么所有未標注解的參數將會被忽略,例如:
@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);

  此時因為聲明的是GET請求沒有請求體,所以obj參數就會被忽略。

 
在Spring Cloud環境下,Feign的Encoder*只會用來編碼沒有添加注解的參數*。如果你自定義了Encoder, 那么只有在編碼obj參數時才會調用你的Encoder。對於Decoder, 默認會委托給SpringMVC中的MappingJackson2HttpMessageConverter類進行解碼。只有當狀態碼不在200 ~ 300之間時ErrorDecoder才會被調用。ErrorDecoder的作用是可以根據HTTP響應信息返回一個異常,該異常可以在調用Feign接口的地方被捕獲到。我們目前就通過ErrorDecoder來使Feign接口拋出業務異常以供調用者處理。
 

Feign的HTTP Client

   https://blog.csdn.net/neosmith/article/details/52449921
         Feign在默認情況下使用的是JDK原生的URLConnection發送HTTP請求,沒有連接池,但是對每個地址會保持一個長連接,即利用HTTP的persistence connection 。我們可以用Apache的HTTP Client替換Feign原始的http client, 從而獲取連接池、超時時間等與性能息息相關的控制能力。Spring Cloud從Brixtion.SR5版本開始支持這種替換,首先在項目中聲明Apache HTTP Client和feign-httpclient依賴:
<!-- 使用Apache HttpClient替換Feign原生httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>${feign-httpclient}</version>
        </dependency>

  

然后在application.properties中添加:
feign.httpclient.enabled=true

通過Feign, 我們能把HTTP遠程調用對開發者完全透明,得到與調用本地方法一致的編碼體驗。這一點與阿里Dubbo中暴露遠程服務的方式類似,區別在於Dubbo是基於私有二進制協議,而Feign本質上還是個HTTP客戶端。如果是在用Spring Cloud Netflix搭建微服務,那么Feign無疑是最佳選擇。

 

原文鏈接:

  https://blog.csdn.net/uotail/article/details/84673347


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM