Spring Cloud Feign 分析(三)之自定义注解实现
前面我们讲解到@FeignClient在SpringBoot1.x与SpringBoot2.x版本之间不兼容,无法复用的问题,并且使用了路径覆盖大法重写@FeignClient这个注解类,使用这种方式基本零修改,本节我们则使用另外一种方式(继承FeignClientsRegistrar)实现,这种方式可以了解@FeignClient注解注册到Spring IOC的整个过程,更有助于我们后续分析Feign原理!
自定义注解实现版本兼容思路
- 自定义@EnableFeignClients注解(笔者尝试过使用路径覆盖大法,发现无效)
- 继承FeignClientsRegistrar,扫描org.springframework.cloud.openfeign.FeignClient注解与org.springframework.cloud.netflix.feign.FeignClient注解
- 注册@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注解对应的属性,所以这里我们就做一个映射关系,映射关系如下:
- {@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}
我们按照映射优先级获取映射之后注解的属性返回,默认优先查找openfeign.FeignClient的属性,当查询不到再查询netflix.feign.FeignClient注解的属性。
至此,到了文章末尾,也大概总结下吧,我们使用自定义注解这种方式呢,我们可以更加灵活的管理和实现,也有助于我们阅读后续的Feign章节。当然这样的方式可能对不熟悉源码的同学不太友好,如果对FeignClientsRegistrar相关逻辑不清楚的可以参阅Spring Cloud Feign 分析(一)之FeignClient注册过程,能加快我们理解本节内容!