SpringCloud之@FeignClient()注解的使用詳解
@FeignClient介紹
@FeignClient 是 Spring Cloud 中用于聲明一個(gè) Feign 客戶(hù)端的注解。由于SpringCloud采用分布式微服務(wù)架構(gòu),難免在各個(gè)子模塊下存在模塊方法互相調(diào)用的情況。
比如訂單服務(wù)要調(diào)用庫(kù)存服務(wù)的方法,@FeignClient()注解就是為了解決這個(gè)問(wèn)題的。
Feign 是一個(gè)聲明式的 Web Service 客戶(hù)端,它的目的是讓編寫(xiě) HTTP 客戶(hù)端變得更簡(jiǎn)單。通過(guò) Feign,只需要?jiǎng)?chuàng)建一個(gè)接口,并使用注解來(lái)描述請(qǐng)求,就可以直接執(zhí)行 HTTP 請(qǐng)求了。
@FeignClient()注解的源碼要求它必須在Interface接口上使用( FeignClient注解被@Target(ElementType.TYPE)修飾,表示FeignClient注解的作用目標(biāo)在接口上)
SpringBoot服務(wù)的啟動(dòng)類(lèi)必須要有@EnableFeignClients 注解才能使@FeginClient注解生效。
@FeignClient工作原理及整體流程
Feign服務(wù)調(diào)用的工作原理可以總結(jié)為以下幾個(gè)步驟
- 首先通過(guò)@EnableFeignCleints注解開(kāi)啟FeignCleint。
- 根據(jù)Feign的規(guī)則實(shí)現(xiàn)接口,添加@FeignCleint注解。程序啟動(dòng)后,會(huì)掃描所有有@FeignCleint的類(lèi),并將這些信息注入到ioc容器中。
- 注入時(shí)從FeignClientFactoryBean.class獲取FeignClient。
- 當(dāng)接口的方法被調(diào)用時(shí),通過(guò)jdk的代理,來(lái)生成具體的RequesTemplate,RequesTemplate生成http的Request。
- Request交給Client去處理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp。
- Client被封裝到LoadBalanceClient類(lèi),這個(gè)類(lèi)結(jié)合類(lèi)Ribbon做到了負(fù)載均衡。
整體流程:

@FeignClient常用屬性
name、value
指定FeignClient的名稱(chēng),如果項(xiàng)目使用了Ribbon,name屬性會(huì)作為微服務(wù)的名稱(chēng),用于服務(wù)發(fā)現(xiàn)
這兩個(gè)屬性的作用是一樣的,如果沒(méi)有配置url,那么配置的值將作為服務(wù)的名稱(chēng),用于服務(wù)的發(fā)現(xiàn),反之只是一個(gè)名稱(chēng)。
@FeignClient(name = "order-server")
public interface OrderRemoteClient {
@GetMapping("/order/detail")
public Order detail(@RequestParam("orderId") String orderId);
}注意:
- 這里寫(xiě)的是你要調(diào)用的那個(gè)服務(wù)的名稱(chēng)(
spring.application.name屬性配置),而不是你自己的那個(gè)服務(wù)的名稱(chēng)。 - 如果同一個(gè)工程中出現(xiàn)兩個(gè)接口使用一樣的服務(wù)名稱(chēng)會(huì)報(bào)錯(cuò)。原因是Client名字注冊(cè)到容器中重復(fù)了。除非指定不同的
contextId參數(shù)。
Description:
The bean ‘order-server.FeignClientSpecification’, defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
兩種解決方案:
- 增加配置 spring.main.allow-bean-definition-overriding=true
- 為每個(gè)FeignClient手動(dòng)指定不同的contextId
contextId
比如我們有個(gè)user服務(wù),但user服務(wù)中有很多個(gè)接口,我們不想將所有的調(diào)用接口都定義在一個(gè)類(lèi)中,那就可以給不同的client指定contextId,不然就會(huì)報(bào)異常。
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
注意:contextId不能帶_等符號(hào)。
@FeignClient(name = "order-server")
public interface OrderRemoteClient {
@GetMapping("/api/order/detail", contextId = "OrderRemoteClient")
public Order detail(@RequestParam("orderId") String orderId);
}上面給出了Bean名稱(chēng)沖突后的解決方案,下面來(lái)分析下contextId在Feign Client的作用,在注冊(cè)Feign Client Configuration的時(shí)候需要一個(gè)名稱(chēng),名稱(chēng)是通過(guò)getClientName方法獲取的:
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
+ FeignClient.class.getSimpleName());
}可以看到如果配置了contextId就會(huì)用contextId,如果沒(méi)有配置就會(huì)去value然后是name最后是serviceId。默認(rèn)都沒(méi)有配置,當(dāng)出現(xiàn)一個(gè)服務(wù)有多個(gè)Feign Client的時(shí)候就會(huì)報(bào)錯(cuò)了。
其次的作用是在注冊(cè)FeignClient中,contextId會(huì)作為Client 別名的一部分,如果配置了qualifier優(yōu)先用qualifier作為別名。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 拼接別名
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
// 配置了qualifier優(yōu)先用qualifier
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
url
url用于配置指定服務(wù)的地址,相當(dāng)于直接請(qǐng)求這個(gè)服務(wù)。像調(diào)試等場(chǎng)景可以使用。
@FeignClient(name = "order-server", url = "http://localhost:8085")
public interface OrderRemoteClient {
@GetMapping("/api/order/detail")
public Order detail(@RequestParam("orderId") String orderId);
}path
path定義當(dāng)前FeignClient訪問(wèn)接口時(shí)的統(tǒng)一前綴。
比如接口地址是/order/detail, 如果你定義了前綴是order, 那么具體方法上的路徑就只需要寫(xiě)/detail即可。
@FeignClient(name = "order-server", url = "http://localhost:8085", path = "/api/order")
public interface OrderRemoteClient {
@GetMapping("/detail")
public Order detail(@RequestParam("orderId") String orderId);
}primary
primary對(duì)應(yīng)的是@Primary注解,默認(rèn)為true,官方這樣設(shè)置也是有原因的。當(dāng)我們的Feign實(shí)現(xiàn)了fallback后,也就意味著Feign Client有多個(gè)相同的Bean在Spring容器中,當(dāng)我們?cè)谑褂聾Autowired進(jìn)行注入的時(shí)候,不知道注入哪個(gè),所以我們需要設(shè)置一個(gè)優(yōu)先級(jí)高的,@Primary注解就是干這件事情的。
qualifier
qualifier對(duì)應(yīng)的是@Qualifier注解,使用場(chǎng)景跟上面的primary關(guān)系很淡,一般場(chǎng)景直接@Autowired直接注入就可以了。
如果我們的Feign Client有fallback實(shí)現(xiàn),默認(rèn)@FeignClient注解的primary=true, 意味著我們使用@Autowired注入是沒(méi)有問(wèn)題的,會(huì)優(yōu)先注入你的Feign Client。
如果你鬼斧神差的把primary設(shè)置成false了,直接用@Autowired注入的地方就會(huì)報(bào)錯(cuò),不知道要注入哪個(gè)對(duì)象。
解決方案很明顯,你可以將primary設(shè)置成true即可,如果由于某些特殊原因,你必須得去掉primary=true的設(shè)置,這種情況下我們?cè)趺催M(jìn)行注入,我們可以配置一個(gè)qualifier,然后使用@Qualifier注解進(jìn)行注入。
Feign Client 定義
@FeignClient(name = "order-server", path = "/api/order", qualifier="orderRemoteClient")
public interface OrderRemoteClient {
@GetMapping("/detail")
public Order detail(@RequestParam("orderId") String orderId);
}Feign Client注入
@Autowired
@Qualifier("orderRemoteClient")
private OrderRemoteClient orderRemoteClient;configuration
configuration是配置Feign配置類(lèi),在配置類(lèi)中可以自定義Feign的Encoder、Decoder、LogLevel、Contract等。
configuration定義
public class FeignConfiguration {
@Bean
public Logger.Level getLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
@Bean
public CustomRequestInterceptor customRequestInterceptor() {
return new CustomRequestInterceptor();
}
// Contract,feignDecoder,feignEncoder.....
}使用示列
@FeignClient(value = "order-server", configuration = FeignConfiguration.class)
public interface OrderRemoteClient {
@GetMapping("/api/order/detail")
public Order detail(@RequestParam("orderId") String orderId);
}fallback
定義容錯(cuò)的處理類(lèi),也就是回退邏輯,當(dāng)調(diào)用遠(yuǎn)程接口失敗或超時(shí)時(shí),會(huì)調(diào)用對(duì)應(yīng)接口的容錯(cuò)邏輯,fallback指定的類(lèi)必須實(shí)現(xiàn)@FeignClient標(biāo)記的接口,無(wú)法知道熔斷的異常信息。
fallback定義
@Component
public class OrderRemoteClientFallback implements OrderRemoteClient {
@Override
public Order detail(String orderId) {
return new Order("order-998", "默認(rèn)fallback");
}
}使用示列
@FeignClient(value = "order-server", fallback = OrderRemoteClientFallback.class)
public interface OrderRemoteClient {
@GetMapping("/api/order/detail")
public Order detail(@RequestParam("orderId") String orderId);
}fallbackFactory
也是容錯(cuò)的處理,可以知道熔斷的異常信息。工廠類(lèi),用于生成fallback類(lèi)示例,通過(guò)這個(gè)屬性我們可以實(shí)現(xiàn)每個(gè)接口通用的容錯(cuò)邏輯,減少重復(fù)的代碼。
fallbackFactory定義
@Component
public class OrderRemoteClientFallbackFactory implements FallbackFactory<OrderRemoteClient> {
private Logger logger = LoggerFactory.getLogger(OrderRemoteClientFallbackFactory.class);
@Override
public OrderRemoteClient create(Throwable cause) {
return new OrderRemoteClient() {
@Override
public Order detail(String id) {
logger.error("OrderRemoteClient.detail 異常", cause);
return new Order("order-998", "默認(rèn)");
}
};
}
}使用示列
@FeignClient(value = "order-server", fallbackFactory = OrderRemoteClientFallbackFactory.class)
public interface OrderRemoteClient {
@GetMapping("/order/detail")
public Order detail(@RequestParam("orderId") String orderId);
}@FeignClient添加Header信息
在@RequestMapping中添加
@FeignClient(
url = "${orderServer_domain:http://order:8082}",
value = "order-server",
contextId = "OrderServerClient",
path = "/api/order"
)
public interface OrderRemoteClient {
@RequestMapping(value="/detail", method = RequestMethod.POST,
headers = {"Content-Type=application/json;charset=UTF-8"})
Order detail(@RequestParam("orderId") String orderId);
}使用@RequestHeader注解添加
@FeignClient(
url = "${orderServer_domain:http://order:8082}",
value = "order-server",
contextId = "OrderServerClient",
path = "/api/order"
)
public interface OrderRemoteClient {
@RequestMapping(value="/detail", method = RequestMethod.POST)
List<String> detail(@RequestHeader Map<String, String> headerMap, @RequestParam("orderId") String orderId);
}使用@Headers注解添加
@FeignClient(
url = "${orderServer_domain:http://order:8082}",
value = "order-server",
contextId = "OrderServerClient",
path = "/api/order"
)
public interface OrderRemoteClient {
@RequestMapping(value="/detail", method = RequestMethod.POST)
@Headers({"Content-Type: application/json;charset=UTF-8"})
List<String> detail(@RequestHeader Map<String, String> headerMap, @RequestParam("orderId") String orderId);
}實(shí)現(xiàn)RequestInterceptor接口
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate temp) {
temp.header(HttpHeaders.AUTHORIZATION, "XXXXX");
}
}FeignClient 源碼
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
@AliasFor("name")
String value() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
String[] qualifiers() default {};
String url() default "";
boolean dismiss404() default false;
Class<?>[] configuration() default {};
Class<?> fallback() default void.class;
Class<?> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
}
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringCloud中的@FeignClient注解使用詳解
- springcloud之FeignClient使用詳解
- SpringCloud @FeignClient注入Spring容器原理分析
- SpringCloud FeignClient 超時(shí)設(shè)置
- SpringCloud全面解析@FeignClient標(biāo)識(shí)接口的過(guò)程
- SpringCloud引入feign失敗或找不到@EnableFeignClients注解問(wèn)題
- SpringCloud @FeignClient參數(shù)的用法解析
- SpringCloud之@FeignClient()注解的使用方式
- SpringCloud中FeignClient自定義配置
相關(guān)文章
詳解如何修改idea配置文件位置從C盤(pán)更改到D盤(pán)
這篇文章主要給大家介紹了關(guān)于如何將idea的配置文件從默認(rèn)的C盤(pán)調(diào)整到D盤(pán),從而節(jié)省C盤(pán)使用空間,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,需要的朋友可以參考下2023-10-10
Java實(shí)現(xiàn)動(dòng)態(tài)IP代理的步驟詳解
在網(wǎng)絡(luò)編程中,動(dòng)態(tài)IP代理可以幫助用戶(hù)隱藏真實(shí)IP以及提高數(shù)據(jù)抓取的效率,本文將介紹如何在Java中實(shí)現(xiàn)動(dòng)態(tài)IP代理,包括設(shè)置代理、發(fā)送請(qǐng)求以及處理響應(yīng),需要的朋友可以參考下2025-02-02
解決Idea查看源代碼警告Library source does not mat
在使用IDEA開(kāi)發(fā)時(shí),遇到第三方j(luò)ar包中的源代碼和字節(jié)碼不一致的問(wèn)題,會(huì)導(dǎo)致無(wú)法正確打斷點(diǎn)進(jìn)行調(diào)試,這通常是因?yàn)閖ar包更新后源代碼沒(méi)有同步更新造成的,解決方法是刪除舊的jar包,通過(guò)Maven重新下載或手動(dòng)下載最新的源代碼包,確保IDE中的源碼與字節(jié)碼版本一致2024-10-10
SpringCloud遠(yuǎn)程服務(wù)調(diào)用實(shí)戰(zhàn)筆記
本文給大家介紹SpringCloud遠(yuǎn)程服務(wù)調(diào)用實(shí)戰(zhàn)筆記,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
Java中實(shí)現(xiàn)List分隔成子List詳解
大家好,本篇文章主要講的是Java中實(shí)現(xiàn)List分隔成子List詳解,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
MyBatis傳入集合 list 數(shù)組 map參數(shù)的寫(xiě)法
這篇文章主要介紹了MyBatis傳入集合 list 數(shù)組 map參數(shù)的寫(xiě)法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
從HelloWorld和文檔注釋開(kāi)始入門(mén)Java編程
這篇文章主要介紹了從HelloWorld和文檔注釋開(kāi)始入門(mén)Java編程,涉及到Javadoc工具的使用,需要的朋友可以參考下2015-10-10
Java?輪詢(xún)鎖使用時(shí)遇到問(wèn)題解決方案
這篇文章主要介紹了Java?輪詢(xún)鎖使用時(shí)遇到問(wèn)題解決方案,當(dāng)我們遇到死鎖之后,除了可以手動(dòng)重啟程序解決之外,還可以考慮使用順序鎖和輪詢(xún)鎖,但是過(guò)程也會(huì)遇到一些問(wèn)題,接下來(lái)我們一起進(jìn)入下面文章了解解決方案,需要的小伙伴可以參考一下2022-05-05
java IO流 之 輸出流 OutputString()的使用
這篇文章主要介紹了java IO流 之 輸出流 OutputString()的使用的相關(guān)資料,需要的朋友可以參考下2016-12-12

