SpringCloud--Feign RPC調(diào)用(五)

一碌奉、Feign簡介

??Feign是Netflix開發(fā)的聲明式劝贸、模板化的HTTP客戶端潮罪, Feign可以幫助我們更快捷康谆、優(yōu)雅地調(diào)用HTTP API。
在Spring Cloud中错洁,使用Feign非常簡單——創(chuàng)建一個接口秉宿,并在接口上添加一些注解,代碼就完成了屯碴。Feign支持多種注解描睦,例如Feign自帶的注解或者JAX-RS注解等。
??Spring Cloud對Feign進(jìn)行了增強(qiáng)导而,使Feign支持了Spring MVC注解忱叭,并整合了Ribbon和Eureka,從而讓Feign的使用更加方便今艺。
??Spring Cloud Feign是基于Netflix feign實(shí)現(xiàn)韵丑,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供這兩者的強(qiáng)大功能外虚缎,還提供了一種聲明式的Web服務(wù)客戶端定義的方式撵彻。
??Spring Cloud Feign幫助我們定義和實(shí)現(xiàn)依賴服務(wù)接口的定義。在Spring Cloud feign的實(shí)現(xiàn)下实牡,只需要創(chuàng)建一個接口并用注解方式配置它陌僵,即可完成服務(wù)提供方的接口綁定,簡化了在使用Spring Cloud Ribbon時自行封裝服務(wù)調(diào)用客戶端的開發(fā)量创坞。
??Spring Cloud Feign具備可插拔的注解支持碗短,支持Feign注解、JAX-RS注解和Spring MVC的注解题涨。

文檔地址:
https://cloud.spring.io/spring-cloud-openfeign/spring-cloud-openfeign.html

二偎谁、Maven依賴

<!--web依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--客戶端依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--openfeign依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

三总滩、配置文件

server.port=8008
eureka.register.port=8761
#服務(wù)實(shí)例名
eureka.instance.hostname=order-service
#服務(wù)名,如果沒有則為 unknown
spring.application.name=order-service

eureka.register.host=localhost
eureka.client.serviceUrl.defaultZone=http\://${eureka.register.host}\:${eureka.register.port}/eureka/
#顯示IP
eureka.instance.prefer-ip-address=true
# 注2.0 必須為ip-address
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#Eureka客戶端向服務(wù)端發(fā)送心跳的時間間隔巡雨,單位為秒(客戶端告訴服務(wù)端自己會按照該規(guī)則)
eureka.instance.lease-renewal-interval-in-seconds =3

三闰渔、簡單示例

  1. 在程序的啟動類加上注解@EnableFeignClients開啟Feign Client功能
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
  1. RPC調(diào)用
@Component
@FeignClient(value = "USER-SERVICE")
public interface UserFeign {
    @GetMapping("/getUser")
    String getUser();
}

  1. 測試
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class OrderServiceApplicationTests {
    @Autowired
    private UserFeign userFeign;

    @Test
    public void contextLoads() {
        String str = userFeign.getUser();
        log.info(str);
    }
}

四、Feign 基本使用

@FeignClient 注解
name:指定 Feign Client 的名稱铐望,如果項(xiàng)目使用了 Ribbon澜建,name 屬性會作為微服務(wù)的名稱,用于服務(wù)發(fā)現(xiàn)蝌以。
url:url 一般用于調(diào)試炕舵,可以手動指定 @FeignClient 調(diào)用的地址。
decode404:當(dāng)發(fā)生404錯誤時跟畅,如果該字段為 true咽筋,會調(diào)用 decoder 進(jìn)行解碼,否則拋出 FeignException徊件。
configuration:Feign 配置類奸攻,可以自定義 Feign 的 Encoder、Decoder虱痕、LogLevel睹耐、Contract。
fallback:定義容錯的處理類部翘,當(dāng)調(diào)用遠(yuǎn)程接口失敗或超時時硝训,會調(diào)用對應(yīng)接口的容錯邏輯,fallback 指定的類必須實(shí)現(xiàn) @FeignClient 標(biāo)記的接口新思。
fallbackFactory:工廠類窖梁,用于生成 fallback 類示例,通過這個屬性我們可以實(shí)現(xiàn)每個接口通用的容錯邏輯夹囚,減少重復(fù)的代碼纵刘。
path:定義當(dāng)前 FeignClient 的統(tǒng)一前綴。

@FeignClient(name = "product-provider")  
public interface ProductServiceFeign {  
  
