openfeign的动态使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

前段时间看到一个视频讲openfeign的动态使用方法,于是自己就写了一下,开始使用还算正常,但是在一些其他场景的情况下出问题了,所以就自己研究了下解决方案,记录一下。当然还有很多其他的场景没有覆盖到,希望有兴趣的多试试

一、使用openfeign

我们还是先按原来的使用方式先使用下openfeign
1、引入pom依赖

<!--添加openfeign相关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <!--哨兵-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

说明下,下面的哨兵后面会用到,所以这里先引入
2、添加nacos配置

#feign 超时配置
feign.okhttp.enabled=true
#启用此配置会覆盖ribbon的重试功能,并且此配置只在client为jdk是才有效
feign.client.config.default.connectTimeout=1000
feign.client.config.default.readTimeout=10000

# Sentinel自动化配置是否生效
spring.cloud.sentinel.eager=true
# Sentinel 控制台地址
spring.cloud.sentinel.transport.dashboard=sentinel.xxx.com:8201
# 开启feign熔断
feign.sentinel.enabled=true
# 应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
spring.cloud.sentinel.transport.port=8202

3、启动类添加启动注解

/**
 * @author jy
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients({"com.dili"})
public class DiliToolApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiliToolApplication.class, args);
    }
}

4、编写RPC接口

/**
 * @Author jy
 * @Date 2023/7/4 9:47
 * @Version 1.0
 */
@FeignClient(value = "pay-service", contextId = "PayRpc", configuration = PayServiceFeignConfig.class, url = "${payService.url:}")
public interface PayRpc {
    /**
     * 查询余额(扩展)
     * @author jy
     * @date 2023/7/4
     */
    @RequestMapping(value = "/payment/api/gateway.do?service=payment.fund.service:queryEx", method = RequestMethod.POST)
    BaseOutput<BalanceResponseDto> getAccountBalanceEx(CreateTradeRequestDto createTradeRequest);
}

说明下,PayServiceFeignConfig就是一个拦截器类,在调用时加一些可能需要的东西

/**
 * ps:不要在该类上面加诸如 @Configuration,否则会变成全局配置
 * @Auther: jy
 * @Date: 2023/7/4 10:01
 */
public class PayServiceFeignConfig {
    private static final Logger log = LoggerFactory.getLogger(PayServiceFeignConfig.class);
    private static final String APPID = "1010";
    private static final String TOKEN = "abcd1010";
    /**
     * 拦截器
     */
    @Bean
    public RequestInterceptor requestInterceptor() {
        return template -> {
            template.header("appid", APPID);
            template.header("token", TOKEN);
            template.header("mchId", 14 + "");
        };
    }
}

5、编写测试类

/**
 * @Author jy
 * @Date 2023/7/4 9:42
 * @Version 1.0
 */
@RestController
@RequestMapping("/api/feign")
public class TestController {
    Logger logger = LoggerFactory.getLogger(TestController.class);
    @Resource
    private PayRpc payRpc;
    /**
     *
     * @return
     */
    @GetMapping("/test")
    @ResponseBody
    public String test(String num) {
        logger.info("测试入参,{}",num);
        CreateTradeRequestDto requestDto = new CreateTradeRequestDto();
        requestDto.setAccountId(109751L);
        BaseOutput<BalanceResponseDto> resolver = payRpc.getAccountBalanceEx(requestDto);
        logger.info("调用支付系统结果返回,{}",resolver.getMessage());
        return "ok";
    }
}

6、测试结果
在这里插入图片描述
这里返回了支付系统的提示,说明我们的调用是成功,只是这个账号错误了,对于openfeign使用的学习没有关系。

通常情况下我们大抵就这样使用了。每次添加一个服务需要调用时就加一个RPC接口,服务少的还好说,像我们公司10多个服务,有些服务还按功能需求分别写RPC接口的,那就多了。某天正好刷到一个动态调用的视频于是自己学习了一下

二、动态调用

将原来的RPC接口全部去掉注释,使之服务启动时无法注册到容器,同时为了彻底去掉openfeign原来使用方式,直接将启动类上的@EnableFeignClients一并注释掉;由于我们前面开启了哨兵模式的,我们先把它关了

