Feign服务调用的工作原理
开启Spring Cloud Feign功能是通过@EnableFeignClients注解实现的。程序启动时,首先会检测是否有@EnableFeignClients注解,如果有,则会开启扫描功能,并扫描被@FeignClient注解修饰的接口。接下来,我们从@EnableFeignClients注解入手,分析Feign远程调用服务的工作原理。
查看@EnableFeignClients注解的源码,具体代码如下所示。
@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 {};
}
从上述源码可以看出,@EnableFeignClients注解引入了FeignClientsRegistrar类,继续跟踪该类源码,发现其内部定义了一个registerBeanDefinitions方法,具体代码如下所示。
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
上述源码中,registerBeanDefinitions方法内部调用了两个方法,分别是registerDefaultConfiguration()和registerFeignClients(),其中registerDefaultConfiguration()用于加载相关配置,registerFeignClients()用于扫描所有@FeignClient注解的接口。这里,我们重点查看registerFeignClients()方法内部实现细节,registerFeignClients()的源码如下所示。
1 public void registerFeignClients(AnnotationMetadata metadata,
2 BeanDefinitionRegistry registry) {
3 // 创建类路径的扫描组件
4 ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
5 scanner.setResourceLoader(this.resourceLoader);
6 // 获取EnableFeignClients注解的信息
7 Map<String, Object> attrs =
8 metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
9 // 创建FeignClient注解的过滤器
10 AnnotationTypeFilter annotationTypeFilter = new
11 AnnotationTypeFilter(FeignClient.class);
12 Class<?>[] clients = attrs == null ? null :
13 (Class[])((Class[])attrs.get("clients"));
14 Object basePackages;
15 if (clients != null && clients.length != 0) {
16 final Set<String> clientClasses = new HashSet();
17 basePackages = new HashSet();
18 Class[] var9 = clients;
19 int var10 = clients.length;
20 for(int var11 = 0; var11 < var10; ++var11) {
21 Class<?> clazz = var9[var11];
22 ((Set)basePackages).add(ClassUtils.getPackageName(clazz));
23 clientClasses.add(clazz.getCanonicalName());
24 }
25 AbstractClassTestingTypeFilter filter =
26 new AbstractClassTestingTypeFilter() {
27 protected boolean match(ClassMetadata metadata) {
28 String cleaned = metadata.getClassName().replaceAll("\\$", ".");
29 return clientClasses.contains(cleaned);
30 }
31 };
32 scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(
33 Arrays.asList(filter, annotationTypeFilter)));
34 } else {
35 scanner.addIncludeFilter(annotationTypeFilter);
36 basePackages = this.getBasePackages(metadata);
37 }
38 Iterator var17 = ((Set)basePackages).iterator();
39 while(var17.hasNext()) {
40 String basePackage = (String)var17.next();
41 Set<BeanDefinition> candidateComponents =
42 scanner.findCandidateComponents(basePackage);
43 Iterator var21 = candidateComponents.iterator();
44 while(var21.hasNext()) {
45 BeanDefinition candidateComponent =
46 (BeanDefinition)var21.next();
47 if (candidateComponent instanceof AnnotatedBeanDefinition) {
48 AnnotatedBeanDefinition beanDefinition =
49 (AnnotatedBeanDefinition)candidateComponent;
50 AnnotationMetadata annotationMetadata =
51 beanDefinition.getMetadata();
52 Assert.isTrue(annotationMetadata.isInterface(),
53 "@FeignClient can only be specified on an interface");
54 Map<String, Object> attributes = annotationMetadata.
55 getAnnotationAttributes(FeignClient.class.getCanonicalName());
56 String name = this.getClientName(attributes);
57 this.registerClientConfiguration(registry, name,
58 attributes.get("configuration"));
59 this.registerFeignClient(registry,
60 annotationMetadata, attributes);
61 }
62 }
63 }
64 }
上述代码中,首先定义了一个基于classpath的组件扫描器,然后组件扫描器会根据指定的扫描位置和@EnableFeignClients注解属性找到开发人员定义的所有Feign客户端,也就是所有添加了@FeignClient注解的所有接口,最后将注册Feign客户端的动作交给registerFeignClient()方法完成。registerFeignClient()方法的源码如下所示。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 获取使用@FeignClient注解修饰的类名
String className = annotationMetadata.getClassName();
//创建一个BeanDefinitionBuilder,指定待创建的Bean是FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.
genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
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"));
definition.setAutowireMode(2);
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});
// 动态注册
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
上述代码中,首先会将使用@FeignClient注解修饰的类名及其注解信息获取出来,赋值给BeanDefinitionBuilder,然后根据BeanDefinitionBuilder得到BeanDefinition,最后将BeanDefinition注入IoC容器。
当调用被@FeignClient修饰接口中的方法时,该方法会被SynchronousMethodHandler拦截处理,并生成一个RequestTemplate对象。SynchronousMethodHandler类中生成RequestTemplate对象的源码具体如下。
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template= buildTemplateFromArgs.create(argv) ;
Retryer retryer = this.retryer.clone();
while(true) {
try {
return executeAndDecode(template) ;
} catch(RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel!=Logger.Level.NONE) {
logger.logRetry(metadata.configKey(),logLevel);
}
continue;
}
}
}
上述代码中,executeAndDecode()方法会通过RequestTemplate 生成 Request对象。Request对象将交给Client处理,具体源码如下。
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request= targetRequest(template) ;
...//省略代码
response = client.execute(request,options);
...//省略代码
综上所述,Feign服务调用的工作原理可以总结为以下几个步骤:
(1)首先通过@EnableFeignClients注解开启FeignClient功能。程序启动时,会通过该注解开启对@FeignClient注解的包扫描。
(2)根据Feign规则实现接口,并在接口上面添加@FeignClient注解。
(3)程序启动后,会进行包扫描,扫描所有的@FeignClient注解类,并将这些信息注入IoC容器。
(4)当接口方法被调用时,通过JDK的代理生成具体的RequestTemplate模板对象。根据RequestTemplate再生成HTTP请求的Request对象,Request对象交给Client处理。