专栏系列文章:SpringCloud系列专栏
系列文章:
SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化
SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约
SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表
SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制
SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群
SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇
SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate
SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置
SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件
SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇
SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇
Feign
Feign 是一个伪 Java HTTP 客户端,Feign 不做任何的请求处理,它只是简化API调用的开发,开发人员只需定义客户端接口,按照 springmvc 的风格开发声明式接口。然后在使用过程中我们只需要依赖注入Bean,然后调用对应的方法传递参数即可。
这里就有个问题,我们开发的是一个接口,然后使用 @FeignClient 注解标注,那又是如何能够注入这个接口的Bean对象的呢?其实很容易就能想到,一定是生成了接口的动态代理并注入到Spring容器中了,才能依赖注入这个客户端接口。
FeignClient 动态注册组件
FeignClientsRegistrar
再看下 @EnableFeignClients 注解,它使用 @Import 导入了 FeignClientsRegistrar,FeignClient 注册者。从名字就可以看出,FeignClientsRegistrar 就是完成 FeignClient 接口注册的核心组件。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// FeignClient 注册处理类
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//...
}
FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware 三个接口。
ResourceLoaderAware是为了注入资源加载器ResourceLoaderEnvironmentAware是为了注入当前环境组件EnvironmentImportBeanDefinitionRegistrar是 Spring 动态注册 bean 的接口,就是利用这个接口来动态注册 FeignClient 动态代理对象的
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
// 资源加载器
private ResourceLoader resourceLoader;
// 当前环境组件
private Environment environment;
//....
}
ImportBeanDefinitionRegistrar
1、ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar 主要包含一个接口方法 registerBeanDefinitions,就是用来动态注册 BeanDefinition 的。平时我们一般就使用 @Service、@Component、@Bean 等注解向 Spring 容器注册对象,我们也可以实现 ImportBeanDefinitionRegistrar 接口来动态注册 BeanDefinition。
所有实现了 ImportBeanDefinitionRegistrar 接口的类的都会被 ConfigurationClassPostProcessor 处理,ConfigurationClassPostProcessor 实现了 BeanFactoryPostProcessor 接口,所以 ImportBeanDefinitionRegistrar 中动态注册的bean是优先于依赖它的bean初始化的,也能被aop、validator 等机制处理。ImportBeanDefinitionRegistrar 实现类写好之后,还要使用 @Import 注解导入实现类。
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation is empty.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
2、BeanDefinition
BeanDefinition 又是什么呢?从注释可以了解到,BeanDefinition 就是用来描述 bean 实例的,BeanDefinition 包含了实例的属性值、构造函数参数等。其实就是通过这个 BeanDefinition 来获取实例对象。
/**
* A BeanDefinition describes a bean instance, which has property values,
* constructor argument values, and further information supplied by
* concrete implementations.
*
* <p>This is just a minimal interface: The main intention is to allow a
* {@link BeanFactoryPostProcessor} to introspect and modify property values
* and other bean metadata.
*/
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
}
3、注册 BeanDefinition
FeignClientsRegistrar 实现的 registerBeanDefinitions 方法中,主要有两步:
- 注册FeignClient默认配置对象,就是根据 @EnableFeignClients 的
defaultConfiguration配置类注入默认配置,这个一般就是全局配置。 - 之后就是扫描 @FeignClient 注解的接口,封装成
BeanDefinition,然后用 BeanDefinitionRegistry 来注册。
因此,FeignClientsRegistrar 就是扫描 @FeignClient 注解的接口,并注册 FeignClient 的核心组件。
// 根据注解元数据注册bean定义
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册 FeignClient 默认配置类,根据 @EnableFeignClients 的 defaultConfiguration 注入默认配置
registerDefaultConfiguration(metadata, registry);
// 扫描 FeignClient 接口,注册 FeignClient
registerFeignClients(metadata, registry);
}
扫描 @FeignClient 注解接口
接着看 registerFeignClients 方法,这个方法主要就是完成扫描 @FeignClient 注解的接口并完成 FeignClient 注册的工作。
主要的流程如下:
- 首先得到一个类路径扫描器
ClassPathScanningCandidateComponentProvider,就是用这个组件来扫描包路径获取到@FeignClient注解的接口。 - 如果 @EnableFeignClients 没有配置
clients属性,扫描的包路径就是 @EnableFeignClients 配置的value、basePackages、basePackageClasses配置的包路径。并且根据注解过滤器来筛选有@FeignClient注解的接口。 - 如果 @EnableFeignClients 配置了
clients属性,就只扫描clients配置的接口类。 - 之后就遍历扫描包路径,获取到
@FeignClient注解的接口。可以看到@FeignClient 注解的类型必须是一个接口,否则断言会抛出异常。 - 最后两步就是注册配置类和注册 FeignClient了,配置类就是 @FeignClient 的
configuration属性配置的客户端配置类,这个配置类将覆盖 @EnableFeignClients 配置的全局配置类。
**
* 注册 FeignClient
*
* @param metadata @EnableFeignClients 注解的元数据
* @param registry BeanDefinition 注册器
*/
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// ClassPath 扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// @EnableFeignClients 注解的属性
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 注解类型过滤器,过滤 @FeignClient 注解的接口
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// 如果 @EnableFeignClients 没有配置 clients,就取 value、basePackages、basePackageClasses 基础包
if (clients == null || clients.length == 0) {
// @FeignClient 注解过滤器
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
// 如果 @EnableFeignClients 中配置了 clients
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
// 基础包取配置的 client 类所在的包
basePackages.add(ClassUtils.getPackageName(clazz));
// 根据名称过滤
clientClasses.add(clazz.getCanonicalName());
}
// 类过滤器
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
// 根据名称过滤
return clientClasses.contains(cleaned);
}
};
// 必须类名在 clientClasses 中且类上有 @FeignClient 注解
scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
// 扫描基础包
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// @FeignClient 注解的类型必须是一个接口
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// @FeignClient 注解的属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// Feign 客户端名称,就是服务名
String name = getClientName(attributes);
// 注解客户端配置类
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册 FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
看下 getBasePackages 方法,可以看出,要扫描的包路径包含 @EnableFeignClients 配置的 value、basePackages、basePackageClasses 类所在的包,这里是取的多个配置的并集。
还有个需要注意的是,从最后一步可以看出,如果配置了 value、basePackages、basePackageClasses 时,就不会扫描 @EnableFeignClients 所在的包路径了,如果要扫描,需配置到 value 等属性中。
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
// 先取 value
for (String pkg : (String[]) attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
// 再取 basePackages
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
// 再从 basePackageClasses 的 Class 获取包
for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 只有当没有配置 value、basePackages、basePackageClasses 时,才会扫描 @EnableFeignClients 所在的包路径
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
Feign客户端BeanDefinition构造并注册
registerFeignClients 中扫描了包路径下的 @FeignCient 注解的接口,然后调用了 registerFeignClient 注册 FeignClient 接口的 BeanDefinition。
主要的流程如下:
- 首先创建了
BeanDefinitionBuilder,要构建的类型是 FeignClientFactoryBean,从名字可以看出就是创建 FeignClient 代理对象的工厂类。FeignClientFactoryBean 就是生成 FeignClient 接口动态代理的核心组件。 - 接着就是将 @FeignClient 注解的属性设置到 definition 中,它这里还设置了回调类
fallback和回调工厂fallbackFactory,但是有没有用呢?这个后面再分析。 - 然后是 bean 的名称,默认为
服务名称 + "FeignClient",例如 "demo-consumerFeignClient";如果设置了qualifier属性,名称就是 qualifier 设置的值。 - 之后用 BeanDefinitionBuilder 获取 BeanDefinition,并设置了对象类型为 FeignClient 接口的全限定名。
- 最后,将 BeanDefinition 等信息封装到
BeanDefinitionHolder,然后调用BeanDefinitionReaderUtils.registerBeanDefinition将 BeanDefinition 注册到Spring IoC 容器中。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// FeignClientFactoryBean 就是用来生成 FeignClient 接口代理类的核心组件
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
// 从 @FeignClient 中得到的属性,并设置到 BeanDefinitionBuilder
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = 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"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// bean 的别名,demo-consumerFeignClient
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// bean 的类型,就是 FeignClient 接口
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
// 自定义的别名标识
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
// 将信息都封装到 BeanDefinitionHolder
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
// 注册bean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
一张图总结 @FeignClient 接口扫描流程
下面用一张图来总结下 @FeignClient 接口是如何被扫描并注册到容器中的。
- 首先我们在代码中开发了 FeignClient 客户端调用接口,并用 @FeignClient 注解,注意
@FeignClient 只能加到接口上面。 - 之后我们需要在启动类或配置类中加一个
@EnableFeignClients注解来启用 FeignClien。@EnableFeignClients 其实就是导入了 FeignClient 注册器FeignClientsRegistrar。 - FeignClientsRegistrar 实现了
ImportBeanDefinitionRegistrar接口,在 registerBeanDefinitions 实现中,主要有两步:- 注册全局配置配置类,就是 @EnableFeignClients 中指定的
defaultConfiguration - 接着就是扫描注册 FeignClient
- 注册全局配置配置类,就是 @EnableFeignClients 中指定的
- 注册客户端时,先用
ClassPathScanningCandidateComponentProvider扫描器扫描出配置的包下的@FeignClient注解的接口 - 扫描到 @FeignClient 接口后,先注册客户端特定的配置,就是 @FeignClient 配置的
configuration。 - 接着注册客户端:
- 先构建一个
BeanDefinitionBuilder,要创建的 BeanDefinition 类型是FeignClientFactoryBean。 - 然后就是将 @FeignClient 中的配置设置到 BeanDefinitionBuilder,其实就是设置给 FeignClientFactoryBean。
- 之后解析出 FeignClient 的别名,默认是
服务名+“FeignClient”。 - 再用 BeanDefinitionBuilder 构建出
BeanDefinition,并将相关信息封装到BeanDefinitionHolder中。 - 最后使用
BeanDefinitionReaderUtils完成 BeanDefinition 的注册。 - 将 BeanDefinition 注入容器后,就会调用 FeignClientFactoryBean 的
getObject方法来创建动态代理。
- 先构建一个