#feign 超时配置
feign.okhttp.enabled=true
#启用此配置会覆盖ribbon的重试功能,并且此配置只在client为jdk是才有效
feign.client.config.default.connectTimeout=1000
feign.client.config.default.readTimeout=10000

# Sentinel自动化配置是否生效
spring.cloud.sentinel.eager=false//关了
# Sentinel 控制台地址
spring.cloud.sentinel.transport.dashboard=sentinel.diligrp.com:8201
# 开启feign熔断
feign.sentinel.enabled=false//这样就关了
# 应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
spring.cloud.sentinel.transport.port=8202

1、编写动态类

/**
 * @Author jy
 * @Date 2023/7/4 10:56
 * @Version 1.0
 */
public interface DynamicService {
    /**
     *
     * @param url
     * @param params
     * @return
     */
    @RequestMapping(value = "{url}",method = RequestMethod.POST)
    Object executePostApi(@PathVariable("url") String url, @RequestBody Object params);

    /**
     *
     * @param url
     * @param params
     * @return
     */
    @RequestMapping(value = "{url}",method = RequestMethod.GET)
    Object executeGetApi(@PathVariable("url") String url, @SpringQueryMap Object params);
}
/**
 * @Author jy
 * @Date 2023/7/4 10:50
 * @Version 1.0
 */
@Component
public class DynamicFeignClientFactory<T> {
    private FeignClientBuilder feignClientBuilder;

    /**
     * @param applicationContext
     */
    public DynamicFeignClientFactory(ApplicationContext applicationContext) {
        this.feignClientBuilder = new FeignClientBuilder(applicationContext);
    }

    /**
     * @param type
     * @param serviceId
     * @return
     */
    public T getFeignClient(final Class<T> type, String serviceId, Class fallback, Class fallbackFactory) {
        return (T) this.feignClientBuilder.forType(type, serviceId).fallback(fallback).fallbackFactory(fallbackFactory).build();
    }
}
/**
 * @Author jy
 * @Date 2023/7/4 11:04
 * @Version 1.0
 */
@Component
public class DynamicClient {
    @Resource
    DynamicFeignClientFactory<DynamicService> dynamicServiceDynamicFeignClientFactory;

    /**
     * @param serviceId
     * @param url
     * @param params
     * @return
     */
    public Object executePostApi(String serviceId, String url, Object params, Class fallback, Class fallbackFactory) {
        DynamicService dynamicService = dynamicServiceDynamicFeignClientFactory.getFeignClient(DynamicService.class, serviceId, fallback, fallbackFactory);
        return dynamicService.executePostApi(url, params);
    }

    /**
     * @param serviceId
     * @param url
     * @param params
     * @return
     */
    public Object executeGetApi(String serviceId, String url, Object params, Class fallback, Class fallbackFactory) {
        DynamicService dynamicService = dynamicServiceDynamicFeignClientFactory.getFeignClient(DynamicService.class, serviceId, fallback, fallbackFactory);
        return dynamicService.executeGetApi(url, params);
    }
}

2、编写测试方法

	/**
     *
     * @return
     */
    @GetMapping("/testV2")
    @ResponseBody
    public String testV2(String num) {
        logger.info("测试入参,{}",num);
        //调用账户服务查询账户信息
        UserAccountCardQuery param = new UserAccountCardQuery();
        param.setExcludeUnusualState(0);
        param.setFirmId(14L);
        param.setPage(1);
        param.setRows(10);
        Object object = dynamicClient.executePostApi("account-service", "api/account/getPage", param, void.class, void.class);
        CreateTradeRequestDto requestDto = new CreateTradeRequestDto();
        logger.info("动态版,账户结果,{}",object);
        return "ok";
    }

测试结果
在这里插入图片描述
从结果上得出,我们的调用是正常的,说明我们的动态调用就是可以的,那么这样就可以了吗?肯定不是这么简单的,这只是一种调用模式;单纯的POST请求和GET请求大致不差,我们就不一一测试了,下面我们测试下POST和GET请求一起的模式

