使用过Spring Cloud Netflix组件的同学都知道,Netflix组件的版本兼容性几乎等于零,特别是大版本变化简直就是噩梦,所以本节主要讲解如何实现Feign的版本兼容,如何兼容SpringBoot1.x、SpringBoot2.x版本中的Feign使用!这样我们在SpringBoot1.x版本使用@FeignClient在后续升级到SpringBoot2.x之后也不需要我们进行单独修改,毕竟现在微服务众多,全部重新使用SpringBoot2.x版本的FeignClient也是一件不小的事情,毕竟你改了代码那就可能出现问题。所以这一节我们主要提供注解版本的兼容方式(基本零修改),顺带分析下FeignClientsRegistrar部分原理!
@EnableFeignClients注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] defaultConfiguration() default {}; Class<?>[] clients() default {}; }
这里我们先看看FeignClient默认实现,通过在启动类上面注解这个类即可开启FeignClient客户端,那么这里我们看看原始FeignClientsRegistrar做了什么事情,为什么就不能兼容以前的版本呢?
FeignClientsRegistrar#registerDefaultConfiguration
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { ...... @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } //根据@EnableFeignClients中参数defaultConfiguration注册FeignClient的默认配置(FeignClientsConfiguration) private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注意这里的获取的EnableFeignClients.class.getName()这个属性 //我们后面要做自定义注解的映射 Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } ...... }
这里我们简单的讲解下,从@EnableFeignClients注解中获取defaultConfiguration参数并生产默认配置,其中这个地方关注点metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true),后面我们的自定义注解会和这个形成映射关系
FeignClientsRegistrar#registerFeignClients
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { ...... @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } //注册@FeignClient类到IOC容器中 public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>(); Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { //获取扫描器, ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); //扫描org.springframework.cloud.openfeign.FeignClient注解类 scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); Set<String> basePackages = getBasePackages(metadata); for (String basePackage : basePackages) { //添加满足条件的BeanDefinition candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); } } else { for (Class<?> clazz : clients) { candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz)); } } for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); //获取org.springframework.cloud.openfeign.FeignClient对应的属性 Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); //注册到IOC容器 registerFeignClient(registry, annotationMetadata, attributes); } } } }
这个地方我们也简单的解释下具体做了哪些事情
- 扫描标注了org.springframework.cloud.openfeign.FeignClient注解类
- 通过basePackages路径添加添加满足条件的BeanDefinition
- 通过BeanDefinition集合获取org.springframework.cloud.openfeign.FeignClient注解对应的属性
- 注册Bean到IOC容器中
FeignClient注解实现版本兼容
package org.springframework.cloud.netflix.feign; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @org.springframework.cloud.openfeign.FeignClient public @interface FeignClient { /** * The name of the service with optional protocol prefix. Synonym for {@link #name() * name}. A name must be specified for all clients, whether or not a url is provided. * Can be specified as property key, eg: ${propertyKey}. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "name") String value() default ""; /** * The service id with optional protocol prefix. Synonym for {@link #value() value}. * * @deprecated use {@link #name() name} instead */ @Deprecated @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "serviceId") String serviceId() default ""; /** * The service id with optional protocol prefix. Synonym for {@link #value() value}. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "value") String name() default ""; /** * Sets the <code>@Qualifier</code> value for the feign client. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "qualifier") String qualifier() default ""; /** * An absolute URL or resolvable hostname (the protocol is optional). */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "url") String url() default ""; /** * Whether 404s should be decoded instead of throwing FeignExceptions */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "decode404") boolean decode404() default false; /** * A custom <code>@Configuration</code> for the feign client. Can contain override * <code>@Bean</code> definition for the pieces that make up the client, for instance * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. * * @see FeignClientsConfiguration for the defaults */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "configuration") Class<?>[] configuration() default {}; /** * Fallback class for the specified Feign client interface. The fallback class must * implement the interface annotated by this annotation and be a valid spring bean. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "fallback") Class<?> fallback() default void.class; /** * Define a fallback factory for the specified Feign client interface. The fallback * factory must produce instances of fallback classes that implement the interface * annotated by {@link FeignClient}. The fallback factory must be a valid spring * bean. * * @see feign.hystrix.FallbackFactory for details. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "fallbackFactory") Class<?> fallbackFactory() default void.class; /** * Path prefix to be used by all method-level mappings. Can be used with or without * <code>@RibbonClient</code>. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "path") String path() default ""; /** * Whether to mark the feign proxy as a primary bean. Defaults to false. */ @AliasFor(annotation = org.springframework.cloud.openfeign.FeignClient.class, attribute = "primary") boolean primary() default true; }
这里我们采用路径覆盖大法,我们重新定义一个org.springframework.cloud.netflix.feign这个包名,然后定义一个FeignClient注解类,然后我们在这个类上在引入一个注解@org.springframework.cloud.openfeign.FeignClient,把SpringBoot2.x版本的FeignClient引入进来,这样我们就实现了版本兼容,我们以前的SpringBoot1.x版本的可以不用修改就可以实现版本兼容。然后在启动类上面使用标准的@EnableFeignClients注解
注意事项
spring: main: allow-bean-definition-overriding: true
在SpringBoot2.1之前,这个开关默认是打开的,及可以重复定义Bean,但是在SpringBoot2.1之后这个配置默认是false,所以如果我们的SpringBoot版本为2.1之后的,那么这个参数需要设置为true,及允许后面的Bean可以覆盖之前相同名称的Bean,因为这个地方registerClientConfiguration会重复定义Bean,建议根据情况配置,笔者这里的业务默认都打开了的,毕竟我们的FeignClient配置是一样的,所以允许重复定义。
已经讲解到这里了,这一节我们通过路径覆盖大法,重写老版本的netflix.FeignClient注解在其之上加上新版本的openfeign.FeignClient注解来实现兼容,下一节我们将通过继承FeignClientsRegistrar来实现的方式,这种方式的实现能让我们更加清楚的了解到@FeignClient的注册方式,如果觉得总结不错就点赞关注吧!
原文链接:https://www.jianshu.com/p/e319a7a550a2
相关资源:
热门文章
- 「1月10日」最高速度20.3M/S,2025年V2ray/Shadowrocket/SSR/Clash每天更新免费节点订阅链接
- 海拉鲁原型城市(海拉鲁人)
- 「1月6日」最高速度19.6M/S,2025年Clash/SSR/V2ray/Shadowrocket每天更新免费节点订阅链接
- 家用小型饲料颗粒机500元一台(家用小型饲料颗粒机图片视频展示)
- 宠物领养协议书怎么生效图片 宠物领养协议书怎么生效图片大全
- springboot base64_base64转码
- 宠物领养平台app 小程序叫什么来着 宠物领养平台app 小程序叫什么来着的
- 猫咪驱虫的多少钱(猫咪驱虫多少钱一针)
- 上海宠物中心领养地址在哪(上海宠物领养中心免费领养狗狗)
- Spring Cloud Feign 分析之FeignClient注解实现版本兼容