feignclient url_对Feign的请求url 重写

本文介绍如何通过自定义LoadBalancerFeignClient来控制Feign请求的URL,实现服务mock功能。通过关闭Sleuth对Feign的包装,避免其对请求的影响。同时,文章还展示了如何通过配置文件灵活地控制服务的直连地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求:对当前请求的 url 重新构建

debug feign 的执行可知,重写 LoadBalancerFeignClient 类中的 execute 方法即可控制当前请求的url

代码分析

当引入 spring-cloud-sleuth-stream 时, seluth也重写了feign 的 feign.Client 查看 TraceFeignClientAutoConfiguration 类,

@Configuration

@ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true)

protected static class FeignBeanPostProcessorConfiguration {

@Bean

FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) {

return new FeignContextBeanPostProcessor(beanFactory);// 对 FeignContext 进行了重新包装

}

}

@Bean // 对 LoadBalancerFeignClient bean 的包装类

TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) {

return new TraceFeignObjectWrapper(beanFactory);

}

@Bean // 对 feign.Client 的所有方法进行拦截,如果 执行的 bean 不是 TraceFeignClient ,则返 TraceFeignClient bean,执行该类中的方法,该类也实现了 feign的 Client 接口

TraceFeignAspect traceFeignAspect(BeanFactory beanFactory) {

return new TraceFeignAspect(beanFactory);

}

final class FeignContextBeanPostProcessor implements BeanPostProcessor {

private final BeanFactory beanFactory;

private TraceFeignObjectWrapper traceFeignObjectWrapper;

FeignContextBeanPostProcessor(BeanFactory beanFactory) {

this.beanFactory = beanFactory;

}

@Override

public Object postProcessBeforeInitialization(Object bean, String beanName)

throws BeansException {

if (bean instanceof FeignContext && !(bean instanceof TraceFeignContext)) { //如果bean 是 FeignContext,则返回 TraceFeignContext 的bean

return new TraceFeignContext(getTraceFeignObjectWrapper(), (FeignContext) bean);

}

return bean;

}

@Override

public Object postProcessAfterInitialization(Object bean, String beanName)

throws BeansException {

return bean;

}

private TraceFeignObjectWrapper getTraceFeignObjectWrapper() {

if (this.traceFeignObjectWrapper == null) {

// 查看 TraceFeignObjectWrapper 类

this.traceFeignObjectWrapper = this.beanFactory.getBean(TraceFeignObjectWrapper.class);

}

return this.traceFeignObjectWrapper;

}

}

查看 TraceFeignObjectWrapper 类,代码如下:

final class TraceFeignObjectWrapper {

private final BeanFactory beanFactory;

private CachingSpringLoadBalancerFactory cachingSpringLoadBalancerFactory;

private SpringClientFactory springClientFactory;

TraceFeignObjectWrapper(BeanFactory beanFactory) {

this.beanFactory = beanFactory;

}

Object wrap(Object bean) {

if (bean instanceof Client && !(bean instanceof TraceFeignClient)) {

if (bean instanceof LoadBalancerFeignClient) { //如果 bean 是 LoadBalancerFeignClient 的 bean,则返回 TraceLoadBalancerFeignClient 的 bean

LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean);

return new TraceLoadBalancerFeignClient(

client.getDelegate(), factory(),

clientFactory(), this.beanFactory);

}

return new TraceFeignClient(this.beanFactory, (Client) bean);

}

return bean;

}

CachingSpringLoadBalancerFactory factory() {

if (this.cachingSpringLoadBalancerFactory == null) {

this.cachingSpringLoadBalancerFactory = this.beanFactory

.getBean(CachingSpringLoadBalancerFactory.class);

}

return this.cachingSpringLoadBalancerFactory;

}

SpringClientFactory clientFactory() {

if (this.springClientFactory == null) {

this.springClientFactory = this.beanFactory

.getBean(SpringClientFactory.class);

}

return this.springClientFactory;

}

}

当请求执行时,查看 LoadBalancerFeignClient类可知如图所示, delegate 显示为TraceFeignClient 的bean ,说明feign 的 负载均衡客户端现在用的是 TraceLoadBalancerFeignClient

查看 AbstractLoadBalancerAwareClient 类,获取当前server 的ip,端口,请求url,如图所示

feign 的默认 Client 为 Default 类,根据请求服务的ip,端口和url ,发起请求,所以最终请求的执行,如下面所示