将原来的测试修改为动态模式的调用
注意!!!我们的请求是POST,但是在路径上加了参数(我们公司就有这样的接口,没办法,所以测到了)

	/**
     *
     * @return
     */
    @GetMapping("/test")
    @ResponseBody
    public String test(String num) {
        logger.info("测试入参,{}",num);
        CreateTradeRequestDto requestDto = new CreateTradeRequestDto();
        requestDto.setAccountId(109751L);
        //BaseOutput<BalanceResponseDto> resolver = payRpc.getAccountBalanceEx(requestDto);
        Object object = dynamicClient.executePostApi("pay-service", "/payment/api/gateway.do?service=payment.fund.service:queryEx", requestDto, void.class, void.class);
        logger.info("改为动态模式,调用支付系统结果返回,{}",object);
        return "ok";
    }

测试结果
在这里插入图片描述
结果报了404因为我们的请求路径出现了乱码,为什么会这样呢?通过源码走查,我们发现由于我们使用的是表达式方式传递url,所以最终会到下图位置
在这里插入图片描述
进一步往下走
在这里插入图片描述
从这里可以看出,在这里我们的入参路径都还是正常的,再往下走一点,还是在这个方法里路径就乱码了
在这里插入图片描述
对于?、=、:的编码解析出现了问题,那么%2F呢,这个源码里是直接用String的替换方法给换成/了。所以最后出现的就是前面几个乱码。既然找到问题了,那我们该怎么处理呢?说实话,最开始我想的是有没有办法,将前面的第二个参数true设置成false,这样也可以处理这个问题,但是没有找到设置的地方(有办法的朋友可以留言下,谢谢)。后来想到了另一个办法,既然这里乱码了,那就不管它了,我在拦截器里重新替换一次就可以了,于是对代码进行了如下修改

添加拦截器

/**
 * @Author jy
 * @Date 2023/6/25 16:54
 * @Version 1.0
 */
@Configuration
public class DynamicRoutingConfig {
    private static String URI = "url";
    @Bean
    public RequestInterceptor cloudContextInterceptor() {
        return template -> {
            Map<String, Collection<String>> headerMap = template.headers();
            //因为获取的是请求里的header所以肯定是不为空的,所以只需判断是否包含url的key并不为空
            if (headerMap.containsKey(URI)&& CollectionUtil.isNotEmpty(headerMap.get(URI))) {
                Target target = template.feignTarget();
                Target.HardCodedTarget hardCodedTarget = new Target.HardCodedTarget(target.type(),target.name(),target.url());
                String uri = headerMap.get(URI).stream().collect(Collectors.toList()).get(0);
                template.feignTarget(hardCodedTarget);
                template.uri(uri);
            }
        };
    }
}

在原来的基础上请求注解上加了headers将url传递到拦截器重新赋值

public interface DynamicService {
    /**
     *
     * @param url
     * @param params
     * @return
     */
    @RequestMapping(value = "{url}",method = RequestMethod.POST,headers = {"url={url}"})
    Object executePostApi(@PathVariable("url") String url, @RequestBody Object params);

    /**
     * 注意这里的url就不要包含?和参数之类的,放在下面的入参里即可,会自动拼接成原来get请求的模样的
     * @param url
     * @param params
     * @return
     */
    @RequestMapping(value = "{url}",method = RequestMethod.GET)
    Object executeGetApi(@PathVariable("url") String url, @SpringQueryMap Object params);
}

经过以上修改后,我们再试试之前的请求
在这里插入图片描述如图所示,成功调用到了对方服务。那么我们又要问了,这样就算成功了吗?肯定不是的,前面我们的禁止掉的哨兵模式还没有打开呢?那我们就打开哨兵吧

三、openfeign+sentinel

将配置恢复到最开始的true,再试试前面的调用。
在这里插入图片描述
从结果中,我们可以看出DynamicService的代理类是没有完成创建的,在spring容器中也没有对应的对象。结合下图,其实连BeanDefinition都没有创建。
在这里插入图片描述
我们都知道openfeign在启动类上会加一个开关注解EnableFeignClients来控制,那么我们这里把这个注解加上就可以了吗?答案是不可以的,还是会报同样的错误,因为我们的DynamicService是没有加FeignClient注解,所以也是扫描不到的。那这个问题怎么解决呢?我这里提供两个方案供大家参考。

1、方案一
我们前面已经说了,是因为在spring中拿不到对象,拿不到对象是没有开始扫描,同时没有扫描到自己,那我们就索性开启扫描,并给DynamicService加上FeignClient注解;这里需要注意的是,我们在给name值时是随意给的,这个不重要,因为最终走的是我们下面参数提供的,这里只是因为必须给一个所以就随意了。后面是加的回调类。

