SpringCloud之@FeignClient()注解的使用詳解
@FeignClient介紹
@FeignClient
是 Spring Cloud 中用于聲明一個 Feign 客戶端的注解。由于SpringCloud采用分布式微服務架構,難免在各個子模塊下存在模塊方法互相調(diào)用的情況。
比如訂單服務要調(diào)用庫存服務的方法,@FeignClient()注解就是為了解決這個問題的。
Feign 是一個聲明式的 Web Service 客戶端,它的目的是讓編寫 HTTP 客戶端變得更簡單。通過 Feign,只需要創(chuàng)建一個接口,并使用注解來描述請求,就可以直接執(zhí)行 HTTP 請求了。
@FeignClient()注解的源碼要求它必須在Interface接口上使用( FeignClient注解被@Target(ElementType.TYPE)修飾,表示FeignClient注解的作用目標在接口上)
SpringBoot服務的啟動類必須要有@EnableFeignClients
注解才能使@FeginClient注解生效。
@FeignClient工作原理及整體流程
Feign服務調(diào)用的工作原理可以總結為以下幾個步驟
- 首先通過@EnableFeignCleints注解開啟FeignCleint。
- 根據(jù)Feign的規(guī)則實現(xiàn)接口,添加@FeignCleint注解。程序啟動后,會掃描所有有@FeignCleint的類,并將這些信息注入到ioc容器中。
- 注入時從FeignClientFactoryBean.class獲取FeignClient。
- 當接口的方法被調(diào)用時,通過jdk的代理,來生成具體的RequesTemplate,RequesTemplate生成http的Request。
- Request交給Client去處理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp。
- Client被封裝到LoadBalanceClient類,這個類結合類Ribbon做到了負載均衡。
整體流程:
@FeignClient常用屬性
name、value
指定FeignClient的名稱,如果項目使用了Ribbon,name屬性會作為微服務的名稱,用于服務發(fā)現(xiàn)
這兩個屬性的作用是一樣的,如果沒有配置url,那么配置的值將作為服務的名稱,用于服務的發(fā)現(xiàn),反之只是一個名稱。
@FeignClient(name = "order-server") public interface OrderRemoteClient { @GetMapping("/order/detail") public Order detail(@RequestParam("orderId") String orderId); }
注意:
- 這里寫的是你要調(diào)用的那個服務的名稱(
spring.application.name
屬性配置),而不是你自己的那個服務的名稱。 - 如果同一個工程中出現(xiàn)兩個接口使用一樣的服務名稱會報錯。原因是Client名字注冊到容器中重復了。除非指定不同的
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
- 為每個FeignClient手動指定不同的contextId
contextId
比如我們有個user服務,但user服務中有很多個接口,我們不想將所有的調(diào)用接口都定義在一個類中,那就可以給不同的client指定contextId,不然就會報異常。
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
注意:contextId不能帶_等符號。
@FeignClient(name = "order-server") public interface OrderRemoteClient { @GetMapping("/api/order/detail", contextId = "OrderRemoteClient") public Order detail(@RequestParam("orderId") String orderId); }
上面給出了Bean名稱沖突后的解決方案,下面來分析下contextId在Feign Client的作用,在注冊Feign Client Configuration的時候需要一個名稱,名稱是通過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就會用contextId,如果沒有配置就會去value然后是name最后是serviceId。默認都沒有配置,當出現(xiàn)一個服務有多個Feign Client的時候就會報錯了。
其次的作用是在注冊FeignClient中,contextId會作為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用于配置指定服務的地址,相當于直接請求這個服務。像調(diào)試等場景可以使用。
@FeignClient(name = "order-server", url = "http://localhost:8085") public interface OrderRemoteClient { @GetMapping("/api/order/detail") public Order detail(@RequestParam("orderId") String orderId); }
path
path定義當前FeignClient訪問接口時的統(tǒng)一前綴。
比如接口地址是/order/detail, 如果你定義了前綴是order, 那么具體方法上的路徑就只需要寫/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對應的是@Primary注解,默認為true,官方這樣設置也是有原因的。當我們的Feign實現(xiàn)了fallback后,也就意味著Feign Client有多個相同的Bean在Spring容器中,當我們在使用@Autowired進行注入的時候,不知道注入哪個,所以我們需要設置一個優(yōu)先級高的,@Primary注解就是干這件事情的。
qualifier
qualifier對應的是@Qualifier注解,使用場景跟上面的primary關系很淡,一般場景直接@Autowired直接注入就可以了。
如果我們的Feign Client有fallback實現(xiàn),默認@FeignClient注解的primary=true, 意味著我們使用@Autowired注入是沒有問題的,會優(yōu)先注入你的Feign Client。
如果你鬼斧神差的把primary設置成false了,直接用@Autowired注入的地方就會報錯,不知道要注入哪個對象。
解決方案很明顯,你可以將primary設置成true即可,如果由于某些特殊原因,你必須得去掉primary=true的設置,這種情況下我們怎么進行注入,我們可以配置一個qualifier,然后使用@Qualifier注解進行注入。
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配置類,在配置類中可以自定義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
定義容錯的處理類,也就是回退邏輯,當調(diào)用遠程接口失敗或超時時,會調(diào)用對應接口的容錯邏輯,fallback指定的類必須實現(xiàn)@FeignClient標記的接口,無法知道熔斷的異常信息。
fallback定義
@Component public class OrderRemoteClientFallback implements OrderRemoteClient { @Override public Order detail(String orderId) { return new Order("order-998", "默認fallback"); } }
使用示列
@FeignClient(value = "order-server", fallback = OrderRemoteClientFallback.class) public interface OrderRemoteClient { @GetMapping("/api/order/detail") public Order detail(@RequestParam("orderId") String orderId); }
fallbackFactory
也是容錯的處理,可以知道熔斷的異常信息。工廠類,用于生成fallback類示例,通過這個屬性我們可以實現(xiàn)每個接口通用的容錯邏輯,減少重復的代碼。
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", "默認"); } }; } }
使用示列
@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); }
實現(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; }
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
JAVA 數(shù)據(jù)結構鏈表操作循環(huán)鏈表
這篇文章主要介紹了JAVA 數(shù)據(jù)結構鏈表操作循環(huán)鏈表的相關資料,需要的朋友可以參考下2016-10-10Spring事務管理中關于數(shù)據(jù)庫連接池詳解
事務的作用就是為了保證用戶的每一個操作都是可靠的,事務中的每一步操作都必須成功執(zhí)行,只要有發(fā)生異常就 回退到事務開始未進行操作的狀態(tài)。事務管理是Spring框架中最為常用的功能之一,我們在使用Spring Boot開發(fā)應用時,大部分情況下也都需要使用事務2022-12-12基于application和bootstrap的加載順序及區(qū)別說明
這篇文章主要介紹了application和bootstrap的加載順序及區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07java數(shù)字和中文算數(shù)驗證碼的實現(xiàn)
這篇文章主要介紹了java數(shù)字和中文算數(shù)驗證碼的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07