目录
一、背景
公司提供的环境资源比较充足,分dev、test、uat、生产,以及最基本的本地idea。有这样一种场景,隔壁合作团队(后称M团队)的服务比较机密,服务在内网,并做了ip访问限制,除生产环境外,只允许test、uat访问,dev、本地则不允许接通。
涉及到M团队服务调用的内容单独抽了一个M服务模块。每次本地调试涉及到M服务调用时,只能手动改FeignClient注解配置url将请求转发到test环境的M服务,比较麻烦,还有误提交该操作配置的风险。Dev环境与前端或其他系统联调时,若涉及M服务调用则更是行不通,除非提交本地FeignClient注解的临时配置,这显然不合适。
二、分析
比较优雅的方式是配置本地和dev环境M服务的调用自动转发test环境,而其他环境这套配置完全不生效。
网上现有的方案有两种。第一种是配置feign自带的拦截器,修改定向的目标地址,这种方案对于url类型的访问是有效的,但是使用注册服务名对注册服务请求会失效。第二种是自定义实现Feign客户端,在请求执行方法中手动处理转发请求到其他http客户端进行请求,这种方法是有效的,唯一不足的就是自定义处理逻辑这种方式还不够优雅。
查看feign请求拦截器RequestInterceptor的调用链,发现在构造请求客户端请求对象Request时,先将拦截器执行了一遍,然后再构造请求对象:

Target.apply方法是直接使用拦截对象RequestTemplate对象的request方法构造Request对象,内部url构造用target参数做域名,所以RequestTemplate.target是主要拦截改造的参数:


简单编写拦截逻辑,替换RequestTemplate.target:

请求w服务报错:“Load balancer does not have available server for client: XXX.XXX.com”,明显走了注册列表负载请求,非http请求,所以单纯拦替换RequestTemplate.target是不可行的。
打断点调试发现,Feign执行注册列表服务请求和url的http请求使用的客户端是不同的,分别是LoadBalancerFeignClient和OkHttpClient,并且FeignClient接口是在bean加载阶段就绑定好执行客户端了。所以请求原注册名服务请求在feign拦截修改定向目标地址后,请求依旧是走LoadBalancerFeignClient客户端,导致请求失败。

三、实现
1 组成
为实现优雅转发,需要的类组成有三个:支持LoadBalancerFeignClient和OkHttpClient双向执行的客户端、重定向请求的Feign拦截器、可以指定生效环境的重定向组件Bean配置类。
2 客户端
对LoadBalancerFeignClient和OkHttpClient两个客户端进行包装,简单通过判断url是否包含注册服务名决定走哪个客户端:
@Slf4j
public class DevFeignRedirectionClient implements Client {
/**
* 注册列表负载客户端
*/
private final LoadBalancerFeignClient loadBalancerFeignClient;
/**
* http请求客户端
*/
private final OkHttpClient okHttpClient;
public DevFeignRedirectionClient(LoadBalancerFeignClient loadBalancerFeignClient, OkHttpClient okHttpClient) {
this.loadBalancerFeignClient = loadBalancerFeignClient;
this.okHttpClient = okHttpClient;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
String serverName = request.requestTemplate().feignTarget().name();
String url = request.requestTemplate().url();
if (url.contains(serverName)) {
return loadBalancerFeignClient.execute(request, options);
}
return okHttpClient.execute(request, options);
}
}
这种方式最大程度保留了原有服务组件和保证了原有服务请求走向。
3 拦截器
具体重定向逻辑放拦截器处理:
@Slf4j
public class DevFeignRedirectionInterceptor implements RequestInterceptor {
/**
* 重定向服务:定向域名
*/
private final static Map<String, String> REDIRECTION_SERVER = new HashMap<>();
static {
REDIRECTION_SERVER.put("M-server", "https://xxx.xxx.com/");
}
public DevFeignRedirectionInterceptor(Map<String, String> redirectionServer) {
if (MapUtils.isNotEmpty(redirectionServer)) {
REDIRECTION_SERVER.putAll(redirectionServer);
}
}
@Override
public void apply(RequestTemplate requestTemplate) {
String serverName = requestTemplate.feignTarget().name();
if (!REDIRECTION_SERVER.containsKey(serverName)) {
return;
}
String sourceDomain = requestTemplate.feignTarget().url();
String targetDomain = REDIRECTION_SERVER.get(serverName);
requestTemplate.target(targetDomain);
log.info("原请求域名:{},重定向域名:{},定向地址:{}", sourceDomain, targetDomain, requestTemplate.url());
}
}
4 配置类
Profile指定生效环境:
@Profile({"local", "dev"})
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureAfter(FeignAutoConfiguration.class)
@EnableConfigurationProperties(DevFeignRedirectionProperties.class)
public class DevFeignRedirectionConfiguration {
@Bean
public Client getClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory,
okhttp3.OkHttpClient client) {
return new DevFeignRedirectionClient(
new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory),
new feign.okhttp.OkHttpClient(client)
);
}
@Bean
public DevFeignRedirectionInterceptor getDevFeignRedirectionInterceptor(@Autowired DevFeignRedirectionProperties properties) {
return new DevFeignRedirectionInterceptor(properties.getRedirectionServer());
}
}
5 为什么不直接在客户端execute方法中修改定向地址?
请求对象Request中定向地址参数url不支持修改。
补充说明:
open feign 13已简化,不用这么复杂配置。一个拦截器就够了
文章描述了一种在开发和本地环境中优雅地重定向Feign服务调用到特定环境的方法,通过自定义拦截器和客户端包装,实现了对LoadBalancerFeignClient和OkHttpClient的控制,避免了直接修改请求URL导致的问题。
511

被折叠的 条评论
为什么被折叠?



