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版本的@FeignClient注解
 * {@link org.springframework.cloud.netflix.feign.FeignClient}
 * {@link org.springframework.cloud.openfeign.FeignClient}
 * e.g:@EnableCompositeFeignClients替换@EnableFeignClients
 */
@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}
     * 

* {@link org.springframework.cloud.openfeign.FeignClient} * {@link org.springframework.cloud.netflix.feign.FeignClient} * 实现老版本的@FeignClient对象生成 */ 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 */ @Override public Set findCandidateComponents(String basePackage) { Set 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}
     * 

* {@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}
     * 

* 在{@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}实现我们注解的映射匹配关系 *

* {@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} *

* 默认规则优先查找并使用 {@link org.springframework.cloud.openfeign.FeignClient} * 其次使用{@link org.springframework.cloud.netflix.feign.FeignClient} */ private Map> 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 wrapAnnotationName(String annotationName) { return annotationTypes.getOrDefault(annotationName, Collections.emptyList()); } @Override public Set getAnnotationTypes() { return metadata.getAnnotationTypes(); } @Override public Set getMetaAnnotationTypes(String annotationName) { List annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { Set metaAnnotationTypes = metadata.getMetaAnnotationTypes(name); if (CollectionUtils.isNotEmpty(metaAnnotationTypes)) { return metaAnnotationTypes; } } return metadata.getMetaAnnotationTypes(annotationName); } @Override public boolean hasAnnotation(String annotationName) { List 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 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 annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { boolean hasAnnotatedMethods = metadata.hasAnnotatedMethods(name); if (hasAnnotatedMethods) { return true; } } return metadata.hasAnnotatedMethods(annotationName); } @Override public Set getAnnotatedMethods(String annotationName) { List annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { Set 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 annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { boolean isAnnotated = metadata.isAnnotated(name); if (isAnnotated) { return true; } } return metadata.isAnnotated(annotationName); } @Override public Map getAnnotationAttributes(String annotationName) { List annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { Map annotationAttributes = metadata.getAnnotationAttributes(name); if (Objects.nonNull(annotationAttributes) && !annotationAttributes.isEmpty()) { return annotationAttributes; } } return metadata.getAnnotationAttributes(annotationName); } @Override public Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { List annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { Map annotationAttributes = metadata.getAnnotationAttributes(name, classValuesAsString); if (Objects.nonNull(annotationAttributes) && !annotationAttributes.isEmpty()) { return annotationAttributes; } } return metadata.getAnnotationAttributes(annotationName, classValuesAsString); } @Override public MultiValueMap getAllAnnotationAttributes(String annotationName) { List annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { MultiValueMap allAnnotationAttributes = metadata.getAllAnnotationAttributes(name); if (Objects.nonNull(allAnnotationAttributes) && !allAnnotationAttributes.isEmpty()) { return allAnnotationAttributes; } } return metadata.getAllAnnotationAttributes(annotationName); } @Override public MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { List annotationNameList = wrapAnnotationName(annotationName); for (String name : annotationNameList) { MultiValueMap 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注册过程,能加快我们理解本节内容!

你可能感兴趣的:(Spring Cloud Feign 分析(三)之自定义注解实现版本兼容)