SpringCloud超詳細講解Feign聲明式服務(wù)調(diào)用
入門案例
在服務(wù)消費者導入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在啟動類上添加@EnableFeignClients注解
Feign中已經(jīng)自動集成了Ribbon負載均衡,因此不需要自己定義RestTemplate了
@SpringCloudApplication
@EnableFeignClients // 開啟Feign注解
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
編寫Feign客戶端
@FeignClient(value = "user-service") // 添加FeignClient,指定服務(wù)ID
public interface UserClient {
/**
* 聲明一個feign的接口,它的實現(xiàn)是服務(wù)提供者的controller實現(xiàn)
*/
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Long id);
}
代碼中調(diào)用,使用userClient訪問:
@Autowired // 注入UserClient
private UserClient userClient;
public User getUserById(@PathVariable long id) {
User user = userClient.getById(id);
return user;
}
@FeignClient注解詳解
@FeignClient注解只能在Interface接口上使用。Feign會通過動態(tài)代理,生成實現(xiàn)類。
FeignClient注解被@Target(ElementType.TYPE)修飾,表示FeignClient注解的作用目標在接口上
@FeignClient ,聲明這是一個Feign客戶端,同時通過 name / value 屬性指定服務(wù)名稱
接口中定義的方法,完全采用SpringMVC的注解,F(xiàn)eign會根據(jù)注解生成URL,并訪問獲取結(jié)果
服務(wù)的啟動類要有@EnableFeignClients 注解才能使Fegin生效
@FeignClient注解的常用屬性如下:
name / value:指定FeignClient的名稱,如果項目使用了Ribbon(注冊中心),name屬性會作為微服務(wù)的名稱,用于服務(wù)發(fā)現(xiàn)
url:一般用于調(diào)試,可以手動指定@FeignClient調(diào)用的地址。默認為空
url可以從配置文件獲取,如果有則通過url調(diào)用,沒有則根據(jù)服務(wù)名調(diào)用。格式為 url = "${xxx.xxx.xxx: }"
configuration:Feign配置類,可以自定義Feign的Encoder、Decoder、LogLevel、Contract,可以為每一個feign client指定不同的配置
fallback:定義容錯的處理類,當調(diào)用遠程接口失敗或超時時,會調(diào)用對應(yīng)接口的容錯邏輯,fallback指定的類必須實現(xiàn)@FeignClient標記的接口
fallbackFactory:工廠類,用于生成fallback類示例,通過這個屬性可以實現(xiàn)每個接口通用的容錯邏輯,減少重復的代碼
path:定義當前FeignClient的統(tǒng)一前綴,當項目中配置了server.context-path, server.servlet-path時使用
decode404:當發(fā)生http 404錯誤時,如果該字段位true,會調(diào)用decoder進行解碼,否則拋出FeignException
調(diào)用方式:
方式一:接口提供方在注冊中心
如果服務(wù)提供方已經(jīng)注冊到注冊中心了,那么name或者value的值為:服務(wù)提供方的服務(wù)名稱。必須為所有客戶端指定一個name或者value
@FeignClient(value="run-product",fallback = ProductClientServiceFallBack.class)
方式二:單獨的一個http接口,接口提供方?jīng)]有注冊到注冊中心
@FeignClient(name="runClient11111",url="localhost:8001")
此處name的值為:調(diào)用客戶端的名稱
以上兩種方式都能正常調(diào)用。name可以為注冊中心的服務(wù)名稱,加上url屬性時,name的值就與注冊中心服務(wù)名稱無關(guān)。
Feign Client的配置
feign配置是在ribbon配置的基礎(chǔ)上做了擴展,可以支持服務(wù)級超時時間配置,所以,feign配置和ribbon配置的效果是?樣的。
SpringCloud對配置的優(yōu)先級順序如下:
- Feign局部配置 > Feign全局配置 > Ribbon局部配置 > Ribbon全局配置
- 配置文件屬性和配置類的優(yōu)先級順序為:配置文件屬性配置 > 配置類代碼配置
feign:
client:
config:
default: # 全部服務(wù)配置
connectTimeout: 5000 # 建立連接的超時時長,單位:毫秒。默認為1000
readTimeout: 5000 # 指建立連接后從服務(wù)端讀取到可用資源所用的超時時間,單位:毫秒。默認為1000
loggerLevel: FULL # 日志級別
errorDecoder: com.example.SimpleErrorDecoder # Feign的錯誤解碼器,相當于代碼配置方式中的ErrorDecoder
retryer: com.example.SimpleRetryer # 配置重試,相當于代碼配置方式中的Retryer
requestInterceptors: # 配置攔截器,相當于代碼配置方式中的RequestInterceptor
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false # 是否對404錯誤解碼
encode: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
serverName: # 單獨給某?服務(wù)配置。serverName是服務(wù)名,使?的時候要?服務(wù)名替換掉這個
connectTimeout: 5000
readTimeout: 5000
Feign請求添加headers
方案一:方法上的@RequestMapping注解添加headers信息
@RequestMapping注解的屬性中包含一個headers數(shù)組,可以在指定的方法上@RequestMapping注解中添加需要的headers,可以是寫死的,也可以讀取配置=
同理@RequestMapping一組的@PostMapping,@GetMapping注解等均適用
@FeignClient(name = "server",url = "127.0.0.1:8080")
public interface FeignTest {
@RequestMapping(value = "/test",headers = {"app=test-app","token=${test-app.token}"})
String test();
}
方案二:接口上的@RequestMapping注解添加headers信息
如果同一個接口中所有的方法都需要同樣的headers,可以在接口上的@RequestMapping注解中添加headers,使整個接口的方法均被添加同樣的headers
@FeignClient(name = "server",url = "127.0.0.1:8080")
@RequestMapping(value = "/",headers = {"app=test-app","token=${test-app.token}"})
public interface FeignTest {
@RequestMapping(value = "/test")
String test();
}
方案三:使用@Headers注解添加headers信息(不推薦)
@FeignClient(name = "server",url = "127.0.0.1:8080")
@Headers({"app: test-app","token: ${test-app.token}"})
public interface FeignTest {
@RequestMapping(value = "/test")
String test();
}
查看openfeign官方文檔發(fā)現(xiàn)其使用的是@Headers來添加headers,測試發(fā)現(xiàn)并沒有生效,spring cloud使用了自己的SpringMvcContract來解析注解,所以需要自己實現(xiàn)一個Contract來實現(xiàn)對@Headers注解的支持,具體實現(xiàn)參照(http://chabaoo.cn/article/252325.htm)
方案四:自定義RequestInterceptor添加headers信息
feign提供了一個攔截器接口RequestInterceptor,實現(xiàn)RequestInterceptor接口就可以實現(xiàn)對feign請求的攔截,接口提供了一個方法apply(),實現(xiàn)apply()方法
實現(xiàn)apply()方法直接添加header會攔截所有的請求都加上headers,如果不是所有的feign請求都需要用到不建議此方法
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Value("${test-app.token}")
private String token;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("app","test-app");//靜態(tài)
requestTemplate.header("token",token);//讀配置
}
}
方案五:自定義RequestInterceptor實現(xiàn)添加動態(tài)數(shù)據(jù)到header
以上方案都不適合把動態(tài)的數(shù)據(jù)放入headers中,而通常場景下可能經(jīng)常需要把計算的簽名,用戶id等動態(tài)信息設(shè)置到headers,所以還需要一個更加完善的方案。方案1/2/3均不能設(shè)置動態(tài)的值,方案4可以設(shè)置動態(tài)值,但是沒做到請求的區(qū)分,所以在方案4的基礎(chǔ)上進行改進得到了較為完善的方案5。具體實現(xiàn)如下:
在請求調(diào)用代碼中,獲取到HttpServletRequest對象,將需要添加到headers中的值封裝成一個map后放入HttpServletRequest的attribute域中
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
String signedMsg = getSignedMsg(reqJson); // 計算簽名字符串
Map<String, String> reqMap = new HashMap<>();
reqMap.put("content-type", "application/json"); //常量字段
reqMap.put("accessKey", accessKey); //常量字段
reqMap.put("signedMsg", signedMsg); //動態(tài)計算/獲取字段
request.setAttribute("customizedRequestHeader", reqMap);
在自定義RequestInterceptor中獲取到HttpServletRequest對象的attribute域中指定的key,將key對應(yīng)map中的所有參數(shù)加入到headers。
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 設(shè)置自定義header
// 設(shè)置request中的attribute到header以便轉(zhuǎn)發(fā)到Feign調(diào)用的服務(wù)
Enumeration<String> reqAttrbuteNames = request.getAttributeNames();
if (reqAttrbuteNames != null) {
while (reqAttrbuteNames.hasMoreElements()) {
String attrName = reqAttrbuteNames.nextElement();
if (!"customizedRequestHeader".equalsIgnoreCase(attrName)) {
continue;
}
Map<String,String> requestHeaderMap = (Map)request.getAttribute(attrName);
for (Map.Entry<String, String> entry : requestHeaderMap.entrySet()) {
requestTemplate.header(entry.getKey(), entry.getValue());
}
break;
}
}
}
}
負載均衡 (Ribbon)
Feign中本身已經(jīng)集成了Ribbon依賴和自動配置,默認支持Ribbon。
Fegin內(nèi)置的ribbon默認設(shè)置了請求超時時長,默認是1000ms。因為Ribbon內(nèi)部有重試機制,一旦超時,會自動重新發(fā)起請求
可以通過配置來修改:
全局配置 使用 ribbon.=
ribbon:
ReadTimeout: 2500 # 數(shù)據(jù)通信超時時長,單位:ms。默認為1000
ConnectTimeout: 500 # 連接超時時長,單位:ms。默認為1000
OkToRetryOnAllOperations: false # 是否對所有的異常請求(連接異常和請求異常)都重試。默認為false
MaxAutoRetriesNextServer: 1 # 最多重試多少次連接服務(wù)(實例)。默認為1。不包括首次調(diào)用
MaxAutoRetries: 0 # 服務(wù)的單個實例的重試次數(shù)。默認為0。不包括首次調(diào)用
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 切換負載均衡策略為隨機
指定服務(wù)配置 <服務(wù)名稱>.ribbon. =
serverName: # 單獨給某?服務(wù)配置。serverName是服務(wù)名,使?的時候要?服務(wù)名替換掉這個
ribbon:
connectTimeout: 5000
readTimeout: 5000
容錯機制
Hystrix支持
Feign默認也有對Hystix的集成,只不過,默認情況下是關(guān)閉的。需要通過下面的參數(shù)來開啟:
feign:
hystrix:
enabled: true # 開啟hystrix熔斷機制
hystrix:
command:
default: # 全局默認配置
execution: # 線程隔離相關(guān)
timeout:
enabled: true # 是否給方法執(zhí)行設(shè)置超時時間,默認為true。一般不改。
isolation:
strategy: THREAD # 配置請求隔離的方式,這里是默認的線程池方式。還有一種信號量的方式semaphore,
thread:
timeoutlnMilliseconds: 10000 # 方式執(zhí)行的超時時間,默認為1000毫秒,在實際場景中需要根據(jù)情況設(shè)置
circuitBreaker: # 服務(wù)熔斷相關(guān)
requestVolumeThreshold: 10 # 觸發(fā)熔斷的最小請求次數(shù),默認20
sleepWindowInMilliseconds: 10000 # 休眠時長,單位毫秒,默認是5000毫秒
errorThresholdPercentage: 50 # 觸發(fā)熔斷的失敗請求最小占比,默認50%
serverName: # 單獨給某?服務(wù)配置
execution:
timeout:
enabled: true
isolation:
strategy: THREAD
thread:
timeoutlnMilliseconds: 10000
注意:
Hystix的超時時間應(yīng)該比Ribbon重試的總時間要大 ,否則Hystrix命令超時后,該命令直接熔斷,重試機制就沒有任何意義了。
Ribbon:總重試次數(shù) = 訪問的服務(wù)器數(shù) * 單臺服務(wù)器最大重試次數(shù)
即 總重試次數(shù) = (1+MaxAutoRetriesNextServer)*(1+MaxAutoRetries )
Hystrix超時時間 > (Ribbon超時時間總和)* 重試次數(shù)
故 建議hystrix的超時時間為:
( ( 1+MaxAutoRetriesNextServer) * (1+MaxAutoRetries ) ) * (ReadTimeout + connectTimeout)
- MaxAutoRetries:Ribbon配置: 服務(wù)的單個實例的重試次數(shù)。不包括首次調(diào)用
- MaxAutoRetriesNextServer:Ribbon配置: 最多重試多少次連接服務(wù)(實例)。不包括首次調(diào)用
- ReadTimeout:Ribbon配置: 通信超時時間
- connectTimeout:Ribbon配置: 建立連接超時時間
Sentinel支持
Sentinel 可以通過并發(fā)線程數(shù)模式的流量控制來提供信號量隔離的功能。并且結(jié)合基于響應(yīng)時間的熔斷降級模式,可以在不穩(wěn)定資源的平均響應(yīng)時間比較高的時候自動降級,防止過多的慢調(diào)用占滿并發(fā)數(shù),影響整個系統(tǒng)。
依賴
<!--Sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
在配置文件中開啟Feign對Sentinel的支持
feign:
sentinel:
enabled: true
Feign開啟容錯機制支持后的使用方式
Feign 開啟 Hystrix 或 Sentinel 容錯機制支持后的使用方式均是如下兩種:
方案一:直接繼承被容錯的接口,并為每個方法實現(xiàn)容錯方案
方案二:實現(xiàn)FallbackFactory接口
方案一:直接繼承被容錯的接口,并為每個方法實現(xiàn)容錯方案
定義一個類,作為fallback的處理類 。直接繼承被容錯的接口,并為每個方法實現(xiàn)容錯方案
@Component
public class UserClientFallback implements UserClient {
@Override
public User getById(Long id) {
return new User(1L, "我是備份-feign", 18, new Date());
}
}
在 @FeignClient 注解中使用 fallback 屬性指定自定義的容錯處理類
@FeignClient(value = "user-service",fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Long id);
}
測試驗證

