Spring Cloud Feign 分析(三)之自定义注解实现版本兼容

前面我们讲解到@FeignClient在SpringBoot1.x与SpringBoot2.x版本之间不兼容,无法复用的问题,并且使用了路径覆盖大法重写@FeignClient这个注解类,使用这种方式基本零修改,本节我们则使用另外一种方式(继承FeignClientsRegistrar)实现,这种方式可以了解@FeignClient注解注册到Spring IOC的整个过程,更有助于我们后续分析Feign原理!


自定义注解实现版本兼容思路

  1. 自定义@EnableFeignClients注解(笔者尝试过使用路径覆盖大法,发现无效)
  2. 继承FeignClientsRegistrar,扫描org.springframework.cloud.openfeign.FeignClient注解与org.springframework.cloud.netflix.feign.FeignClient注解
  3. 注册@FeignClient到Spring IOC容器中

package org.springframework.cloud.openfeign;

/**
 * 兼容SpringBoot1.x、SpringBoot2.x版本的<code>@FeignClient</code>注解
 * {@link org.springframework.cloud.netflix.feign.FeignClient}
 * {@link org.springframework.cloud.openfeign.FeignClient}
 * e.g:<code>@EnableCompositeFeignClients</code>替换<code>@EnableFeignClients</code>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CompositeFeignClientsRegistrar.class)
public @interface EnableCompositeFeignClients {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] defaultConfiguration() default {};
    Class<?>[] clients() default {};
}

上面这个地方就是自定义一个注解,然后将启动类上的@EnableFeignClients替换成我们的@EnableCompositeFeignClients注解,然后里面的方法定义直接拷贝
@EnableFeignClients注解类中的,然后@Import(CompositeFeignClientsRegistrar.class)加上这个自定义的注册类


CompositeFeignClientsRegistrar

package org.springframework.cloud.openfeign;

/**
 * CompositeFeignClientsRegistrar
 * 混合FeignClient注册实现
 * {@link org.springframework.cloud.netflix.feign.FeignClient}
 * {@link org.springframework.cloud.openfeign.FeignClient}
 */
public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        super.registerBeanDefinitions(new AnnotationMetadataWrapper(metadata), registry);
    }

    /**
     * 扫描注解,兼容以前的FeignClient
     * {@link org.springframework.cloud.netflix.feign.FeignClient}
     * {@link org.springframework.cloud.openfeign.FeignClient}
     *
     * @return ClassPathScanningCandidateComponentProvider
     */
    @Override
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        ClassPathScanningCandidateComponentProvider scanner = super.getScanner();
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(org.springframework.cloud.netflix.feign.FeignClient.class));
        return new ClassPathScanningCandidateComponentProviderWrapper(scanner);
    }
}

经过分析FeignClientsRegistrar父类中的FeignClientsRegistrar#registerDefaultConfiguration()方法和FeignClientsRegistrar#registerFeignClients()方法,我们发现在getScanner()会扫描定义了@FeignClient注解的类,所以这个地方我们对这个方法进行重写,然后把我们openfeign.FeignClient与netflix.feign.FeignClient加入到扫描路径中,然后返回一个ClassPathScanningCandidateComponentProviderWrapper包装类


ClassPathScanningCandidateComponentProviderWrapper

package org.springframework.cloud.openfeign;