    /** 
     * PathVariable 注解使用時荸哟,必須里面要有值,即@PathVariable("")或@PathVariable(name="") 
     * 
     * @param productId 
     * @return 
     */  
    @GetMapping("product/selectOne/{productId}")  
    ProductVO selectByProductId(@PathVariable("productId") String productId);  
  
    /** 
     * 去掉 @RequestParam 注解將變成post請求,加上為get請求 
     * 
     * @param params 
     * @return 
     */  
    @GetMapping("product/selectByProductIdAndName")  
    Map<String, Object> selectByProductIdAndNameMap(@RequestParam Map<String, Object> params);  
  
//  程序啟動報錯假哎,存在多個參數(shù)沒有@requestParam注解  
//  @GetMapping("product/selectByProductIdAndName")  
//  Map<String, Object> selectByProductIdAndName(String productId, String productName);  
  
//  程序啟動報錯,沒有指定value的值鞍历,且參數(shù)不是map  
//  @GetMapping("product/selectByProductIdAndName")  
//  Map<String, Object> selectByProductIdAndName(@RequestParam String productId, String productName);  
  
//  由于后面有一個參數(shù)沒有加上@RequestParam注解舵抹,此時這個請求就變成了post請求發(fā)送,即使申明的是get請求  
//  @GetMapping("product/selectByProductIdAndName")  
//  Map<String, Object> selectByProductIdAndName(@RequestParam("productId") String productId, String productName);  
  
    @GetMapping("product/selectByProductIdAndName")  
    Map<String, Object> selectByProductIdAndName(@RequestParam("productId") String productId, @RequestParam("productName") String productName);  
  
    @PostMapping("product/addProduct")  
    Map<String, Object> addProduct(@RequestBody ProductVO productVO);  
  
    @PostMapping("product/updateProduct")  
    Map<String, Object> updateProduct(@RequestParam Map<String, Object> params);  
  
    @PostMapping("product/delete")  
    Map<String, Object> delteProduct(@RequestParam("productId") String productId);  
  
}  

五堰燎、Feign 自定義配置

把配置文件放到ComponentScan掃描不到的地方掏父。
Configuration配置類

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import feign.Contract;
import feign.Feign;

@Configuration
public class FeignConfig {

    //配置是在FeignClient中的接口中使用Feign自帶的注解
    @Bean
    public Contract feignContract(){
        return new feign.Contract.Default();
   }
    
    //禁用Hystrix
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    } 
}

然后在UserFeignClient類中指定configuration:

@FeignClient(value = "user-service",configuration = Configuration.class)

第一個bean配置的是使用Feign的默認(rèn)注解笋轨。
注意秆剪,我們在此類中修改了Feign的Contract 赊淑,那么Contract 是什么呢。它叫做契約仅讽。因?yàn)镕eign一開始使用的契約是SpringMVC陶缺,所以剛才我們SpringMVC的注解的時候直接成功了,但是你如果現(xiàn)在啟動項(xiàng)目你就會發(fā)現(xiàn)已經(jīng)啟動不了了洁灵。因?yàn)镃ontract.Default()使用的契約是Feign自己的饱岸,也就是說我們要把SpringMVC的注解修改為Feign的注解。
第二個bean配置的是是禁用Hystrix
SpringMVC版本

@GetMapping (value = "/user/getUser/{id}")
public User getUser(@PathVariable("id")Long id);

Feign版本

@RequestLine("GET /user/getUser/{id}")
 public User getUser(@Param("id") Long id);

六徽千、Feign輸出日志

Feign日志輸出說明
Feign的日志是以下部分組成
1苫费、Feign的Level日志級別配置默認(rèn)是:NONE,不要跟log日志級別混淆
日志級別枚舉類 Logger.Level
NONE 不輸出日志
BASIC 只有請求方法双抽、URL百框、響應(yīng)狀態(tài)代碼、執(zhí)行時間
HEADERS基本信息以及請求和響應(yīng)頭
FULL 請求和響應(yīng) 的heads牍汹、body铐维、metadata,建議使用這個級別
2慎菲、log日志級別配置嫁蛇,默認(rèn)是debug
使用指定Feign類會包名配置日志打印級別
此處使用spring logging配置 比如打印 UserFeign logging.level.com.example.feign.UserFeign=debug
Feign日志輸出-Logger.Level.FULL+log debug級別
全局開啟方式 使用spring java config 配置,注意該類一定要放到spring可以掃描到的包下:

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLevel() {
        return Logger.Level.FULL;
    }
}

application.properties 配置debug 日志輸出級別

user.url=http://localhost:8080/user
logging.level.com.example.feign.UserFeign=debug

運(yùn)行 UserFeignTest.save方法 可以看到以下保存user輸出的請求和響應(yīng)日志

