FeignClient常見用法
正常的FeignClient的建立與使用我相信隻要使用過spring cloud全家桶的套件的基本上都是非常熟悉了,我們隻需定義一個interface,然後定義相關的遠端接口方法簽名及在方法上标記相關的請求映射的注解指明請求URL及方式,最後在該接口類上方标記@FeignClient注解,并設定相關參數即可,示例模闆如下所示:
/**
* 自定義一個FeignClient标準接口類
* @author zuowenjunn.cn
*/
@FeignClient(name = "baidu",url = "http://www.baidu.com")
public interface DemoFeignClient {
@RequestMapping(value = "/s",produces ="text/html",consumes = "text/html")
String searchBaiDu(@RequestParam("wd") String wd);
}
用法就比較簡單了,直接把上述标記為@FeignClient的接口類(如:DemoFeignClient)當成普通BEAN的接口進行依賴注入即可,如下示例代碼:
@Autowired
private DemoFeignClient demoFeignClient;
String searchResultResp = demoFeignClient.searchBaiDu("夢在旅途");
System.out.println("searchBaiDu Result:" + searchResultResp);
注意了,如上述示例代碼均隻是為了便于示範而建立的,确實可以拿來(複制到JAVA的spring cloud項目中)即可執行,但實際生産項目中使用則應根據實際的微服務的名稱及URL來靈活配置,具體配置與使用可參見我(夢在旅途)之前的文章:玩轉Spring Cloud之服務注冊發現(eureka)及負載均衡消費(ribbon、feign)
上述通過定義接口+@FeignClient注解的方式是最常見也是最簡單的使用的方式,那大家知悉原理嗎?其實原理我在之前文章也講過,核心點就是:在Spring IOC容器初始化時,會通過@EnableFeignClients中的@Import(FeignClientsRegistrar.class)最終執行FeignClientsRegistrar.registerBeanDefinitions方法,把所有标記有@FeignClient注解的接口包裝成FeignClientFactoryBean并注冊為BEAN,當項目中實際依賴FeignClient接口類時,則會通過FeignClientFactoryBean.getObject來生成真實的FeignClient接口的實作類BEAN,詳細源碼解讀可參見:https://blog.csdn.net/forezp/article/details/83896098 這裡面最關鍵的其實就是FeignClientFactoryBean.getObject方法,而這個方法本質又是調getTarget方法,這個方法最終傳回的就是一個Targeter接口的實作類。如果我們想動态自定義一個FeignClient接口,增加一些自定義的邏輯(比如:額外裝飾一些邏輯),那麼我們隻需要自行建立Targeter接口的實作類即可。這種方案是可行的,而且架構也提供了這種自定義的擴充能力:Feign.Builder,Feign.Builder類就是可以實作動态建構FeignClient的利器,下面我将基于這種思路實作動态自定義建構FeignClient。
動态自定義建構FeignClient實作方式一(直接指明服務的URL位址【如果是實際項目中這應該是網關的負載均衡位址哦】)
/**
* 自定義一個FeignClient接口類,除了不用加@FeignClient注解外,其餘均相同
* @author zuowenjunn.cn
*/
public interface CustomApiFeignClient {
@RequestMapping(value = "/s",produces ="text/html",consumes = "text/html")
String searchBaiDu(@RequestParam("wd") String wd);
}
/**
* 定義配置類,集中動态配置自定義的FeignClient代理類BEAN
* @author zuowenjun.cn
*/
@Configuration
@Import(FeignClientsConfiguration.class)
public class FeignClientConfig {
@Bean
public CustomApiFeignClient customApiFeignClient(Contract contract, Decoder decoder, Encoder encoder) {
return Feign.builder().contract(contract).encoder(encoder).decoder(decoder).target(CustomApiFeignClient.class, "http://www.baidu.com");
}
}
用法就比較簡單了,如正常用法一樣,把視為FeignClient接口(如:CustomApiFeignClient)依賴注入到相關的BEAN中即可,代碼如下所示:
CustomApiFeignClient feignClient = SpringUtils.getBean(CustomApiFeignClient.class); //這裡使用工具類擷取BEAN,當然也可以使用@Autowired注解方式獲得
String searchResultResp = feignClient.searchBaiDu("夢在旅途");
System.out.println("searchBaiDu Result:" + searchResultResp);
動态自定義建構FeignClient實作方式二(直接指明服務名,并進行裝飾,支援自定義負載均衡、更換請求API的元件等功能)
/**
* 自定義一個FeignClient接口類,除了不用加@FeignClient注解外,其餘均相同
* @author zuowenjunn.cn
*/
public interface CategoryTreeConfigFeignClient {
@RequestMapping(value = "/categoryTreeConfig/getCategoryInfo", method = RequestMethod.POST)
ResponseData<Object> getCategoryInfo(CategoryTreeConfigBO categoryTreeConfigBO);
}
/**
* 定義配置類,集中動态配置自定義的FeignClient代理類BEAN
* @author zuowenjun.cn
*/
@Configuration
@Import(FeignClientsConfiguration.class)
public class FeignClientConfig {
@Bean
public CustomApiFeignClient customApiFeignClient(Contract contract, Decoder decoder, Encoder encoder) {
return Feign.builder().contract(contract).encoder(encoder).decoder(decoder).target(CustomApiFeignClient.class, "http://www.baidu.com");
}
@Bean
public CategoryTreeConfigFeignClient categoryTreeConfigFeignClient(@Qualifier("feignClient") Client client, SpringClientFactory clientFactory, Contract contract, Decoder decoder, Encoder encoder) {
Client customClient = new Client.Default(null, null) {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
return super.execute(request, options);
}
@Override
public HttpURLConnection getConnection(URL url) throws IOException {
String serviceName = url.getHost();
String rawUrl= url.toString();
List<Server> upServers= clientFactory.getLoadBalancer(serviceName).getReachableServers();
//TODO:自定義用戶端負載均衡政策,這裡隻是舉例選第1個
Server bestServer= upServers.stream().findFirst().orElse(null);
Assert.notNull(bestServer,serviceName +":從注冊中心沒有找到可用的Server執行個體.");
url=new URL(url.getProtocol(),bestServer.getHost(),bestServer.getPort(),url.getFile());
return super.getConnection(url);
}
};
return Feign.builder().client(customClient).contract(contract).encoder(encoder).decoder(decoder).target(CategoryTreeConfigFeignClient.class, "http://微服務名");
}
}
實際用法與上述實作方式一相同,如下:
CategoryTreeConfigFeignClient categoryTreeConfigFeignClient=SpringUtils.getBean(CategoryTreeConfigFeignClient.class);//這裡使用工具類擷取BEAN,當然也可以使用@Autowired注解方式獲得
ResponseData<Object> responseData= categoryTreeConfigFeignClient.getCategoryInfo(new CategoryTreeConfigBO(){
{
setId("50");
setIncludeChild(0);
}
});
除了上述通過Feign.builder()的方式來直接進行額外自定義處理處,還可以在正常用法的基礎上增加個性化配置的方式來實作,即:給@FeignClient注解屬性configuration指派自定義的配置類,在配置類中可以定義相關的BEAN以替換全局預設的BEAN,具體方法請參見網上的相關文章介紹。