public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    /**
     * ClassPathScanningCandidateComponentProviderWrapper
     * {@link FeignClientsRegistrar#registerFeignClients}
     * 重写{@link ClassPathScanningCandidateComponentProvider#findCandidateComponents}
     * <p>
     * {@link org.springframework.cloud.openfeign.FeignClient}
     * {@link org.springframework.cloud.netflix.feign.FeignClient}
     * 实现老版本的<code>@FeignClient</code>对象生成
     */
    public static class ClassPathScanningCandidateComponentProviderWrapper
            extends ClassPathScanningCandidateComponentProvider {
        private ClassPathScanningCandidateComponentProvider scanner;

        public ClassPathScanningCandidateComponentProviderWrapper(
                ClassPathScanningCandidateComponentProvider scanner) {
            this.scanner = scanner;
        }

        /**
         * findCandidateComponents
         * 扫描{@link org.springframework.cloud.openfeign.FeignClient}
         * {@link org.springframework.cloud.netflix.feign.FeignClient}并生成BeanDefinition
         *
         * @param basePackage basePackage
         * @return Set<BeanDefinition>
         */
        @Override
        public Set<BeanDefinition> findCandidateComponents(String basePackage) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            return candidateComponents.stream().map(beanDefinition ->
                    new AnnotatedBeanDefinitionWrapper((AnnotatedBeanDefinition) beanDefinition)
            ).collect(Collectors.toSet());
        }

        /**
         * addIncludeFilter
         *
         * @param includeFilter includeFilter
         */
        @Override
        public void addIncludeFilter(TypeFilter includeFilter) {
            scanner.addIncludeFilter(includeFilter);
        }

        /**
         * setResourceLoader
         *
         * @param resourceLoader resourceLoader
         */
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            scanner.setResourceLoader(resourceLoader);
        }
    }
}

为什么需要包装ClassPathScanningCandidateComponentProvider这个对象呢?因为在FeignClientsRegistrar#registerFeignClients()方法中会扫描指定注解(@FeignClient)的路径,然后调用findCandidateComponents()这个方法来生成满足条件的BeanDefinition用于后面注册到Spring IOC容器中,所以我们重写getScanner()方法,然后返回了一个ClassPathScanningCandidateComponentProviderWrapper包装对象。


AnnotatedBeanDefinitionWrapper

package org.springframework.cloud.openfeign;