/**
 * @Author jy
 * @Date 2023/7/4 10:56
 * @Version 1.0
 */
@FeignClient(name = "a",fallback = DynamicServiceCallBack.class)
public interface DynamicService {
    /**
     *
     * @param url
     * @param params
     * @return
     */
    @RequestMapping(value = "{url}",method = RequestMethod.POST,headers = {"url={url}"})
    Object executePostApi(@PathVariable("url") String url, @RequestBody Object params);

    /**
     *
     * @param url
     * @param params
     * @return
     */
    @RequestMapping(value = "{url}",method = RequestMethod.GET)
    Object executeGetApi(@PathVariable("url") String url, @SpringQueryMap Object params);
}

经过改造后,再试试
在这里插入图片描述
可以了,服务又可以正常调用了,同时我们还测了一下降级处理
在这里插入图片描述
也是正常回调到的,但是问题也出来了,这样的操作,我们的降级处理类只能是固定的(当然对于想统一处理异常降级机制的刚好),对于想按各自接口来定义降级处理的,就不行了。所以有了第二个思路

2、方案二
前面的报错地方我们已经知道,再仔细一看,获取bean对象后,其实没有拿来做什么就获取了一个对象属性。本着缺什么补什么的原则,缺少BeanDefinition对象,那我们就加,于是我采用了spring的后置处理器,自定义了一个对象按openfeign的形式注入了一个class对象为DynamicService的BeanDefinition对象,这样一来调用是没有问题了,但是降级处理是没有办法实现了,这样相当于阉割了一些功能,这肯定是不行的,于是再想其他办法。既然获取方法不行,我们就改这个获取方式,修改原来的获取降级处理类。

原来的

					Object feignClientFactoryBean = Builder.this.applicationContext
							.getBean("&" + target.type().getName());

					Class fallback = (Class) getFieldValue(feignClientFactoryBean,
							"fallback");
					Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
							"fallbackFactory");
					String beanName = (String) getFieldValue(feignClientFactoryBean,
							"contextId");
					if (!StringUtils.hasText(beanName)) {
						beanName = (String) getFieldValue(feignClientFactoryBean, "name");
					}

要修改这里的话,就需要重写SentinelFeign,因为是final修饰的类,无法继承,所以得重新写一个新类,连带的SentinelInvocationHandler、SentinelFeignAutoConfiguration都需要重新编写,好在基本是照原来的复制下来,部分修改即可。同时还有一个用来传递fallback等信息的ThreaLocal工具类。

配置类,将原来的配置类顶替掉

/**
 * sentinel 配置
 */
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {

    @Bean
    @Primary
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.sentinel.enabled")
    public Feign.Builder feignSentinelBuilderPrimary() {
        return CloudSentinelFeign.builder();
    }
}
**
 * 支持自动降级注入 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}
 */
public final class CloudSentinelFeign {

    private CloudSentinelFeign() {

    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder extends Feign.Builder implements ApplicationContextAware {

        private Contract contract = new Contract.Default();

        private ApplicationContext applicationContext;

        private FeignContext feignContext;

        @Override
        public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Builder contract(Contract contract) {
            this.contract = contract;
            return this;
        }

        @Override
        public Feign build() {
            super.invocationHandlerFactory(new InvocationHandlerFactory() {
                @Override
                public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
                    // 查找 FeignClient 上的 降级策略
                    Class<?> fallback = (Class<?>) DynamicFeignHolder.getFallBack();
                    Class<?> fallbackFactory = (Class<?>) DynamicFeignHolder.getFallBackFactory();
                    String beanName = target.name();
       
                    Object fallbackInstance;
                    FallbackFactory<?> fallbackFactoryInstance;
                    if (void.class != fallback&&null!=fallback) {
                        fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
                        return new CloudSentinelInvocationHandler(target, dispatch,
                                new FallbackFactory.Default(fallbackInstance));
                    }

                    if (void.class != fallbackFactory&&null!=fallbackFactory) {
                        fallbackFactoryInstance = (FallbackFactory<?>) getFromContext(beanName, "fallbackFactory",
                                fallbackFactory, FallbackFactory.class);
                        return new CloudSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
                    }

                    return new CloudSentinelInvocationHandler(target, dispatch);
                }

                private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
                    Object fallbackInstance = feignContext.getInstance(name, fallbackType);
                    if (fallbackInstance == null) {
                        throw new IllegalStateException(String.format(
                                "No %s instance of type %s found for feign client %s", type, fallbackType, name));
                    }

                    if (!targetType.isAssignableFrom(fallbackType)) {
                        throw new IllegalStateException(String.format(
                                "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
                                type, fallbackType, targetType, name));
                    }
                    return fallbackInstance;
                }
            });

            super.contract(new SentinelContractHolder(contract));
            return super.build();
        }

