> 本文作者:陈刚,叩丁狼高级讲师。原创文章,转载请注明出处。
#### 回顾
我们还是在之前Feign的使用案例集成上来分析Feign源码,在分析 之前我们先简单来回顾一下Feign的用法,要用Feign首选是需要在配置类似开启Feign客户端支持
```
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
```
然后我们需要去定义Feign客户端接口
```
//PRODUCER:指向要访问的服务
//configuration = FooConfiguration.class Feign Client配置类,指定负载均衡策略
//fallback = MyFeignClientImpl.class: MyFeignClient结构的实现类,需要复写 provide方法,并实现错误处理逻辑
//当访问出现故障,程序会自动调用实现类的 provide方法的错误处理逻辑。
@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class)
public interface MyFeignClient {
//当此方法别调用会自动请求到 PRODUCER服务的 /provide 资源
@RequestMapping(value = "/provide")
public String provide(@RequestParam("name") String name);
}
```
在使用的时候就只需要调用 MyFeignClient 接口即可,SpringCloud会自动去调用对应的目标服务。
分析Feign我们还是从他的@EnableFeignClients标签开始,我们先看下他的源码
```
/**
* Scans for interfaces that declare they are feign clients (via {@link FeignClient
* @FeignClient}). Configures component scanning directives for use with
* {@link org.springframework.context.annotation.Configuration
* @Configuration} classes.
*
* @author Spencer Gibb
* @author Dave Syer
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
```
该标签的注释告诉我们,它开启了对打了 @FeignClient 标签所在的接口的扫描,和对期配置组件的扫描支持 ,并且该标签import了一个FeignClientsRegistrar类,我们跟踪进去看一下
```
/**
* @author Spencer Gibb
* @author Jakub Narloch
* @author Venil Noronha
* @author Gang Li
*/
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {
```
他实现了 ImportBeanDefinitionRegistrar接口,我们继续看ImportBeanDefinitionRegistrar的源码
```
/**
* Interface to be implemented by types that register additional bean definitions when
* processing @{@link Configuration} classes. Useful when operating at the bean definition
* level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
*
*
Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
* may be provided to the @{@link Import} annotation (or may also be returned from an
* {@code ImportSelector}).
*
*
An {@link ImportBeanDefinitionRegistrar} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #registerBeanDefinitions}:
*
See implementations and associated unit tests for usage examples.
*
* @author Chris Beams
* @since 3.1
* @see Import
* @see ImportSelector
* @see Configuration
*/
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
*
Note that {@link BeanDefinitionRegistryPostProcessor} types may not be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
```
翻译“Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes.”得知,该接口提供了注册bean的功能支持,而registerBeanDefinitions则是bean注册的方法。 AnnotationMetadata是bean的元注解对象 ,BeanDefinitionRegistry是当前注册的bean的注册表,
我们继续跟踪一下 FeignClientsRegistrar#registerBeanDefinitions 方法
```
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
```
registerDefaultConfiguration(metadata, registry);注册默认配置,我们跟踪一下源码
```
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
//name以 "default."+类名
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
```
这里获取了主程序配置类的@EnableFeignClients注解中的feign默认配置 ,然后用 “default.” 开头加上类名作为名字来注册 ,继续跟踪
```
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
```
这里把 configuration 配置交给了 BeanDefinitionBuilder然后去创建了一个 FeignClientSpecification 对象出来 ,然后调用BeanDefinitionRegistry的registerBeanDefinition方法把 包装了配置类的FeignClientSpecification对象进行注册,注册到哪儿去了呢?最终会调用BeanDefinitionRegistryd的子类DefaultListableBeanFactory#registerBeanDefinition方法注册
```
...省略代码...
/** Map of bean definition objects, keyed by bean name */
private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...省略代码...
this.beanDefinitionMap.put(beanName, beanDefinition);
```
这里最终会put到一个线程安全的map:beanDefinitionMap = new ConcurrentHashMap<>(256); 中,这个东西就是ico容器o(* ̄︶ ̄*)o 。
回过头来我们继续看 FeignClientsRegistrar#registerBeanDefinitions 方法
```
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册默认配置到ioc容器中
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
```
到这里我们已经知道 registerDefaultConfiguration(metadata, registry);注册默认配置,那么 registerFeignClients(metadata, registry);方法看名字应该是注册所有的feignClient的bean,我们一步一步进去看
```
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//得到组件扫描器,resourceLoader是资源加载器,
//比如加载spring配置文件就会用到ResourceLoader
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
//获取元注解上EnableFeignClients的属性配置
Map attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class>[] clients = attrs == null ? null
: (Class>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
//获取元注解所在的主程序配置类的包名即项目所在的包
basePackages = getBasePackages(metadata);
}
else {
final Set clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class> clazz : clients) {
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);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
//扫描候选的组件,会把basePackage报下的bean都扫描到,然后过滤出打了 @FeignClient 标签的接口,封装成 BeanDefinition bean描述对象
Set candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
//获取FeignClient接口上的的注解(我定义的FeignClient接口是MyFeignClient)
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
//如果贴上 @FeignClient标签的不是接口,抛出异常
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取到@FeignClient 注解上我们配置的所有属性
Map attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
//获取@FeignClient标签的服务名我这指定是“PRODUCER”
//(@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class))
String name = getClientName(attributes);
//注册配置对象:即会获取到configuration = FooConfiguration.class然后实现注册(将bean封装成beanDefinition对象放到ioc容器中),
//而FooConfiguration中我们自己实现了负载均衡规则
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册 registerFeign
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
```
这里面做的事情挺多的
1.通过组件扫描器ClassPathScanningCandidateComponentProvider
2.获取元注解所在配置类的包名,
3.然后通过ClassPathScanningCandidateComponentProvider扫描打了@FeignClient标签的接口 ,然后封装成 Set
4.获取 FeignClient 接口上的 @FeignClient标签的配置类,完成注册
5.完成 FeignClient接口本身的注册
我们继续跟踪 registerFeignClient 方法
```
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map attributes) {
String className = annotationMetadata.getClassName();
//这里使用了FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//这里把FeignClient的所有配置属性都设置给了 FeignClientFactoryBean
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
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);
//取别名 FeignClient结尾
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//注册 FeignClientFactoryBean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
```
这里使用 FeignClientFactoryBean来封装 FeignClient的描述信息 ,并且注册到ioc容器中 ,使用FeignClientFactoryBean 的目的是为了在spring容器刷新过程中会使用代理工厂来创建出代理类(你是否想过,我们的MyFeignClient始终是个接口,那么他是如何实现远程服务的调用的?肯定需要创建出具体的实例去处理),我们跟踪一下 FeignClientFactoryBean
```
class FeignClientFactoryBean implements FactoryBean