由上面的流程可知,引入sleuth 时,sleuth 中的feign相关类重写了 feign 的负载均衡类,所以要关闭 这个功能,当 配置文件中有 spring.sleuth.feign.enabled=false 时,则

sleuth 重写 feign 负载均衡器 ,则不生效。

当配置文件中有 ribbon.eureka.enabled=false 属性时,才会使用 ribbon.listOfServers 中配置的信息,所有要 实现 EnvironmentPostProcessor 类,将属性放入

defaultProperties 配置文件级别中,方便当项目中有相同属性时,覆盖已添加的属性信息

@Slf4j

public class MockEnvironmentPostProcessor implements EnvironmentPostProcessor {

private static final String PROPERTY_SOURCE_NAME = "defaultProperties";

@Override

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {

//获取mock属性配置,并绑定到MockProperties 对象中

MockProperties target = new MockProperties();

RelaxedDataBinder binder = new RelaxedDataBinder(target,

MockProperties.MOCK_PREFIX);

binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));

boolean enabled = target.isEnabled();

if (true == enabled) {

System.out.println("mock server 构建环境属性");

Map map = new HashMap<>();

//对单个服务mock的处理

if (StringUtils.isNotBlank(target.getServices())) {

String[] services = target.getServices().split(",");

for (String service : services) {

map.put(service.toUpperCase() + ".ribbon.listOfServers", target.getIpAddress());

System.out.println(String.format("对[%s]服务配置 mock地址[%s]", service, target.getIpAddress()));

}

}

// 自定义 每个服务的ip地址 服务直连情况

if (!target.getServicesMap().isEmpty()) {

Map servicesMap = target.getServicesMap();

for (String key : servicesMap.keySet()) {

String ip = servicesMap.get(key);

map.put(key.toUpperCase() + ".ribbon.listOfServers", ip);

System.out.println(String.format("对[%s]服务配置直连地址[%s]", key, ip));

}

}

// 服务重试切换次数

map.put("ribbon.MaxAutoRetriesNextServer", 0);

// 服务重试次数

map.put("ribbon.MaxAutoRetries", 0);

// ribbon 连接时间

map.put("ribbon.connectTimeoutMillis", 10000);

// ribbon 读取时间

map.put("ribbon.readTimeoutMillis", 60000);

// 对所有操作请求都不重试

map.put("ribbon.OkToRetryOnAllOperations", false);

//ribbon不使用eureka上的服务信息

map.put("ribbon.eureka.enabled", false);

// 关闭 sleuth 对 feign client 的包装

map.put("spring.sleuth.feign.enabled", false);

// 设置全局的超时时间 hystrix 熔断超时时间

map.put("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 60000);

addOrReplace(environment.getPropertySources(), map);

}

}

private void addOrReplace(MutablePropertySources propertySources,

Map map) {

MapPropertySource target = null;

if (propertySources.contains(PROPERTY_SOURCE_NAME)) {

PropertySource> source = propertySources.get(PROPERTY_SOURCE_NAME);

if (source instanceof MapPropertySource) {

target = (MapPropertySource) source;

for (String key : map.keySet()) {

if (!target.containsProperty(key)) {

target.getSource().put(key, map.get(key));

}

}

}

}

if (target == null) {

target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);

}

if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {

propertySources.addLast(target);

}

}

}

在 spring.factories 文件中配置

# Environment Post Processor

org.springframework.boot.env.EnvironmentPostProcessor=\

MockEnvironmentPostProcessor

继承 LoadBalancerFeignClient 类,重写  execute 方法

@Slf4j

