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,具体方法请参见网上的相关文章介绍。