动态Feign实现
实现主要依赖FeignClientBuilder,过程也和其他博客类似,但是注意关键位置的变化。
代码实现
一个工厂
// 这和教程没多大区别
@Component
public class DynamicFeignClient<T> {
private FeignClientBuilder feignClientBuilder;
private final ConcurrentHashMap<String, T> CACHE_BEAN = new ConcurrentHashMap();
public DynamicFeignClient(@Autowired ApplicationContext appContext, @Autowired FeignContext feignContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
/**
* 可以基于此方法,完成服务动态切换
* @param type
* @param serviceName 服务名
* @return
*/
public T getFeignClient(final Class<T> type, final String serviceName) {
return getFeignClient(type, serviceName, null);
}
public T getFeignClient(final Class<T> type, final Class<?> fallbackFactory, final String serviceName) {
return getFeignClient(type, fallbackFactory, serviceName, null);
}
public T getFeignClient(final Class<T> type, final FeignClientFactoryBean clientFactoryBean, final String serviceName) {
return getFeignClient(type, clientFactoryBean, serviceName, null);
}
public T getFeignClient(final Class<T> type, String serviceName, final String serviceUrl) {
String k = serviceName;
if(StrUtil.isNotEmpty(serviceUrl)){
k = serviceUrl;
}
return CACHE_BEAN.computeIfAbsent(k, (t)->{
FeignClientBuilder.Builder<T> builder = this.feignClientBuilder.forType(type, serviceName);
if(StrUtil.isNotEmpty(serviceUrl)){
builder.url(serviceUrl);
}
return builder.build();
});
}
public T getFeignClient(final Class<T> type, final Class<?> fallbackFactory, final String serviceName, final String serviceUrl) {
String k = serviceName;
if(StrUtil.isNotEmpty(serviceUrl)){
k = serviceUrl;
}
return CACHE_BEAN.computeIfAbsent(k, (t)->{
FeignClientFactoryBean feignClientFactoryBean = new FeignClientFactoryBean();
feignClientFactoryBean.setFallbackFactory(fallbackFactory);
FeignClientBuilder.Builder<T> builder = this.feignClientBuilder.forType(type, feignClientFactoryBean, serviceName);
if(StrUtil.isNotEmpty(serviceUrl)){
builder.url(serviceUrl);
}
return builder.build();
});
}
public T getFeignClient(final Class<T> type, final FeignClientFactoryBean clientFactoryBean, final String serviceName, final String serviceUrl) {
String k = serviceName;
if(StrUtil.isNotEmpty(serviceUrl)){
k = serviceUrl;
}
return CACHE_BEAN.computeIfAbsent(k, (t)->{
FeignClientBuilder.Builder<T> builder = this.feignClientBuilder.forType(type, clientFactoryBean, serviceName);
if(StrUtil.isNotEmpty(serviceUrl)){
builder.url(serviceUrl);
}
return builder.build();
});
}
private T getFromCache(final String serviceName, final String serviceUrl){
if(StrUtil.isNotEmpty(serviceUrl)){
return CACHE_BEAN.get(serviceUrl);
}else {
return CACHE_BEAN.get(serviceName);
}
}
}
一个示例接口
注意:常见博客都让删除这个注解,但是删除后会出现No bean named OpenAPI 异常,保留此注解,并将primary设置为false
primary=false 不是必须的,如果想将工厂构造的对象作为bean覆盖feign创建的bean则需要这个参数;
@FeignClient(
fallbackFactory = RouteServiceAPIFallbackFactory.class,
configuration = FeignConfiguration.class,
primary = false
)
public interface OpenAPI {
@GetMapping("select/demo")
String selectDemo(@RequestParam(name = "name", required = false) Integer name);
}
覆盖bean(可选)
如果不想用新创建的结果覆盖feign创建的bean,则不必要。比如项目中会运行时动态切换服务名,则不适合将结果设置为bean。
本人项目只是想服务名受到配置文件控制。
@Configuration
@Slf4j
public class NotificationOpenConfiguration {
@Value("${test.serverName:xxx-server}")
private String serverName;
@Value("${test.serverUrl:}")
private String serverUrl;
private final DynamicFeignClient<NotificationOpenAPI> client;
@Bean
@Primary
public OpenAPI init(){
log.debug("Init notification open api serverName={}, serverUrl={},如果serverUrl不为空,则优先使用serverUrl", serverName, serverUrl);
OpenAPI openAPI = client.getFeignClient(OpenAPI.class, RouteServiceAPIFallbackFactory.class, serverName, serverUrl);
return openAPI;
}
}
猜测
根据代码追踪结果来看,FeignClientBuilder最终执行需要一个托管给spring的实现类{OpenAPI} 的bean,FeignClientBuilder会更具serverName或url判断是否需要创建新的{OpenAPI} 的bean,如果serverName或url无变化则返回原有Bean,否则创建一个新的实现。