public class MockLoadBalancerFeignClient extends LoadBalancerFeignClient {

private MockProperties mockProperties;

private DiscoveryClient discoveryClient;

private Client delegate;

private CachingSpringLoadBalancerFactory lbClientFactory;

private SpringClientFactory clientFactory;

public MockLoadBalancerFeignClient(Client delegate,

CachingSpringLoadBalancerFactory lbClientFactory,

SpringClientFactory clientFactory, MockProperties mockProperties, DiscoveryClient discoveryClient) {

super(delegate, lbClientFactory, clientFactory);

this.delegate = delegate;

this.lbClientFactory = lbClientFactory;

this.clientFactory = clientFactory;

this.mockProperties = mockProperties;

this.discoveryClient = discoveryClient;

log.info("mock feign 负载均衡器初始化");

}

/**

* 1. 如果配置 mock全局属性(默认false),则请求的所有服务都走 mock 服务器

* 2. 请求的服务在mock服务列表中,则请求走mock服务器

* 3. 请求的服务不在 mock 服务列表中,则先从直连配置获取服务信息,没有则从注册心上获取服务信息,请求转发到注册中心上的服务

* 4. 请求的服务不在 mock 服务列表中,也没有开启全局 mock 功能,则请求服务走 直连配置和注册中心相结合

*

* @param request

* @param options

* @return

* @throws IOException

*/

@Override

public Response execute(Request request, Request.Options options) throws IOException {

String url = request.url();

URI uri = URI.create(url);

String clientName = uri.getHost();

String[] mockServer = mockProperties.getIpAddress().split(":");

//请求的客户名称转为小写

clientName = clientName.toUpperCase();

String newUrl = null;

// 从全局中获取该服务名称的配置信息

IClientConfig clientConfig = this.clientFactory.getClientConfig(clientName);

if (null != clientConfig && mockProperties.getGlobal()) {

// 配置当前服务的ip地址信息

// clientConfig.set(CommonClientConfigKey.ListOfServers, mockProperties.getIpAddress());

// 获取当前服务的负载均衡器,对当前服务的负载均衡器添加服务ip地址信息

if (this.clientFactory.getLoadBalancer(clientName).getAllServers().isEmpty()) {

this.clientFactory.getLoadBalancer(clientName).

addServers(Arrays.asList(new Server(mockServer[0], Integer.parseInt(mockServer[1]))));

}

// 重新构建请求 URL

newUrl = this.getNewRequestUrl(url, clientName);

log.info("请求的 {} 服务已开启全局 mock 功能,服务地址:{}", clientName, this.clientFactory.getLoadBalancer(clientName).getAllServers());

} else {

//获取 配置 mock 服务的列表

String services = this.mockProperties.getServices();

if (StringUtils.isNotBlank(services)) {

// 配置 mock 服务的列表转为小写

services = services.toUpperCase();

// mock 服务列表

List service = Arrays.asList(services.split(","));

// 当前服务是否在 mock 服务列表中

if (service.contains(clientName)) {

log.info("请求的 {} 服务在 mock 服务列表中,服务地址:{}", clientName, clientConfig.get(CommonClientConfigKey.ListOfServers));

newUrl = this.getNewRequestUrl(url, clientName);

} else {

// 服务直连情况 加 注册中心 处理

newUrl = getServiceInfoFromDiscoveryClient(url, clientName, clientConfig);

}

} else {

if (mockProperties.getServicesMap().isEmpty()) {

String msg = "没有配置 mock 服务列表,也没有开启全局mock功能,也没有配置服务直连,请检查配置或关闭mock功能";

throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.BAD_REQUEST_EXCEPTION, null, msg, null, null);

}

// 服务直连情况 加 注册中心 处理

newUrl = getServiceInfoFromDiscoveryClient(url, clientName, clientConfig);

}

}

return this.getResponse(request, options, newUrl);

}

/**

* 1.如果有服务直接配置,则直接返回url

* 2.如果不是直连,则从注册中上获取服务信息,并返回url

*

* @param url

* @param clientName

* @param clientConfig

* @return

*/

private String getServiceInfoFromDiscoveryClient(String url, String clientName, IClientConfig clientConfig) {

String newUrl;

//服务直连处理

if (mockProperties.getServicesMap().size() > 0) {

// 处理一些自定义的服务和ip地址,服务的直连情况

Set customServiceInfo = mockProperties.getServicesMap().keySet();

if (customServiceInfo.contains(clientName.toUpperCase())) {

log.info("请求的 {} 服务在直连列表中,服务地址:{}", clientName, mockProperties.getServicesMap().get(clientName));

newUrl = url;

return newUrl;

}

}

if (null == this.discoveryClient) {

String format = String.format("%s 服务没有配置在mock列表中,并且也没有开启注册中心功能,请检查配置", clientName);

throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, null, format, null, null);

}

// 获取 服务名的 服务信息

List instances = this.discoveryClient.getInstances(clientName);

String host;

if (null == instances || instances.isEmpty()) {

host = String.format("%s 服务没有配置在mock列表中,也没有注册在住册中心上,请检查配置", clientName);

throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, null, host, null, null);

}

// 获取 服务在 注册中心的地址信息

host = instances.get(0).getHost() + ":" + instances.get(0).getPort();

log.info("请求的 {} 服务在注则中心上,服务地址:{}", clientName, host);

newUrl = url;