        private Object getFieldValue(Object instance, String fieldName) {
            Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
            field.setAccessible(true);
            try {
                return field.get(instance);
            } catch (IllegalAccessException e) {
                // ignore
            }
            return null;
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
            feignContext = this.applicationContext.getBean(FeignContext.class);
        }

    }

}

这里主要是重写create方法

/**
 * 支持自动降级注入 重写 {@link SentinelInvocationHandler}
 */
@Slf4j
public class CloudSentinelInvocationHandler implements InvocationHandler {
    private final Target<?> target;

    private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;

    private FallbackFactory<?> fallbackFactory;

    private Map<Method, Method> fallbackMethodMap;

    public CloudSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
                                          FallbackFactory<?> fallbackFactory) {
        this.target = checkNotNull(target, "target");
        this.dispatch = checkNotNull(dispatch, "dispatch");
        this.fallbackFactory = fallbackFactory;
        this.fallbackMethodMap = toFallbackMethod(dispatch);
    }

    public CloudSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
        this.target = checkNotNull(target, "target");
        this.dispatch = checkNotNull(dispatch, "dispatch");
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args)
            throws Throwable {
        if ("equals".equals(method.getName())) {
            try {
                Object otherHandler = args.length > 0 && args[0] != null
                        ? Proxy.getInvocationHandler(args[0])
                        : null;
                return equals(otherHandler);
            } catch (IllegalArgumentException e) {
                return false;
            }
        } else if ("hashCode".equals(method.getName())) {
            return hashCode();
        } else if ("toString".equals(method.getName())) {
            return toString();
        }

        Object result;
        InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
        // only handle by HardCodedTarget
        if (target instanceof Target.HardCodedTarget) {
            Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
            MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
                    .get(hardCodedTarget.type().getName()
                            + Feign.configKey(hardCodedTarget.type(), method));
            // resource default is HttpMethod:protocol://url
            if (methodMetadata == null) {
                result = methodHandler.invoke(args);
            } else {
                String resourceName = methodMetadata.template().method().toUpperCase()
                        + ":" + hardCodedTarget.url() + methodMetadata.template().path();
                Entry entry = null;
                try {
                    ContextUtil.enter(resourceName);
                    entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
                    result = methodHandler.invoke(args);
                } catch (Throwable ex) {
                    // fallback handle
                    if (!BlockException.isBlockException(ex)) {
                        Tracer.traceEntry(ex, entry);
                    }
                    if (fallbackFactory != null) {
                        try {
                            Object fallbackResult = fallbackMethodMap.get(method)
                                    .invoke(fallbackFactory.create(ex), args);
                            return fallbackResult;
                        } catch (IllegalAccessException e) {
                            // shouldn't happen as method is public due to being an
                            // interface
                            throw new AssertionError(e);
                        } catch (InvocationTargetException e) {
                            throw new AssertionError(e.getCause());
                        }
                    } else {
                        //主动降级注入的关键点
                        // 若是Result类型 执行自动降级返回Result
                        if (Result.class == method.getReturnType()) {
                            log.error("feign 服务间调用异常", ex);
                            //SentinelExceptionUtil自己封装的判断sentinel熔断类型
                            return null;
                        } else {
                            throw ex;
                        }
                    }
                } finally {
                    if (entry != null) {
                        entry.exit(1, args);
                    }
                    ContextUtil.exit();
                }
            }
        } else {
            // other target type using default strategy
            result = methodHandler.invoke(args);
        }

        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SentinelInvocationHandler) {
            CloudSentinelInvocationHandler other = (CloudSentinelInvocationHandler) obj;
            return target.equals(other.target);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return target.hashCode();
    }

    @Override
    public String toString() {
        return target.toString();
    }

    static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
        Map<Method, Method> result = new LinkedHashMap<>();
        for (Method method : dispatch.keySet()) {
            method.setAccessible(true);
            result.put(method, method);
        }
        return result;
    }

}