public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    /**
     * AnnotatedBeanDefinitionWrapper
     * 主要用于生成{@link org.springframework.beans.factory.config.BeanDefinition}
     * <p>
     * {@link org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition}
     */
    @SuppressFBWarnings("SE_NO_SERIALVERSIONID")
    public static class AnnotatedBeanDefinitionWrapper extends GenericBeanDefinition
            implements AnnotatedBeanDefinition {

        private AnnotatedBeanDefinition beanDefinition;

        public AnnotatedBeanDefinitionWrapper(AnnotatedBeanDefinition beanDefinition) {
            this.beanDefinition = beanDefinition;
        }

        @Override
        public AnnotationMetadata getMetadata() {
            return new AnnotationMetadataWrapper(beanDefinition.getMetadata());
        }

        @Override
        public MethodMetadata getFactoryMethodMetadata() {
            return beanDefinition.getFactoryMethodMetadata();
        }

        @Override
        public boolean equals(Object other) {
            return beanDefinition.equals(other);
        }

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

在findCandidateComponents()这个方法中我们也返回一个AnnotatedBeanDefinitionWrapper包装类,这个也是相同的疑问,为什么需要这个包装类?因为getMetadata()这个方法,我们需要返回一个注解元数据,这个也简单解释下,因为我需要实现注解映射,就是我需要将@EnableFeignClients注解映射成@EnableCompositeFeignClients这个注解的数据,如果还是不好理解,那可以仔细研究下这一节的代码片段!


AnnotationMetadataWrapper

package org.springframework.cloud.openfeign;

public class CompositeFeignClientsRegistrar extends FeignClientsRegistrar {
    /**
     * 注解元数据包装类
     * {@link FeignClientsRegistrar#registerFeignClients}{@link FeignClientsRegistrar#registerDefaultConfiguration}
     * <p>
     * 在{@link FeignClientsRegistrar}中指定了获取{@link EnableFeignClients}{@link FeignClient}注解的参数配置
     * 为了兼容我们{@link org.springframework.cloud.netflix.feign.FeignClient}
     * {@link org.springframework.cloud.openfeign.FeignClient}注解,我们需要进行注解映射
     * 包装{@link org.springframework.core.type.StandardAnnotationMetadata}实现我们注解的映射匹配关系
     * <p>
     * {@link EnableFeignClients} => {@link org.springframework.cloud.openfeign.EnableCompositeFeignClients}
     * {@link FeignClient} => {@link org.springframework.cloud.openfeign.FeignClient}
     * {@link FeignClient} => {@link org.springframework.cloud.netflix.feign.FeignClient}
     * 映射关系按照优先级执行
     */
    public static class AnnotationMetadataWrapper implements AnnotationMetadata {
        /**
         * 用于兼容老版本的@FeignClient {@link org.springframework.cloud.netflix.feign.FeignClient}
         * 重新定义annotationTypes以下Key用于兼容
         * {@link FeignClientsRegistrar#registerFeignClients}{@link FeignClientsRegistrar#registerDefaultConfiguration}
         * <p>
         * 默认规则优先查找并使用 {@link org.springframework.cloud.openfeign.FeignClient}
         * 其次使用{@link org.springframework.cloud.netflix.feign.FeignClient}
         */
        private Map<String, List<String>> annotationTypes = MapBuilder.createWith(EnableFeignClients.class.getName(),
                Collections.singletonList(EnableCompositeFeignClients.class.getName()))
                .with(FeignClient.class.getName(), Arrays.asList(FeignClient.class.getName(),
                        org.springframework.cloud.netflix.feign.FeignClient.class.getName()))
                .with(FeignClient.class.getCanonicalName(), Arrays.asList(FeignClient.class.getCanonicalName(),
                        org.springframework.cloud.netflix.feign.FeignClient.class.getCanonicalName()))
                .build();
        private AnnotationMetadata metadata;

        public AnnotationMetadataWrapper(AnnotationMetadata metadata) {
            this.metadata = metadata;
        }

        protected List<String> wrapAnnotationName(String annotationName) {
            return annotationTypes.getOrDefault(annotationName, Collections.emptyList());
        }

        @Override
        public Set<String> getAnnotationTypes() {
            return metadata.getAnnotationTypes();
        }

        @Override
        public Set<String> getMetaAnnotationTypes(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Set<String> metaAnnotationTypes = metadata.getMetaAnnotationTypes(name);
                if (CollectionUtils.isNotEmpty(metaAnnotationTypes)) {
                    return metaAnnotationTypes;
                }
            }
            return metadata.getMetaAnnotationTypes(annotationName);
        }

        @Override
        public boolean hasAnnotation(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean hasAnnotation = metadata.hasAnnotation(name);
                if (hasAnnotation) {
                    return true;
                }
            }
            return metadata.hasAnnotation(annotationName);
        }

        @Override
        public boolean hasMetaAnnotation(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean hasMetaAnnotation = metadata.hasMetaAnnotation(name);
                if (hasMetaAnnotation) {
                    return true;
                }
            }
            return metadata.hasMetaAnnotation(annotationName);
        }

        @Override
        public boolean hasAnnotatedMethods(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean hasAnnotatedMethods = metadata.hasAnnotatedMethods(name);
                if (hasAnnotatedMethods) {
                    return true;
                }
            }
            return metadata.hasAnnotatedMethods(annotationName);
        }

        @Override
        public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Set<MethodMetadata> annotatedMethods = metadata.getAnnotatedMethods(name);
                if (CollectionUtils.isNotEmpty(annotatedMethods)) {
                    return annotatedMethods;
                }
            }
            return metadata.getAnnotatedMethods(annotationName);
        }

        @Override
        public MergedAnnotations getAnnotations() {
            return metadata.getAnnotations();
        }

        @Override
        public boolean isAnnotated(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                boolean isAnnotated = metadata.isAnnotated(name);
                if (isAnnotated) {
                    return true;
                }
            }
            return metadata.isAnnotated(annotationName);
        }

        @Override
        public Map<String, Object> getAnnotationAttributes(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(name);
                if (Objects.nonNull(annotationAttributes) && !annotationAttributes.isEmpty()) {
                    return annotationAttributes;
                }
            }
            return metadata.getAnnotationAttributes(annotationName);
        }

        @Override
        public Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(name, classValuesAsString);
                if (Objects.nonNull(annotationAttributes) && !annotationAttributes.isEmpty()) {
                    return annotationAttributes;
                }
            }
            return metadata.getAnnotationAttributes(annotationName, classValuesAsString);
        }

        @Override
        public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                MultiValueMap<String, Object> allAnnotationAttributes = metadata.getAllAnnotationAttributes(name);
                if (Objects.nonNull(allAnnotationAttributes) && !allAnnotationAttributes.isEmpty()) {
                    return allAnnotationAttributes;
                }
            }
            return metadata.getAllAnnotationAttributes(annotationName);
        }

        @Override
        public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName,
                                                                        boolean classValuesAsString) {
            List<String> annotationNameList = wrapAnnotationName(annotationName);
            for (String name : annotationNameList) {
                MultiValueMap<String, Object> allAnnotationAttributes =
                        metadata.getAllAnnotationAttributes(name, classValuesAsString);
                if (Objects.nonNull(allAnnotationAttributes) && !allAnnotationAttributes.isEmpty()) {
                    return allAnnotationAttributes;
                }
            }
            return metadata.getAllAnnotationAttributes(annotationName, classValuesAsString);
        }

        @Override
        public String getClassName() {
            return metadata.getClassName();
        }

        @Override
        public boolean isInterface() {
            return metadata.isInterface();
        }

        @Override
        public boolean isAnnotation() {
            return metadata.isAnnotation();
        }

        @Override
        public boolean isAbstract() {
            return metadata.isAbstract();
        }

        @Override
        public boolean isConcrete() {
            return metadata.isConcrete();
        }

        @Override
        public boolean isFinal() {
            return metadata.isFinal();
        }

        @Override
        public boolean isIndependent() {
            return metadata.isIndependent();
        }

        @Override
        public boolean hasEnclosingClass() {
            return metadata.hasEnclosingClass();
        }

        @Override
        public String getEnclosingClassName() {
            return metadata.getEnclosingClassName();
        }

        @Override
        public boolean hasSuperClass() {
            return metadata.hasSuperClass();
        }

        @Override
        public String getSuperClassName() {
            return metadata.getSuperClassName();
        }

        @Override
        public String[] getInterfaceNames() {
            return metadata.getInterfaceNames();
        }

        @Override
        public String[] getMemberClassNames() {
            return metadata.getMemberClassNames();
        }
    }
}