if (null != clientConfig) {

// clientConfig.set(CommonClientConfigKey.ListOfServers, host);

// 获取当前服务的负载均衡器,对当前服务的负载均衡器添加服务ip地址信息

if (this.clientFactory.getLoadBalancer(clientName).getAllServers().isEmpty()) {

this.clientFactory.getLoadBalancer(clientName).

addServers(Arrays.asList(new Server(instances.get(0).getHost(), instances.get(0).getPort())));

}

}

return newUrl;

}

/**

* 请求响应

*

* @param request

* @param options

* @param newUrl

* @return

* @throws IOException

*/

private Response getResponse(Request request, Request.Options options, String newUrl) throws IOException {

//重新构建 request 对象

Request newRequest = Request.create(request.method(),

newUrl, request.headers(), request.body(),

request.charset());

return super.execute(newRequest, options);

}

/**

* 修改请求 url

*

* @param url

* @param clientName

* @return

*/

private String getNewRequestUrl(String url, String clientName) {

StringBuilder sb = new StringBuilder();

sb.append(clientName);

String mockServerUrl = mockProperties.getMockServerUrl();

if (mockServerUrl.endsWith("/")) {

sb.append(mockServerUrl);

} else {

sb.append(mockServerUrl).append("/");

}

sb.append(clientName.toLowerCase());

String newUrl = url.replaceFirst(clientName, sb.toString());

log.info("mock 服务重新构建请求 URL 地址:{}", newUrl);

return newUrl;

}

}

配置自化配置类

@Slf4j

@ConditionalOnProperty(prefix = MockProperties.MOCK_PREFIX, name = "enabled", havingValue = "true")

@EnableConfigurationProperties(value = {MockProperties.class})

@Configuration

public class MockAutoConfiguration {

@Bean

public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

SpringClientFactory clientFactory, @Autowired(required = false) DiscoveryClient discoveryClient, MockProperties mockProperties) {

return new MockLoadBalancerFeignClient(new Client.Default(null, null),

cachingFactory, clientFactory, mockProperties, discoveryClient);

}

@Bean

public MockFeignInterceptor mockFeignInterceptor() {

return new MockFeignInterceptor();

}

}

在 spring.factories 中配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

MockAutoConfiguration

自定义相关属性配置和提示,关于mock的属性配置

@ConfigurationProperties(prefix = MockProperties.MOCK_PREFIX, ignoreUnknownFields = true)

public class MockProperties {

public static final String MOCK_PREFIX = "mock.server";

/**

* 开启mock ,true:开启 false:关闭

*/

@Getter

@Setter

private boolean enabled = false;

/**

* mock 服务的ip地址或域名 例:10.12.141.146:18081

*/

@Getter

@Setter

private String ipAddress;

/**

* 如果每个服务的 mock server 的地址都一样的使用该配置,多个服务以 ,号 隔开 例:order-service,user-service

*/

@Getter

@Setter

private String services;

/**

* 如果每个服务的 mock server 地址不一样,使用该配置,key:服务名 value: ip地址 ,例

*/

@Getter

@Setter

private Map servicesMap = new ConcurrentHashMap<>();

/**

* mock server 服务url

*/

@Getter

@Setter

private String mockServerUrl;

/**

* 是否需要所有服务都用 mock

*/

private boolean global = false;

public boolean getGlobal() {

return global;

}

public void setGlobal(boolean global) {

this.global = global;

}

}

Mock server 相关属性

当开启mock 功能时,所有的 feign 请求都会带上请求头: X-Mock-Application: true

# 开启 mock 功能

mock.server.enabled=true

# mock 服务器ip地址和端口

mock.server.ip-address=123.90.8.1:18820

# mock 服务器的 url

mock.server.mock-server-url=/admin/mock

# mock 服务的列表,这里填写 feign 服务的服务名,多个以 ,号隔开,并大写

mock.server.services=PLATFORM-SERVICE,HELLO-SERVICE-1

如果项目中需要所有服务都要使用 mock 功能,则添加下面的属性

# 开启全局 mock 功能,也就是项目中所有的 feign服务都需要 mock

mock.server.global=true

如果项目中需要服务直连,则添加下面的属性

# user-service 服务的直连ip地址和端口

mock.server.services-map.user-service=192.168.111.10:9010

# cache-service 服务的直连ip地址和端口

mock.server.services-map.cache-service=10.10.90.23:9090

关闭全局 mock 功能

mock.server.global=false

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值