在上篇博文《OpenFeign学习(七):Spring Cloud OpenFeign的使用》中,我介绍了Spring Cloud OpenFeign的简单用法。在本篇博文中,我将继续对Spring Cloud OpenFeign进行学习,通过源码介绍Spring Cloud是如何对OpenFeign进行集成支持,如何进行加载配置。
在阅读源码前,我们先通过文档简单了解下Spring对OpenFeign进行了那些扩展支持:
Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
可以看到,Spring Cloud对OpenFeign添加了Spring MVC 注解,与Spring Web相同的HttpMessageConverters,以及集成了Ribbon, Eureka 和 Spring Cloud LoadBalancer。
接下来,我们通过阅读源码来了解Spring Cloud OpenFeignhi如何进行加载配置的:
在之前的使用介绍中,我们主要用了以下两个注解:
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class Feign2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(Feign2ClientApplication.class, args);
}
}
@FeignClient(value = "feign2")
public interface Feign2Service {
}
在@EnableFeignClients注解中通过@Import注解将FeignClientsRegistrar类导入容器,并且通过该注解可以配置FeignClient的默认配置,扫描基础包等。
@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注解来配置对应服务客户端,该注解只能作用于接口类上,也可以通过该注解对客户端进行个性化配置,客户端名称,配置类,调用url等。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";
/** @deprecated */
@Deprecated
String serviceId() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default "";
String url() default "";
boolean decode404() default false;
Class>[] configuration() default {};
Class> fallback() default void.class;
Class> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
}
通过两个注解,我们就可以在spring cloud中使用默认配置的OpenFeign了,十分方便,不需要再通过Builder手动创建。
在配置加载中,除了FeignClientsRegistrar来进行注册配置外,Spring Boot 还通过AutoConfigurationImportSelector类来扫描配置在 META-INF/spring.factories文件中的自动装配类。在Spring Cloud OpenFeign中,配置了以下类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
可以看到有FeignRibbonClientAutoConfiguration, FeignAutoConfiguration等。
关于Spring Boot的自动装配原理,可以看以下博文:
https://mp.weixin.qq.com/s/RsIdbv22eh9GMEE9hvV5Wg
https://mp.weixin.qq.com/s/p3p8n2WMuukvy7eYFNzwTQ
该类实现了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware三个接口。分别实现了registerBeanDefinitions,setResourceLoader,setResourceLoader方法。
该类的调用是在ApplicationContext在refresh方法中,通过ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法进行调用。
private void loadBeanDefinitionsFromRegistrars(Map registrars) {
registrars.forEach((registrar, metadata) -> {
registrar.registerBeanDefinitions(metadata, this.registry);
});
}
可以看到直接的方法调用的就是ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法。
通过该方法进行默认配置和FeignClient的注册。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
方法有两个参数metadata,registry。metadata为项目的启动类,这里为Feign2ClientApplication。registry为BeanDefinitionRegistry的实现类DefaultListableBeanFactory。
通过方法名称可知,首先进行默认配置的注册,再进行FeginClient的注册。
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取启动类上@EnableFeignClients注解的属性信息
Map defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
// 设置部分beanName 如果是内部类 则设置top level类的名称
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
// 在注解属性中得到默认配置类进行注册
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
通过代码可以看到,先获取了启动类上的@EnableFeignClients的注解配置的属性信息,默认情况下,属性值全为空。接下来根据启动类属性设置beanName相关信息,可以看到默认的配置,bean名称以default作为前缀。最后调用registerClientConfiguration方法进行注册。
registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
// 通过BeanDefinitionBuilder创建FeignClientSpecification类的BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
// 设置构造参数
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// bean注册
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
在该方法中,首先创建了FeignClientSpecification类的BeanDefinition构造器,该类是Spring Cloud 提供的FeignClient对应配置基础类。该类有两个参数,分别为client对应name和对应配置类的数组configuration。
class FeignClientSpecification implements Specification {
private String name;
private Class>[] configuration;
}
接下来,通过addConstructorArgValue方法设置该类的构造参数值,name为之前设置的 default.启动类全类名, configuration为@EnableFeignClients注解的配置属性值。
最后。进行默认配置bean的注册,beanName为 default.启动类全类名.FeignClientSpecification。
至此,通过@EnableFeignClients注解为所有FeignClient配置的Configuration类注册完毕。
用该方法注册使用@FeignClient注解配置的Client。
该方法有些长,我们分开进行阅读了解。
首先获取了生成bean的扫描对象scanner,该对象为ClassPathScanningCandidateComponentProvider的实例对象。(这里有个知识点,在如何判断为待注册bean中,使用了beanDefinition.getMetadata().isIndependent()方法,关于isIndependent()方法的解释,详见:https://www.jianshu.com/p/107c05b29290
)。
接下来,获取@EnableFeignClients注解中配置的clients,若有配置值,则通过Set basePackages进行收集类所在包。否则通过注解属性value,basePackages,basePackageClasses的值获取要扫描的包路径。同时设置注解类型过滤器AnnotationTypeFilter,过滤具有FeignClient注解的类。
在获取扫描路经时如果没有配置属性值,则获取启动类的package根路径。这也是为什么要将@EnableFeignClients注解放到启动类的原因。
// 生成bean扫描类
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 生成注解类过滤器
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
// 获取@EnableFeignClients配置的clients类
Class>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
Object basePackages;
// 若clients值存在 进行收集包路径
if (clients != null && clients.length != 0) {
final Set clientClasses = new HashSet();
basePackages = new HashSet();
Class[] var9 = clients;
int var10 = clients.length;
for(int var11 = 0; var11 < var10; ++var11) {
Class> clazz = var9[var11];
((Set)basePackages).add(ClassUtils.getPackageName(clazz));
// 收集配置的client类 作为过滤的依据
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
protected boolean match(ClassMetadata metadata) {
// 将内部类的类名转换为java语言规范定义的格式
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
// 根据配置进行过滤
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
} else {
// 在该方法中 根据@EnableFeignClients配置的value,basePackages,basePackageClasses属性值设置基础扫描路径
// 若无配置值,则将启动类的包根路径设置为扫描路径
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = this.getBasePackages(metadata);
}
在获取到扫描基础包和配置的client类后,进行扫描获取FeignClient的BeanDefinition进行注册。
在注册时,对@FeignClient注解是否作用在接口类进行校验,再获取配置的属性值,分别注册对应的配置类和client。
Iterator var17 = ((Set)basePackages).iterator();
while(var17.hasNext()) {
String basePackage = (String)var17.next();
// 扫描获取FeignClient的BeanDefinition
Set candidateComponents = scanner.findCandidateComponents(basePackage);
Iterator var21 = candidateComponents.iterator();
while(var21.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var21.next();
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 对注解作用类进行校验
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = this.getClientName(attributes);
// 注册配置的configuration
// beanName为 clientName.FeignClientSpecification
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册client 实际为注册对应FeignClientFactoryBean
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
registerFeignClient
通过该方法注册FeignClient对应的FeignClientFactoryBean。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) {
// 获取注解作用类的类名作为beanName
String className = annotationMetadata.getClassName();
// 生成FeignClientFactoryBean的BeanDefinition构造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class)
// 对配置的hyxtrix降级配置进行校验
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
// 获取配置的contextId 若没有配置 则使用clientName
String contextId = this.getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
// 设置自动注入类型为AUTOWIRE_BY_TYPE
definition.setAutowireMode(2);
// 设置bean别名
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
// 注册FeignClientFactoryBean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
至此,通过FeignClientsRegistrar进行FeignClient的加载注册源码已经阅读完毕,可以看到Spring Cloud OpenFeign通过@EnableFeignClients和@FeignClient两个注解实现FeignClient的配置与加载,在注册client时,实际注册的是接口类对应的FeignClientFactoryBean。
上面提到Spring Cloud在spring.factories文件中配置了自动装配类,接下来我将继续通过源码介绍这些类是如何进行配置OpenFeign的。