AnnotationMetadataWrapper这个注解元数据包装类其实就是一个注解映射作用,因为在FeignClientsRegistrar父类中会获取@EnableFeignClients、@FeignClient注解对应的属性,所以这里我们就做一个映射关系,映射关系如下:

  1. {@link EnableFeignClients} => {@link org.springframework.cloud.openfeign.EnableCompositeFeignClients}
  2. {@link FeignClient} => {@link org.springframework.cloud.openfeign.FeignClient}
  3. {@link FeignClient} => {@link org.springframework.cloud.netflix.feign.FeignClient}

我们按照映射优先级获取映射之后注解的属性返回,默认优先查找openfeign.FeignClient的属性,当查询不到再查询netflix.feign.FeignClient注解的属性。


至此,到了文章末尾,也大概总结下吧,我们使用自定义注解这种方式呢,我们可以更加灵活的管理和实现,也有助于我们阅读后续的Feign章节。当然这样的方式可能对不熟悉源码的同学不太友好,如果对FeignClientsRegistrar相关逻辑不清楚的可以参阅Spring Cloud Feign 分析(一)之FeignClient注册过程,能加快我们理解本节内容!




原文链接:https://www.jianshu.com/p/36c4e01c21dd

相关资源:

GitOps 初探 - Dcsdn

行业分类-设备装置-一种用于数据库运维的移动平台及其使用方法 - Dcsdn

redis(哨兵模式配置) - Dcsdn

Redis数据的导出和导入 - Dcsdn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值