feign远程调用不通,通过网关调用方便本地调试

背景

本地开发使用二方包里的feignclient时,本地调用远程服务超时(有墙等不可抗力),但是可以通过走网关的方式调用远程服务,所以修改(间接和直接)二方包里的feignclient以通过走网关调用远程服务。

前置知识,feignclient里指定了url时,优先通过url访问,而不是通过服务发现调用远程服务。

二方包提供的feignclient

    @FeignClient(name = "test",
            configuration = configuration.class)
    interface OldClient {
        @RequestMapping("/test")
        String test();

    }

方法一

使用方式:

继承OldClient,复制旧feignclient的参数,手动指定服务的网关地址url。

    @FeignClient(name = "test",
            url = "www.baidu.com",
            configuration = configuration.class)
    interface NewClient extends OldClient {
        
    }
@FeignClient(name = "test",
        url = "${local-gateway.test:}",
        configuration = configuration.class)
interface NewClient extends OldClient {

}

${local-gateway.test:}

通过spel表达式,读取local-gateway.test

local-gateway.test配置到application-local.yml等配置文件中。

只在local环境启动时,能读取到local-gateway.test,通过走网关的方式调用服务。在其他环境时,spel读取不到值,会取缺省值也就是空字符串,也与@FeignClient源码里中url()提供的缺省值一致。此时,会通过服务发现调用远程服务。

注意事项:

开发迭代时,要检查二方包中的client与我们自定义的client中的@FeignClient参数是否一致(一般二方包提供的client不会随意改变参数,迭代的时候检查一下是最保险的)。

(二方包提供者能通过spel的方式指定client的url就方便了。当然,本地能直接调用远程服务的话就没这个文章了)

(虽然FeignClient上有@Inherited注解,但是接口间不能继承注解,所以具体有什么作用暂时不知道,有知道的也请留言)

方法二

使用方式:

注册FeignClient时,修改设置url的代码,设置成自定义的地址。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

找到@EnableFeignClients,然后找到@Import(FeignClientsRegistrar.class)。

然后找到FeignClientsRegistrar.class。

前置知识:spring的Import相关知识可以先百度学习一下。

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {

找到registerFeignClient方法,找到第16行

factoryBean.setUrl(getUrl(beanFactory, attributes));

这行代码就是设置url的逻辑。

因为FeignClientsRegistrar.class不能继承,所以自定义一个@EnableFeignClients和自定义一个FeignClientsRegistrar.class,在设置url时改写成自己的逻辑。将自定义FeignClientsRegistrar.class里与@EnableFeignClients相关的代码替换成自定义的@EnableFeignClients。

最后在启动类上使用自定义的@EnableFeignClients。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar2.class)
public @interface EnableCustomFeign {
       factoryBean.setContextId(contextId);
        factoryBean.setType(clazz);
        factoryBean.setRefreshableClient(isClientRefreshEnabled());
        BeanDefinitionBuilder definition = 
        BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
            if ("local".equals(SpringUtil.getActiveProfile())) {
                factoryBean.setUrl("www.baidu.com");
            } else {
                factoryBean.setUrl(getUrl(beanFactory, attributes));
            }
            factoryBean.setPath(getPath(beanFactory, attributes));

使用方式与旧的@EnableFeignClients一致。

注意事项:

FeignClient相关的依赖版本变动时,需要检查我们重写后的类与版本变动后的类是否一致

方法三

使用方式:

注册feignclient之前,修改feignclient注解的参数。

前置知识:

前置知识:通过反射动态修改自定义注解属性值_反射修改注解的值_着实着迷的博客-CSDN博客

参考代码:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

由于spring是通过直接读取class文件,获取元数据,所以通过反射修改参数不可行。把我们的思路转移到直接修改class文件。

通过asm提供的库修改字节码。

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        ServletContextResourcePatternResolver servletContextResourcePatternResolver = new ServletContextResourcePatternResolver(resolver);
        Resource[] resources1 = servletContextResourcePatternResolver.getResources("classpath*:com/example/LocalCodeApplication$xxx.class");
        Resource resource = resources1[0];

        ClassReader classReader = new ClassReader(resource.getInputStream());
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
            @Override
            public AnnotationVisitor visitAnnotation(String s, boolean b) {
                AnnotationVisitor annotationVisitor = super.visitAnnotation(s, b);
                if (s.equals("Lorg/springframework/cloud/openfeign/FeignClient;")) {
                    return new AnnotationVisitor(Opcodes.ASM5, annotationVisitor) {
                        @Override
                        public void visit(String s, Object o) {
                            if (s.equals("url")) {
                                super.visit(s, "www.360.com");
                            } else {
                                super.visit(s, o);
                            }
                        }
                    };
                }
                return annotationVisitor;
            }
        };
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
        byte[] byteArray = classWriter.toByteArray();

        FileUtil.writeBytes(byteArray, resource.getURI().getPath());

注意事项:保存class时,仅可在本地保存。服务器上运行的是jar包,覆盖class文件时会报错。可将运行时修改字节码改为编译时修改字节码(暂时不会)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值