2018-08-07 16:48:03.011 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] ---> POST http://localhost:8080/user HTTP/1.1
2018-08-07 16:48:03.011 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] Content-Type: application/json;charset=UTF-8
2018-08-07 16:48:03.011 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] Content-Length: 27
2018-08-07 16:48:03.011 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] 
2018-08-07 16:48:03.011 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] {"id":null,"name":"張三"}
2018-08-07 16:48:03.012 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] ---> END HTTP (27-byte body)
2018-08-07 16:48:03.041 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] <--- HTTP/1.1 200 (29ms)
2018-08-07 16:48:03.042 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] content-length: 0
2018-08-07 16:48:03.043 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] date: Tue, 07 Aug 2018 08:48:03 GMT
2018-08-07 16:48:03.043 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] 
2018-08-07 16:48:03.043 DEBUG 75661 --- [           main] com.example.feign.UserFeign              : [UserFeign#save] <--- END HTTP (0-byte body)

七露该、配置請求用戶名密碼

在項(xiàng)目中,微服務(wù)之間的通信也是通過Feign代理的HTTP客戶端通信,為了保護(hù)我們的業(yè)務(wù)微服務(wù)不被其他非法未經(jīng)允許的服務(wù)調(diào)用, 我們要進(jìn)行訪問授權(quán)配置!
Feign是客戶端配置,@FeignClient注解有個configuation屬性,可以配置我們自定義的配置類,在此類中注入微服務(wù)認(rèn)證攔截器

  1. 創(chuàng)建Feign的配置類
@Configuration
public class FeignConfiguration {
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor(){
        return new BasicAuthRequestInterceptor("admin","123456");
    }
}

說明:@Bean配置是在請求接口中要進(jìn)行基于Http Basic的認(rèn)證后才能調(diào)用睬棚。

  1. FeignClient接口引用配置
@Component
@FeignClient(value = "USER-SERVICE", configuration= FeignConfiguration.class)
public interface UserFeign {
    @GetMapping("/getUser")
    String getUser();
}

需要注意的是: FeignConfiguration類不能包含在主應(yīng)用程序上下文的@ComponentScan中,否則該配置會被所有的@FeignClient共享解幼。
Ribbon的自定義配置中也需要注意這個問題闸拿。

八、Feign超時設(shè)置

feign.client.config.springApplicationName.connectTimeout=1000
feign.client.config.springApplicationName.readTimeout=1000

注意:
@GetMapping注解不支持书幕;
@PathVariable注解需要設(shè)置value值新荤;
@RequestParam注解需要設(shè)置value值;
接口參數(shù)是復(fù)雜的JAVA對象的時候台汇,需要采用POST方式請求苛骨,且參數(shù)名前需要添加@RequestBody注解,且需要保證接口提供者的接口訪問方式是POST苟呐;
客戶端的調(diào)用接口的FeignClient接口中痒芝,方法名、參數(shù)名及參數(shù)類型必須和接口方法保持一致牵素;參數(shù)名前必須添加@PathVariable或者@RequestParam注解严衬。
FeignClient注解中沒有寫其他值,則name值只得是服務(wù)提供者的服務(wù)名稱笆呆;如果定義了url请琳,則feignClient會查找對應(yīng)url上的微服務(wù)粱挡,name此時的值是指feignClient的名稱。name值必須填寫俄精,還可以設(shè)置其他的值询筏,如configuration(feignClient配置:默認(rèn)是SpringMvcContract)的值;
多個feignCLient類中@FeignClient注解中的name值不能重復(fù)竖慧,url可以重復(fù)嫌套;

九、對象參數(shù)

  1. 提供端與服務(wù)端都使用POST方法圾旨。
    參數(shù)使用@RequestBody注解踱讨。

十、Feign的Encoder砍的、Decoder和ErrorDecoder

Feign將方法簽名中方法參數(shù)對象序列化為請求參數(shù)放到HTTP請求中的過程勇蝙,是由編碼器(Encoder)完成的。同理挨约,將HTTP響應(yīng)數(shù)據(jù)反序列化為java對象是由解碼器(Decoder)完成的味混。
默認(rèn)情況下,F(xiàn)eign會將標(biāo)有@RequestParam注解的參數(shù)轉(zhuǎn)換成字符串添加到URL中诫惭,將沒有注解的參數(shù)通過Jackson轉(zhuǎn)換成json放到請求體中翁锡。注意,如果在@RequetMapping中的method將請求方式指定為POST夕土,那么所有未標(biāo)注解的參數(shù)將會被忽略馆衔。

    @PostMapping(value = "/goods/sku/skus", consumes = MediaType.APPLICATION_JSON_VALUE)
    Result<List<Sku>> getSkus(List<Long> ids);

注:其中參數(shù)中的@RequestBody省略。

十一怨绣、常用注解

注解@EnableFeignClients告訴框架掃描所有使用注解@FeignClient定義的feign客戶端;
注解@FeignClient 定義feign客戶端 ;

@FeignClient(value = "GOODS-SERVICE",path = "/goods/sku")

注解@Autowired使用所定義feign的客戶端角溃。

十二、OkHttp替換Feign原生httpclient

  1. Maven依賴
<!--openfeign依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
  1. 配置
# okhttp替換Feign原生httpclient
feign.okhttp.enabled= true
  1. OkHttp連接池配置
/**
 * OkHttp連接池配置
 */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
    @Bean
    public okhttp3.OkHttpClient okHttpClient(){
        return new okhttp3.OkHttpClient.Builder()
                // 讀取超時時間
                .readTimeout(60, TimeUnit.SECONDS)
                // 連接超時時間
                .connectTimeout(60,TimeUnit.SECONDS)
                .connectionPool(new ConnectionPool())
                .build();
    }
}