该类就是单纯的复制即可,因为原类是类路径保护的无法引用。

/**
 * @Author jy
 * @Date 2023/7/4 11:21
 * @Version 1.0
 */
public class DynamicFeignHolder {
    private static final ThreadLocal<Object> HOLDER = new ThreadLocal<>();
    private static final ThreadLocal<Object> FACTORY = new ThreadLocal<>();

    /**
     * 添加
     * @param obj
     */
    public static void setFallBack(Object obj) {
        HOLDER.set(obj);
    }

    /**
     * 获取
     * @return
     */
    public static Object getFallBack() {
        return HOLDER.get();
    }

    /**
     * 清除
     */
    public static void clearFallBack() {
        HOLDER.remove();
    }

    /**
     * 添加
     * @param obj
     */
    public static void setFallBackFactory(Object obj) {
        FACTORY.set(obj);
    }

    /**
     * 获取
     * @return
     */
    public static Object getFallBackFactory() {
        return FACTORY.get();
    }

    /**
     * 清除
     */
    public static void clearFallBackFactory() {
        FACTORY.remove();
    }
}

用于传递fallback和fallbackFactory的

原来的调用也需要做稍许改变,增加了降级类的传递操作及ThreadLocal销毁的动作

/**
 * @Author jy
 * @Date 2023/7/4 10:50
 * @Version 1.0
 */
@Component
public class DynamicFeignClientFactory<T> {
    private FeignClientBuilder feignClientBuilder;

    /**
     * @param applicationContext
     */
    public DynamicFeignClientFactory(ApplicationContext applicationContext) {
        this.feignClientBuilder = new FeignClientBuilder(applicationContext);
    }

    /**
     * @param type
     * @param serviceId
     * @return
     */
    public T getFeignClient(final Class<T> type, String serviceId, Class fallback, Class fallbackFactory) {
        if (ObjectUtil.isNotEmpty(fallback)) {
            DynamicFeignHolder.setFallBack(fallback);
        }
        if (ObjectUtil.isNotEmpty(fallbackFactory)) {
            DynamicFeignHolder.setFallBackFactory(fallbackFactory);
        }
        return (T) this.feignClientBuilder.forType(type, serviceId).fallback(fallback).fallbackFactory(fallbackFactory).build();
    }
}
/**
 * @Author jy
 * @Date 2023/7/4 11:04
 * @Version 1.0
 */
@Component
public class DynamicClient {
    @Resource
    DynamicFeignClientFactory<DynamicService> dynamicServiceDynamicFeignClientFactory;

    /**
     * @param serviceId
     * @param url
     * @param params
     * @return
     */
    public Object executePostApi(String serviceId, String url, Object params, Class fallback, Class fallbackFactory) {
        try {
            DynamicService dynamicService = dynamicServiceDynamicFeignClientFactory.getFeignClient(DynamicService.class, serviceId, fallback, fallbackFactory);
            return dynamicService.executePostApi(url, params);
        } finally {
            DynamicFeignHolder.clearFallBack();
            DynamicFeignHolder.clearFallBackFactory();
        }
    }

    /**
     * @param serviceId
     * @param url
     * @param params
     * @return
     */
    public Object executeGetApi(String serviceId, String url, Object params, Class fallback, Class fallbackFactory) {
        try {
            DynamicService dynamicService = dynamicServiceDynamicFeignClientFactory.getFeignClient(DynamicService.class, serviceId, fallback, fallbackFactory);
            return dynamicService.executeGetApi(url, params);
        } finally {
            DynamicFeignHolder.clearFallBack();
            DynamicFeignHolder.clearFallBackFactory();
        }
    }
}

启动类上的开关注解也可去掉。至此改造也就完成了,经过测试调用成功,降级处理也顺利启用。在我们项目中出现的两个问题算是解决了,不过还有没有其他问题这个就没办法保证了,毕竟测试的人力、时间、场景有限。

总结

这样改造好像也没有比写RPC接口方便多少,O(∩_∩)O哈哈~ 算了只当记录学习过程,总结经验,继续加油吧!!!!!!望大家多指教

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值