Spring Cloud 之 Feign 調(diào)用實例及異常分析

一、簡介

基于 Spring Cloud 的微服務(wù)架構(gòu)星著,各個微服務(wù)之間通過 Feign 調(diào)用。所有微服務(wù)注冊在 Eureka 上,Spring Cloud 將它集成在自己的子項目 spring-cloud-netflix 中甘有,實現(xiàn) Spring Cloud 的「服務(wù)發(fā)現(xiàn)」功能。
在 Spring Cloud Netflix 棧中葡缰,各個微服務(wù)都是以 HTTP 接口的形式暴露自身亏掀,因此在調(diào)用遠程服務(wù)時就必須使用 HTTP 客戶端忱反。我們可以使用 JDK 原生的 URLConnection、Apache 的 Http Client滤愕、Netty 的異步 HTTP Client 以及 Spring 的 RestTemplate温算。當然,用起來最方便的當屬 Feign 了间影。
Feign 是一種聲明式注竿、模板化的 HTTP 客戶端,包含了 Ribbon 和 Hystrix魂贬,支持負載均衡和容錯巩割。在 Spring Cloud 中,創(chuàng)建接口并引用 @FeignClient 注解即可引用 Feign付燥,以實現(xiàn)微服務(wù)間的遠程調(diào)用宣谈。
Feign 工作原理:Spring Cloud 應用在啟動時,先檢查配置是否有@EnableFeignClients 注解键科,如果有該注解闻丑,則開啟包掃描,掃描標有 @FeignClient 注解的接口勋颖,生成代理嗦嗡,并注冊到 Spring 容器中。生成代理時 Feign 為每個接口方法創(chuàng)建一個 RequetTemplate 對象牙言,該對象封裝了 HTTP 請求需要的全部信息酸钦,包括請求參數(shù)名、請求方法等信息咱枉,F(xiàn)eign 的模板化就體現(xiàn)在這里卑硫。

二、Feign 調(diào)用實例

portal-test-service 項目配置:

spring:
  profiles:
    active: test
  application:
    name: portal-test-service
    version: 1.0.0
eureka:
  client:
    service-url:
      defaultZone: http://172.21.11.79:9091/eureka
  status:
    open: true
  instance:
    preferIpAddress: true
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}
    leaseRenewalIntervalInSeconds: 1
    leaseExpirationDurationInSeconds: 2
server:
  port: 9990

paas-test-service 項目配置:

spring:
  profiles:
    active: test
  application:
    name: paas-test-service
    version: 1.0.0
eureka:
  status:
    open: ture
  client:
    service-url:
      defaultZone: http://172.21.11.79:9091/eureka
  instance:
    preferIpAddress: true
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}
    leaseRenewalIntervalInSeconds: 1
    leaseExpirationDurationInSeconds: 2
server:
  port: 8890

paas-test-serviceportal-test-service 是兩個注冊到同一 Eureka 上的微服務(wù)項目蚕断,下面演示 paas-test-service 通過 Feign 遠程調(diào)用 portal-test-service 中的接口欢伏。

1. 添加依賴

paas-test-service 的 pom.xml 文件中添加 spring-cloud-starter-feign 依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

如圖所示:


2. 開啟 Feign

paas-test-service 項目的啟動類中,通過@EnableFeignClients 注解開啟 Feign 的功能:

3. 定義調(diào)用接口

使用 @FeignClient(name = "服務(wù)名") 注解亿乳,來指定調(diào)用哪個服務(wù)硝拧。
@FeignClient 注解的常用屬性如下:

  • name:指被調(diào)用的微服務(wù)名稱,可省略葛假。@FeignClient(name = "服務(wù)名") 亦可寫作 @FeignClient( "服務(wù)名")障陶。
  • value:和 name 互為別名,也是指被調(diào)用的微服務(wù)名稱聊训。@FeignClient(name = "服務(wù)名") 亦可寫作 @FeignClient(value= "服務(wù)名")抱究。
  • url:直接添加硬編碼的路徑。一般用于調(diào)試带斑,可以手動指定 @FeignClient 調(diào)用的地址鼓寺,此時被調(diào)用的服務(wù)可以不注冊到 Eureka 中心上勋拟。
  • configuration:標明 FeignClient 的配置類,使用默認即可妈候。

下面是 paas-test-service 項目中定義的調(diào)用接口敢靡,其中 @GetMapping 注解與 @RequestMapping 注解兩種寫法均可:

@FeignClient(name = "portal-test-service")
public interface IPortalInterface {

    //@RequestMapping(value = "/system/v1/SysUserWsg/queryList", method = RequestMethod.GET)
    @GetMapping("/system/v1/SysUserWsg/queryList")
    @ResponseBody
    PortalResult queryListByObj(@RequestParam("id") String id);

}

示例如圖:


下面是 portal-test-service 項目中被調(diào)用的方法,其中 PortalResult 對象與 WsgResult 對象屬性一致:

    @RequestMapping(value = "/system/v1/SysUserWsg/queryList", method = RequestMethod.GET)
    @ResponseBody
    public WsgResult queryListByObj(@RequestParam String id) {
        SysUserDTO sysUserDTO = BeanConvertor.getCopyObject(SysUserDTO.class, new SysUserVO());
        WsgResult restRe = new WsgResult();
        List<SysUserDTO> list = new ArrayList<SysUserDTO>();
        try {
            list = sysUserAppImpl.queryListByObj(sysUserDTO);
        } catch (PortalBaseException e) {
            e.printStackTrace();
            restRe.setRetCode(e.getRetCode());
            restRe.setRetMsg(e.getRetMsg());
        }
        restRe.setData(new AppData(list));
        return restRe;
    }

注意兩點:

  • 第一苦银,請求方式啸胧、請求路徑必須與被調(diào)用接口保持一致。
  • 第二幔虏,雖然 Feign 服務(wù)客戶端中的接口名吓揪、返回對象可以任意定義,但對象中的屬性類型和屬性名必須與被調(diào)用接口保持一致所计。

4. 添加消費方法

聲明接口之后,在代碼中通過 @Resource 或 @Autowired 注入即可使用团秽。
paas-test-service 項目中主胧,新建一個 PortalTestController.java 類,引用 @Resource 注解引入上面定義的 IPortalInterface 接口习勤,代碼示例如下:

5. 啟動項目

本地啟動這兩個項目踪栋,啟動成功如下:

2018-11-05 23:33:20.142 [main] INFO  [org.apache.coyote.http11.Http11NioProtocol] - Initializing ProtocolHandler ["http-nio-9990"]
2018-11-05 23:33:20.198 [main] INFO  [org.apache.coyote.http11.Http11NioProtocol] - Starting ProtocolHandler ["http-nio-9990"]
2018-11-05 23:33:20.249 [main] INFO  [org.apache.tomcat.util.net.NioSelectorPool] - Using a shared selector for servlet write/read
2018-11-05 23:33:20.448 [main] INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] - RESTEASY002225: Deploying javax.ws.rs.core.Application: class com.sitech.fw.core.spring.boot.autoconfigure.ResteasyApplication
2018-11-05 23:33:20.451 [main] INFO  [org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer] - Tomcat started on port(s): 9990 (http)
2018-11-05 23:33:20.461 [main] INFO  [org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration] - Updating port to 9990
2018-11-05 23:33:20.477 [main] INFO  [com.sitech.cmap.wsg.system.PortalLoginServiceApplication] - Started PortalLoginServiceApplication in 74.286 seconds (JVM running for 78.392)

登錄 Eureka 中心,可看到這兩個項目已成功注冊上去:


6. 測試 Feign 調(diào)用

首先图毕,在 paas-test-service 項目的 PortalTestController.java 類中的消費方法上夷都、portal-test-service 項目的被調(diào)用方法上,分別打上斷點予颤,如圖:


然后囤官,網(wǎng)頁上訪問 paas-test-service 項目的消費方法:

http://172.21.11.79:9191/paas-test-service/v1/portal_test/users/list?id=1

可以見到,依次經(jīng)過所設(shè)定的斷點蛤虐。也就是說党饮,我們訪問 paas-test-service 微服務(wù),然后通過 Feign 的遠程調(diào)用驳庭,實現(xiàn)了對 portal-test-service 的訪問刑顺。如下圖:



nice!頁面成功返回數(shù)據(jù)饲常,測試 Feign 完畢蹲堂!

三、Feign 調(diào)用異常分析

Spring Cloud 之 Feign 作為 HTTP 客戶端調(diào)用遠程服務(wù)贝淤,常見的異常主要有以下兩類柒竞。

1. feign.FeignException: status 404 reading

說明找不到被調(diào)用的方法,也就是你定義的 Feign 客戶端接口與被調(diào)用接口不一致霹娄。要么是請求方式能犯、請求路徑不匹配鲫骗,要么就是參數(shù)不匹配,只要認真核對踩晶,不難糾正錯誤执泰。

下面舉一個自己曾經(jīng)犯錯的例子:
portal-test-service 項目中,指定了 context-path 屬性渡蜻,調(diào)用 portal-test-service 接口會加上 /portalWsg 前綴术吝。

server:
  port: 9990
  context-path: /portalWsg

然而,我在定義 Feign 接口的時候茸苇,忘記加上 /portalWsg 前綴排苍,代碼如下:

@FeignClient(name = "portal-test-service")
public interface IPortalInterface {

    @RequestMapping(value = "/system/v1/SysUserWsg/queryList", method = RequestMethod.GET)
    @ResponseBody
    PortalResult queryListByObj(@RequestParam("id") String id);

}

于是報錯,截取部分信息如下:

feign.FeignException: status 404 reading IPortalInterface#queryListByObj(String)
    at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-9.5.0.jar:?]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-9.5.0.jar:?]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-9.5.0.jar:?]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.5.0.jar:?]
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-9.5.0.jar:?]
    at com.sun.proxy.$Proxy296.queryListByObj(Unknown Source) ~[?:?]
    at com.sitech.cmap.paasplatform.wsg.controller.workorder.PortalTestController.listUsers(PortalTestController.java:33) ~[classes/:?]
    at com.sitech.cmap.paasplatform.wsg.controller.workorder.PortalTestController$$FastClassBySpringCGLIB$$b7108cc2.invoke(<generated>) ~[classes/:?]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at com.sitech.cmap.wsg.common.aspect.WsgResultAspect.handlerControllerMethod(WsgResultAspect.java:36) [wsg-extension-3.1.0-SNAPSHOT.jar:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_91]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_91]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) [spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at com.sitech.cmap.paasplatform.wsg.controller.workorder.PortalTestController$$EnhancerBySpringCGLIB$$dd10d04f.listUsers(<generated>) [classes/:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_91]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_91]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]

路徑同時加上 /portalWsg 前綴学密,問題便得到解決淘衙!

@FeignClient(name = "portal-test-service")
public interface IPortalInterface {

    @RequestMapping(value = "/portalWsg/system/v1/SysUserWsg/queryList", method = RequestMethod.GET)
    @ResponseBody
    PortalResult queryListByObj(@RequestParam("id") String id);

}

2. Read timed out executing

調(diào)用服務(wù)超時。有時候可能數(shù)據(jù)庫數(shù)據(jù)量大或其他原因腻暮,使得遠程調(diào)用的時間超過 Feign 的默認超時時間彤守,便會拋出該異常。

下面演示一個導致該bug的例子:
往 sys_user 表中插入大量的用戶數(shù)據(jù)哭靖,然后訪問 paas-test-service具垫,報錯如下:


通過設(shè)置 Feign 的超時時間可解決問題。Feign 的調(diào)用分兩層试幽,Ribbon 的調(diào)用和 Hystrix 的調(diào)用筝蚕,高版本的 Hystrix 默認是關(guān)閉的,所以設(shè)置 Ribbon 即可铺坞。
(了解更多請參考『Feign 配置詳解』起宽。)
配置文件中添加配置如下:

#請求處理的超時時間
#ribbon.ReadTimeout: 120000
portal-test-service.ribbon.ReadTimeout: 120000
#請求連接的超時時間
#ribbon.ConnectTimeout: 30000
portal-test-service.ribbon.ConnectTimeout: 30000

重啟項目,再次訪問接口康震,返回數(shù)據(jù)成功燎含。Perfect!



--------------------------------------我是華麗的分割線--------------------------------------
補充異常

3. status 404 reading 之 Request method 'POST' not supported腿短。

使用 Feign 遠程調(diào)用 Get 請求不支持通過 @RequestBody 注解傳遞參數(shù)導致屏箍。
添加 feign-httpclient 依賴即可(親測有效,詳情參見 「'POST' not supported 」)橘忱。

         <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赴魁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钝诚,更是在濱河造成了極大的恐慌颖御,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潘拱,居然都是意外死亡疹鳄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門芦岂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘪弓,“玉大人,你說我怎么就攤上這事禽最∠偾樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵川无,是天一觀的道長呛占。 經(jīng)常有香客問我,道長懦趋,這世上最難降的妖魔是什么晾虑? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮仅叫,結(jié)果婚禮上走贪,老公的妹妹穿的比我還像新娘。我一直安慰自己惑芭,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布继找。 她就那樣靜靜地躺著遂跟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婴渡。 梳的紋絲不亂的頭發(fā)上幻锁,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音边臼,去河邊找鬼哄尔。 笑死,一個胖子當著我的面吹牛柠并,可吹牛的內(nèi)容都是我干的岭接。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼臼予,長吁一口氣:“原來是場噩夢啊……” “哼鸣戴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粘拾,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤窄锅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缰雇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體入偷,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡追驴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疏之。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殿雪。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖体捏,靈堂內(nèi)的尸體忽然破棺而出冠摄,到底是詐尸還是另有隱情,我是刑警寧澤几缭,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布河泳,位于F島的核電站,受9級特大地震影響年栓,放射性物質(zhì)發(fā)生泄漏拆挥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一某抓、第九天 我趴在偏房一處隱蔽的房頂上張望纸兔。 院中可真熱鬧,春花似錦否副、人聲如沸汉矿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洲拇。三九已至,卻和暖如春曲尸,著一層夾襖步出監(jiān)牢的瞬間赋续,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工另患, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纽乱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓昆箕,卻偏偏與公主長得像鸦列,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鹏倘,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理敛熬,服務(wù)發(fā)現(xiàn),斷路器第股,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • ?通過前面兩章對Spring Cloud Ribbon和Spring Cloud Hystrix的介紹应民,我們已經(jīng)掌...
    Chandler_玨瑜閱讀 213,024評論 15 140
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,804評論 6 342
  • 軟件是有生命的,你做出來的架構(gòu)決定了這個軟件它這一生是坎坷還是幸福。 本文不是講解如何使用Spring Cloud...
    Bobby0322閱讀 22,648評論 3 166
  • 今年家鄉(xiāng)的雪诲锹,照往年晚了些繁仁。但它到來的時候還是那么美,那么動人归园。
    請叫我劉小欠_862閱讀 186評論 0 0