十三篮撑、Apache的HTTP Client替換Feign原生httpclient

  1. Maven依賴
<!--openfeign依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 使用Apache HttpClient替換Feign原生httpURLConnection -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.4.0</version>
</dependency>
  1. 配置
# HttpClient替換Feign原生httpclient
feign.httpclient.enabled= true
  1. 測試(可以使用GET方法傳實(shí)體)
    服務(wù)提供:
@GetMapping(value = "/skus",consumes = MediaType.APPLICATION_JSON_VALUE)
public Result<List<Sku>> getSkus(@RequestBody List<Long> ids){
    QueryWrapper<Sku> queryWrapper=new QueryWrapper<>();
    queryWrapper.in("id",ids);
    List<Sku> list=skuService.list(queryWrapper);
    return  Result.ok(list);
}

服務(wù)消費(fèi):

@GetMapping(value = "/goods/sku/skus", consumes = MediaType.APPLICATION_JSON_VALUE)
    Result<List<Sku>> getSkus(List<Long> ids);

十四减细、常見問題

  1. java.lang.NoSuchMethodError: feign.Response.create(ILjava/lang/String;Ljava/util/Map;Lfeign/Response$Body;)Lfeign/Response;
    原因:使用包裝類Result沒有明確指定泛型類型。
    例:
    接口提供端:
@PostMapping(value = "/skus",consumes = MediaType.APPLICATION_JSON_VALUE)
    public Result<List<Sku>> getSkus(@RequestBody List<Long> ids){
}

接口消費(fèi)端:

@PostMapping(value = "/goods/sku/skus", consumes = MediaType.APPLICATION_JSON_VALUE)
    Result<List<Sku>> getSkus(@RequestBody List<Long> ids);

如果使用了HTTP Client替換Feign原生httpclient:
原因?yàn)橐蕾嚥徽_:
使用以下:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.4.0</version>
</dependency>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赢笨,一起剝皮案震驚了整個濱河市未蝌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茧妒,老刑警劉巖萧吠,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桐筏,居然都是意外死亡纸型,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狰腌,“玉大人除破,你說我怎么就攤上這事“┍穑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵蹋笼,是天一觀的道長展姐。 經(jīng)常有香客問我,道長剖毯,這世上最難降的妖魔是什么圾笨? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮逊谋,結(jié)果婚禮上擂达,老公的妹妹穿的比我還像新娘。我一直安慰自己胶滋,他們只是感情好板鬓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著究恤,像睡著了一般俭令。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上部宿,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天抄腔,我揣著相機(jī)與錄音,去河邊找鬼理张。 笑死赫蛇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雾叭。 我是一名探鬼主播悟耘,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼织狐!你這毒婦竟也來了作煌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赚瘦,失蹤者是張志新(化名)和其女友劉穎粟誓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體起意,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹰服,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悲酷。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡套菜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出设易,到底是詐尸還是另有隱情逗柴,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布顿肺,位于F島的核電站戏溺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屠尊。R本人自食惡果不足惜旷祸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讼昆。 院中可真熱鬧托享,春花似錦、人聲如沸浸赫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽既峡。三九已至辫诅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涧狮,已是汗流浹背炕矮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留者冤,地道東北人肤视。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像涉枫,于是被迫代替她去往敵國和親峦朗。 傳聞我的和親對象是個殘疾皇子单绑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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