Feign 是Spring Cloud Netflix組件中的一個(gè)輕量級(jí)Restful的 HTTP 服務(wù)客戶端,實(shí)現(xiàn)了負(fù)載均衡和 Rest 調(diào)用的開源框架,封裝了Ribbon和RestTemplate, 實(shí)現(xiàn)了WebService的面向接口編程,進(jìn)一步降低了項(xiàng)目的耦合度。
(相關(guān)資料圖)
先來看我們以前利用RestTemplate發(fā)起遠(yuǎn)程調(diào)用的代碼:
String?url?=?"http://user-service:8081/user/"+order.getUserId();User?user?=?restTemplate.getForObject(url,?User.class);
以上的代碼存在參數(shù)復(fù)雜、URL難以維護(hù)等問題,如當(dāng)我有一臺(tái)服務(wù)地址換了,那么這時(shí)候就需要云同步修改url,那要是多臺(tái)要修改的情況下那就得改很多臺(tái),當(dāng)我們服務(wù)多的時(shí)候這是個(gè)很麻煩的事情。
1.Feign替代RestTemplate
Fegin的使用步驟如下:
1)引入依賴
我們?cè)趏rder-service服務(wù)的pom文件中引入feign的依賴:
<dependency>??<groupId>org.springframework.cloud</groupId>??<artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
2)添加注解
在order-service的啟動(dòng)類添加注解開啟Feign的功能:
3)編寫Feign的客戶端
在order-service中新建一個(gè)接口,內(nèi)容如下:
package?cn.itcast.order.service.feign;import?cn.itcast.order.pojo.User;import?org.springframework.cloud.openfeign.FeignClient;import?org.springframework.web.bind.annotation.GetMapping;import?org.springframework.web.bind.annotation.PathVariable;@FeignClient("user-service")public?interface?UserFeignClient?{????@GetMapping("/user/{id}")????User?queryById(@PathVariable("id")?Long?id);}
這個(gè)客戶端主要是基于SpringMVC的注解來聲明遠(yuǎn)程調(diào)用的信息,比如:
服務(wù)名稱:user-service
請(qǐng)求方式:GET
請(qǐng)求路徑:/user/{id}
請(qǐng)求參數(shù):Long id
返回值類型:User
這樣,F(xiàn)eign就可以幫助我們發(fā)送http請(qǐng)求,無需自己使用RestTemplate來發(fā)送了。底層會(huì)通過服務(wù)名稱:user-service去映射到具體的user服務(wù)對(duì)應(yīng)的url地址。
4)測試
修改order-service中的OrderService類中的queryOrderById方法,使用Feign客戶端代替RestTemplate:
@Servicepublic?class?OrderService?{????@Autowired????private?OrderMapper?orderMapper;????@Autowired????private?RestTemplate?restTemplate;????@Autowired????UserFeignClient?feignClient;????public?Order?queryOrderById(Long?orderId)?{????????//?1.查詢訂單????????Order?order?=?orderMapper.findById(orderId);????????User?user?=?feignClient.queryById(orderId);????????order.setUser(user);????????//?4.返回????????return?order;????}}
測試調(diào)用:
2.自定義配置
Feign可以支持很多的自定義配置,如下表所示:
類型
作用
說明
feign.Logger.Level
修改日志級(jí)別
包含四種不同的級(jí)別:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder
響應(yīng)結(jié)果的解析器
http遠(yuǎn)程調(diào)用的結(jié)果做解析,例如解析json字符串為java對(duì)象
feign.codec.Encoder
請(qǐng)求參數(shù)編碼
將請(qǐng)求參數(shù)編碼,便于通過http請(qǐng)求發(fā)送
feign. Contract
支持的注解格式
默認(rèn)是SpringMVC的注解
feign. Retryer
失敗重試機(jī)制
請(qǐng)求失敗的重試機(jī)制,默認(rèn)是沒有,不過會(huì)使用Ribbon的重試
一般情況下,默認(rèn)值就能滿足我們使用,如果要自定義時(shí),只需要?jiǎng)?chuàng)建自定義的@Bean覆蓋默認(rèn)Bean即可。
下面以日志為例來演示如何自定義配置。
2.1.配置文件方式
基于配置文件修改feign的日志級(jí)別可以針對(duì)單個(gè)服務(wù):(注意:有時(shí)yml配置文件中有中文注釋會(huì)報(bào)錯(cuò))
feign:????client:????config:???????user-service:?#?針對(duì)某個(gè)微服務(wù)的配置????????loggerLevel:?FULL?#??日志級(jí)別
也可以針對(duì)所有服務(wù):
feign:????client:????config:???????default:?#?這里用default就是全局配置,如果是寫服務(wù)名稱,則是針對(duì)某個(gè)微服務(wù)的配置????????loggerLevel:?FULL?#??日志級(jí)別
而日志的級(jí)別分為四種:
NONE:不記錄任何日志信息,這是默認(rèn)值。
BASIC:僅記錄請(qǐng)求的方法,URL以及響應(yīng)狀態(tài)碼和執(zhí)行時(shí)間
HEADERS:在BASIC的基礎(chǔ)上,額外記錄了請(qǐng)求和響應(yīng)的頭信息
FULL:記錄所有請(qǐng)求和響應(yīng)的明細(xì),包括頭信息、請(qǐng)求體、元數(shù)據(jù)。
2.2.Java代碼方式
也可以基于Java代碼來修改日志級(jí)別,先聲明一個(gè)類,然后聲明一個(gè)Logger.Level的對(duì)象:
public?class?DefaultFeignConfiguration??{????@Bean????public?Logger.Level?feignLogLevel(){????????return?Logger.Level.BASIC;?//?日志級(jí)別為BASIC????}}
如果要全局生效,將其放到啟動(dòng)類的@EnableFeignClients這個(gè)注解中:
package?cn.itcast.order;import?cn.itcast.order.config.DefaultFeignConfiguration;import?com.netflix.loadbalancer.IRule;import?com.netflix.loadbalancer.RandomRule;import?org.mybatis.spring.annotation.MapperScan;import?org.springframework.boot.SpringApplication;import?org.springframework.boot.autoconfigure.SpringBootApplication;import?org.springframework.cloud.client.loadbalancer.LoadBalanced;import?org.springframework.cloud.openfeign.EnableFeignClients;import?org.springframework.context.annotation.Bean;import?org.springframework.web.client.RestTemplate;@MapperScan("cn.itcast.order.mapper")????@SpringBootApplication????//?配置類的方式開啟全局日志記錄????//@EnableFeignClients(defaultConfiguration?=?DefaultFeignConfiguration.class)?//?開啟feign客戶端的支持????@EnableFeignClients?//?開啟feign客戶端的支持????public?class?OrderApplication?{????????public?static?void?main(String[]?args)?{????????????SpringApplication.run(OrderApplication.class,?args);????????}????????//......????}
如果是局部生效,則把它放到對(duì)應(yīng)的@FeignClient這個(gè)注解中:
package?cn.itcast.order.feign;import?cn.itcast.order.config.DefaultFeignConfiguration;import?cn.itcast.order.pojo.User;import?org.springframework.cloud.openfeign.FeignClient;import?org.springframework.web.bind.annotation.GetMapping;import?org.springframework.web.bind.annotation.PathVariable;//?指定服務(wù)日志配置//@FeignClient(value?=?"userservice",configuration?=?DefaultFeignConfiguration.class)@FeignClient(value?=?"userservice")????public?interface?UserFeignClient?{????????@GetMapping("/user/{id}")????????User?queryById(@PathVariable("id")?Long?id);????}
2.3.Feign使用優(yōu)化
Feign底層發(fā)起http請(qǐng)求,依賴于其它的框架。其底層客戶端實(shí)現(xiàn)包括:
?URLConnection:默認(rèn)實(shí)現(xiàn),不支持連接池
?Apache HttpClient :支持連接池
?OKHttp:支持連接池
因此提高Feign的性能主要手段就是使用連接池代替默認(rèn)的URLConnection。
這里我們用Apache的HttpClient來演示。
1)引入依賴
在order-service的pom文件中引入Apache的HttpClient依賴:
<!--httpClient的依賴?--><dependency>??<groupId>io.github.openfeign</groupId>??<artifactId>feign-httpclient</artifactId></dependency>
2)配置連接池
在order-service的application.yml中添加配置:
feign:??client:????config:??????default:?#?default全局的配置????????loggerLevel:?BASIC?#?日志級(jí)別,BASIC就是基本的請(qǐng)求和響應(yīng)信息??httpclient:????enabled:?true?#?開啟feign對(duì)HttpClient的支持????max-connections:?200?#?最大的連接數(shù)????max-connections-per-route:?50?#?每個(gè)路徑的最大連接數(shù)
max-connections解釋:比如有以下情況,有一個(gè)服務(wù)A同時(shí)有可能會(huì)訪問B服務(wù)和C服務(wù),這時(shí)候配置的最大連接數(shù)指的就是A在訪問B和C時(shí),總的連接數(shù)不超過200。
max-connections-per-route解釋:指的是A服務(wù)訪問B服務(wù)時(shí)的路徑最大連接數(shù)據(jù)為50,也就是200個(gè)連接,A服務(wù)到B服務(wù)的訪問最多只會(huì)有50個(gè)連接,當(dāng)超出50個(gè)連接時(shí),其他連接就會(huì)路由到B服務(wù)之外的服務(wù)中。
接下來,在FeignClientFactoryBean中的loadBalance方法中打斷點(diǎn):
Debug方式啟動(dòng)order-service服務(wù),可以看到這里的client,底層就是Apache HttpClient:
改成http連接池后,從演示項(xiàng)目后臺(tái)的請(qǐng)求日志中可以發(fā)現(xiàn)會(huì)從原來的幾十ms變成個(gè)位數(shù)ms,有興趣的小伙伴可以自己測試一下。
2.4.最佳實(shí)踐-抽取feign-api接口
目前存在一個(gè)問題,我們目前演示的是只有一個(gè)order-service調(diào)用user-service,那如果當(dāng)有多個(gè)服務(wù)要去調(diào)userservice的時(shí)候呢,那是否需要在每個(gè)service里都去寫一份遠(yuǎn)程調(diào)用user-service的代碼?完成沒必要是不是?所以,把這部分代碼直接抽成一個(gè)module打成jar包,在需要調(diào)用的地方引入即可。
將Feign的Client抽取為獨(dú)立模塊,并且把接口有關(guān)的POJO、默認(rèn)的Feign配置都放到這個(gè)模塊中,提供給所有消費(fèi)者使用。
即將UserClient、User、Feign的默認(rèn)配置都抽取到一個(gè)feign-api包中,所有微服務(wù)引用該依賴包,即可直接使用。
1)抽取
首先創(chuàng)建一個(gè)module,命名為feign-api:
項(xiàng)目結(jié)構(gòu):
在feign-api中然后引入feign的starter依賴
<dependency>??<groupId>org.springframework.cloud</groupId>??<artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
然后,order-service中編寫的UserClient、User、DefaultFeignConfiguration都復(fù)制到feign-api項(xiàng)目中
2)在order-service中使用feign-api
首先,刪除order-service中的UserClient、User、DefaultFeignConfiguration等類或接口。
在order-service的pom文件中中引入feign-api的依賴:
<dependency>??<groupId>cn.itcast.demo</groupId>??<artifactId>feign-api</artifactId>??<version>1.0</version></dependency>
修改order-service中的所有與上述三個(gè)組件有關(guān)的導(dǎo)包部分,改成導(dǎo)入feign-api中的包
掃描包問題
最后,包的掃描要指定一下,不然啟動(dòng)會(huì)報(bào)錯(cuò)找不到userfeignclient
方式一:
指定Feign應(yīng)該掃描的包:
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:
指定需要加載的Client接口:
@EnableFeignClients(clients = {UserClient.class})
Feign實(shí)現(xiàn)原理
Feign的底層源碼實(shí)現(xiàn)主要包括以下幾個(gè)部分:
1. 接口定義
Feign的接口定義類似于Java的接口定義,但是它使用了注解來描述HTTP請(qǐng)求的參數(shù)和返回值。例如,@RequestMapping注解用于指定HTTP請(qǐng)求的URL和請(qǐng)求方法,@RequestParam注解用于指定HTTP請(qǐng)求的參數(shù),@RequestBody注解用于指定HTTP請(qǐng)求的請(qǐng)求體,@PathVariable注解用于指定HTTP請(qǐng)求的路徑參數(shù)等。
2. 動(dòng)態(tài)代理
Feign使用了Java的動(dòng)態(tài)代理技術(shù)來生成HTTP請(qǐng)求的實(shí)現(xiàn)類。當(dāng)應(yīng)用程序調(diào)用Feign接口的方法時(shí),F(xiàn)eign會(huì)動(dòng)態(tài)生成一個(gè)HTTP請(qǐng)求的實(shí)現(xiàn)類,并將請(qǐng)求參數(shù)傳遞給該實(shí)現(xiàn)類。該實(shí)現(xiàn)類會(huì)將請(qǐng)求參數(shù)轉(zhuǎn)換為HTTP請(qǐng)求,并發(fā)送給遠(yuǎn)程服務(wù)。當(dāng)遠(yuǎn)程服務(wù)返回響應(yīng)時(shí),該實(shí)現(xiàn)類會(huì)將響應(yīng)轉(zhuǎn)換為Java對(duì)象,并返回給應(yīng)用程序。
3. 編碼器和解碼器
Feign使用了編碼器和解碼器來將Java對(duì)象轉(zhuǎn)換為HTTP請(qǐng)求和響應(yīng)。編碼器將Java對(duì)象轉(zhuǎn)換為HTTP請(qǐng)求的請(qǐng)求體,解碼器將HTTP響應(yīng)的響應(yīng)體轉(zhuǎn)換為Java對(duì)象。Feign支持多種編碼器和解碼器,例如JSON編碼器和解碼器、XML編碼器和解碼器等。
4. 負(fù)載均衡
Feign可以與負(fù)載均衡器無縫集成,以實(shí)現(xiàn)服務(wù)的負(fù)載均衡。當(dāng)應(yīng)用程序調(diào)用Feign接口的方法時(shí),F(xiàn)eign會(huì)將請(qǐng)求發(fā)送給負(fù)載均衡器,負(fù)載均衡器會(huì)選擇一個(gè)可用的服務(wù)實(shí)例,并將請(qǐng)求轉(zhuǎn)發(fā)給該實(shí)例。如果該實(shí)例不可用,則負(fù)載均衡器會(huì)選擇另一個(gè)可用的服務(wù)實(shí)例,并將請(qǐng)求轉(zhuǎn)發(fā)給該實(shí)例。
5. 斷路器
Feign可以與斷路器無縫集成,以實(shí)現(xiàn)服務(wù)的容錯(cuò)。當(dāng)應(yīng)用程序調(diào)用Feign接口的方法時(shí),F(xiàn)eign會(huì)將請(qǐng)求發(fā)送給斷路器,斷路器會(huì)檢查服務(wù)實(shí)例的可用性。如果服務(wù)實(shí)例不可用,則斷路器會(huì)返回一個(gè)默認(rèn)的響應(yīng),以避免應(yīng)用程序出現(xiàn)異常。如果服務(wù)實(shí)例可用,則斷路器會(huì)將請(qǐng)求轉(zhuǎn)發(fā)給該實(shí)例,并返回實(shí)例的響應(yīng)。
關(guān)鍵詞: