一碌奉、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
三闰渔、簡單示例
- 在程序的啟動類加上注解@EnableFeignClients開啟Feign Client功能
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
- RPC調(diào)用
@Component
@FeignClient(value = "USER-SERVICE")
public interface UserFeign {
@GetMapping("/getUser")
String getUser();
}
- 測試
@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)證攔截器
- 創(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)用睬棚。
- 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ù)
- 提供端與服務(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
- 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>
- 配置
# okhttp替換Feign原生httpclient
feign.okhttp.enabled= true
- 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
- 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>
- 配置
# HttpClient替換Feign原生httpclient
feign.httpclient.enabled= true
- 測試(可以使用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);
十四减细、常見問題
- 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>