前文介紹了實現客戶端負載均衡的Ribbon,但直接使用Ribbon的API來實現服務間的調用相對較為繁瑣,服務間的調用能否像本地接口調用一樣便捷、透明,更符合編程習慣呢?Feign就是用來干這事的。
Feign
Feign是一個聲明式的Web服務客戶端,讓服務之間的調用變得非常簡單——定義帶@FeignClient注解的接口,本地直接@Autowired 接口,通過調用接口的方法來實現遠程服務的調用。
支持的注解包括Feign注解與JAX-RS(Java API for RESTful Web Services)注解。
每一個Feign的客戶端都包含一系列對應的組件,Spring Cloud通過FeignClientsConfiguration 為每一個命名的Feign客戶端創建一個組件集合,包括feign.Decoder,feign.Encoder,feign.Contract等。
Feign提供的默認bean實現及說明
| Bean類型 | 默認實現類 | 說明 |
|---|---|---|
| Decoder | ResponseEntityDecoder | ResponseEntityDecoder封裝了SpringDecoder,解碼器,將服務的響應消息進行解碼 |
| Encoder | SpringEncoder | 編碼器 |
| Logger | Slf4jLogger | 日志框架 |
| Contract | SpringMvcContract | 支持注解契約,使用SpringMvcContract可以對Spring MVC注解提供支持 |
| Feign.Builder | HystrixFeign.Builder | 使用斷路器來裝飾Feign接口 |
| Client | LoadBalancerFeignClient | 如果是ribbon則 LoadBalancerFeignClient, 如果是spring cloud LoadBalancer 則 FeignBlockingLoadBalancerClient,默認ribbon |
跟Ribbon類似,可以通過配置類來自定義Feign客戶端,如
@FeignClient(name = "hello-service", configuration = CustomConfiguration.class) public interface StoreClient { //.. } public class CustomConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); } }
這樣Feign客戶端就包含了FeignClientsConfiguration 與CustomConfiguration 中定義的組件,並且后者會覆蓋前者(即自定義配置的優先級高於默認配置)。
自定義配置類不需要加注解@Configuration,如果加了且被@ComponentScan掃描到,則將成為所有Feign客戶端的默認配置
同樣Feign客戶端也支持通過配置文件來配置
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
對於應用於所有Feign客戶端的全局默認配置,也可以通過兩種方式
-
通過@EnableFeignClients 的defaultConfiguration 屬性指定默認配置類
-
在配置文件中通過名稱為default的配置實現
feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
優先級同Ribbon, 配置文件>自定義配置類>默認的FeignClientsConfiguration
案例演示
本文案例演示基於前面搭建的springcloud-eureka 與 springcloud-eureka-client 兩個示例項目。
-
新建springcloud-feign項目,pom.xml中加入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
spring-cloud-starter-openfeign 包含了spring-cloud-starter-netflix-ribbon 與 spring-cloud-starter-loadbalancer。
-
啟動類加上@EnableFeignClients 注解
@SpringBootApplication @EnableFeignClients public class FeignApplication { public static void main(String[] args) { SpringApplication.run(FeignApplication.class, args); } }
-
定義Feign client(feign client支持繼承)
@FeignClient("hello-service") public interface HelloClient extends BaseHelloClient{ @RequestMapping("hello/param") String hello(@SpringQueryMap QueryParam param); } -
調用Feign client
@RestController public class FeignController { @Autowired private HelloClient helloClient; @RequestMapping("feign") public String feignTest(){ return "調用Hello-service返回:" + helloClient.hello(); } @RequestMapping("feign/param") public String feignTestParam(QueryParam param) { return "調用Hello-service返回:" + helloClient.hello(param); } }
依次啟動三個項目,調用http://localhost:8083/feign 能正常返回調用hello-service的結果。
本示例項目還通過@SrpingQueryMap 注解實現了Feign對 pojo用於GET請求參數的支持。如果不加@SrpingQueryMap, 則pojo參數是無法通過Feign client傳遞的,可去掉注解自行驗證下。
一些知識點
-
如果需要定制化產生的查詢參數map,可以實現並注入一個自定義的 QueryMapEncoder bean
-
Feign client的日志可通過feign client接口的全路徑名進行配置,如logging.level.project.user.UserClient: DEBUG,默認為NONE(即不打印日志)。全局設置
@Configuration public class FooConfiguration { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
可設置的level值
-
-
NONE:不記錄日志 ,默認
-
BASIC:只記錄請求方法,url,以及響應狀態碼與執行時間
-
HEADERS:包括BASIC與請求、響應頭
-
FULL:包括請求與響應的headers,body,metadata
-
-
Feign默認使用Ribbon來做負載均衡,可通過配置spring.cloud.loadbalancer.ribbon.enabled=false 來使用spring cloud loadbalancer(目前Ribbon處於維護狀態,近期內不做更新)
-
可通過配置feign.okhttp.enabled=true 或 feign.httpclient.enabled=true 來使用OkHttpClient 或ApacheHttpClient, 默認使用的是JDK 原生的URLConnection 發送HTTP請求,沒有連接池
-
如果需要在RequestInterceptor 中使用ThreadLocal中的變量, 那么要么禁用Hystrix,要么設置hystrix的線程隔離策略為SEMAPHORE
feign: hystrix: enabled: false # 或者 hystrix: command: default: execution: isolation: strategy: SEMAPHORE -
使用有Hystrix fallback的Feign時,會在ApplicationContext中存在多個同類型bean, 導致@Autowired 失效。為了解決這個問題,Spring cloud netflix 將所有feign實例標為@Primary,如果要關閉該特性, 可將@FeignClient的 primary屬性置為false。
@FeignClient(name = "hello", primary = false) public interface HelloClient { // ... }
本文示例代碼:https://github.com/ronwxy/springcloud-demos
認真生活,快樂分享
歡迎關注微信公眾號:空山新雨的技術空間