方案二:實現(xiàn)FallbackFactory接口。可以拿到具體的服務(wù)錯誤信息,便于后期排查問題
@FeignClient(value="34-SPRINGCLOUD-SERVICE-GOODS", fallbackFactory = GoodsRemoteClientFallBackFactory.class)
public interface GoodsRemoteClient {
@RequestMapping("/service/goods")
public ResultObject goods();
}
@Component
public class GoodsRemoteClientFallBackFactory implements FallbackFactory<GoodsRemoteClient> {
@Override
public GoodsRemoteClient create(Throwable throwable) {
return new GoodsRemoteClient() {
@Override
public ResultObject goods() {
String message = throwable.getMessage(); // message即為錯誤信息
System.out.println("feign遠程調(diào)用異常:" + message);
return new ResultObject();
}
};
}
}
請求壓縮feign.compression
Spring Cloud Feign 支持對請求和響應(yīng)進行GZIP壓縮,以減少通信過程中的性能損耗。通過下面的參數(shù)即可開啟請求與響應(yīng)的壓縮功能:
feign:
compression:
request:
enabled: true
response:
enabled: true
也可以對請求的數(shù)據(jù)類型,以及觸發(fā)壓縮的大小下限進行設(shè)置,只有超過這個大小的請求才會對其進行壓縮:
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
日志級別
通過logging.level.xx=debug來設(shè)置日志級別。然而這個對Fegin客戶端而言不會產(chǎn)生效果。因為@FeignClient注解修改的客戶端在被代理時,都會創(chuàng)建一個新的Fegin.Logger實例,所以需要額外指定這個日志的級別才可以。
Feign支持4種日志級別:
- NONE:不記錄任何日志信息,這是默認值。
- BASIC:僅記錄請求的方法,URL以及響應(yīng)狀態(tài)碼和執(zhí)行時間
- HEADERS:在BASIC的基礎(chǔ)上,額外記錄了請求和響應(yīng)的頭信息
- FULL:記錄所有請求和響應(yīng)的明細,包括頭信息、請求體、元數(shù)據(jù)。
全局配置
方式一:配置文件屬性實現(xiàn)
feign:
client:
config:
default: # 將調(diào)用的微服務(wù)名稱設(shè)置為default即為配置成全局
loggerLevel: FULL
方式一:代碼實現(xiàn)
//在啟動類上為@EnableFeignClients注解添加defaultConfiguration配置 @EnableFeignClients(defaultConfiguration = FeignConfig.class)
細粒度(指定服務(wù)配置)
方式一:配置文件實現(xiàn)
feign:
client:
config:
server-1: # 想要調(diào)用的微服務(wù)名稱
loggerLevel: FULL
方式二:代碼實現(xiàn)
1)編寫配置類,定義日志級別
@Configuration
public class FeignConfig {
@Bean
Logger.Level level(){
return Logger.Level.FULL;
}
}
2)在FeignClient中指定配置類:(可以省略)
@FeignClient(value = "user-service", configuration = FeignConfig.class)
// 添加FeignClient,指定服務(wù)ID
public interface UserClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Long id);
}
Feign每次訪問的日志示例:

到此這篇關(guān)于SpringCloud超詳細講解Feign聲明式服務(wù)調(diào)用的文章就介紹到這了,更多相關(guān)SpringCloud Feign內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Flask接口如何返回JSON格式數(shù)據(jù)自動解析
這篇文章主要介紹了Flask接口如何返回JSON格式數(shù)據(jù)自動解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11
Java?GenericObjectPool?對象池化技術(shù)之SpringBoot?sftp?連接池工具類詳解
這篇文章主要介紹了Java?GenericObjectPool?對象池化技術(shù)之SpringBoot?sftp?連接池工具類詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04
使用SpringBoot配置多數(shù)據(jù)源的經(jīng)驗分享
這篇文章主要介紹了使用SpringBoot配置多數(shù)據(jù)源的經(jīng)驗分享,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04
SpringBoot Security整合JWT授權(quán)RestAPI的實現(xiàn)
這篇文章主要介紹了SpringBoot Security整合JWT授權(quán)RestAPI的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11

