简介:
Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。
源码分析:
当我们需要在项目中使用Feign的功能,需要在我们的pom.xml文件中引入Maven的依赖,依赖的代码如下:
<!--引入openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>Spring-cloud-starter-openfeign</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
当我们引入这个依赖以后会间接依赖如下的jar包:
要使用Feign的功能,需要在我们的启动类上添加@EnableFeignClients注解,这个注解里面的内容是什么那?
package org.springframework.cloud.openfeign;
import java.lang.Annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//basePackages 属性的别名,允许使用更简洁的注释声明
String[] value() default {};
//扫描包下带注释的组件
String[] basePackages() default {};
//basePackages() 的类型安全的替代方法,用于指定要扫描带注释的组件的软件包,指定类别的包装将被扫描。
Class<?>[] basePackageClasses() default {};
//适用于所有自定义@Configuration,可以包含组成客户端的部分的@Bean
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
可以看出这个注解类是
spring-cloud-openfeign-core包下的,正是我们前面间接分析的依赖的jar包,这个注解类里面@Import一个FeignClientsRegistrar类,它实现了ImportBeanDefinitionRegistrar接口,SpringBoot在启动时候会调用registerBeanDefinitions方法,该方法的代码如下:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
首先调用
registerDefaultConfiguration方法,该方法主要判断注解EnableFeignClients是否有defaultConfiguration属性,往容器注入一个类型是FeignClientSpecification,它的构造函数名称分别是String类型的名称、defaultConfiguration类型是Class类型的数组,这个方法主要也是我们自定义的一些加载Bean,如果没有就会使用默认的Bean配置。
在我这个例子中没有自定义defaultConfiguration属性,所以会跳过这个方法的执行,我们重点分析registerFeignClients方法,它是Feign的核心方法,
方法负责读取@EnableFeignClients的属性,获取需要扫描的包名,然后扫描指定的所有包名下的被@FeignClient注解注释的接口,将扫描出来的接口调用registerFeignClient方法注册到spring容器,我们先看registerFeignClients方法的内容:
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> 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<String> 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) {
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();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
首先创建了一个
ClassPathScanningCandidateComponentProvider对象,它主要用于在类路径上扫描指定包(或包含特定注解)的类,并将它们注册为 Spring 容器中的 Bean 候选者。获取启动类下的EnableFeignClients注解信息,创建一个AnnotationTypeFilter的用于匹配FeignClient注解的类,这个FeignClient它的代码如下:
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
//指定要调用的目标服务的名称。该属性通常用于指定服务的名称。
String value() default "";
String serviceId() default "";
//在使用多个 @FeignClient 注解时,用于标识不同的 Feign 客户端,防止冲突。
String contextId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default "";
//指定目标服务的 URL 地址。当不使用服务注册中心时,可以使用该属性直接指定服务的地址。
String url() default "";
//默认情况下,Feign 不会解码 HTTP 404 错误。设置为 true 后,Feign 将解码 404 错误。
boolean decode404() default false;
//指定 Feign 客户端的配置类,该配置类可以包含一些自定义的配置,如超时时间、日志级别等。
Class<?>[] configuration() default {};
//定 Feign 客户端的熔断器(Hystrix)回退类。当调用远程服务失败时,会执行回退类中的逻辑。
Class<?> fallback() default void.class;
//与 fallback 类似,不同之处在于 fallbackFactory 允许提供一个工厂类,用于创建回退类的实例。
Class<?> fallbackFactory() default void.class;
//指定 Feign 客户端的统一前缀路径,该路径会添加到每个请求的 URL 前面。
String path() default "";
boolean primary() default true;
}
把它添加到
ClassPathScanningCandidateComponentProvider对象的成员属性includeFilters上,获取当前要扫描FeignClient注解的包名,如果指定了包名则用指定的,如果没有指定就用启动类所在包路径下,循环变量这个包下的所有子包看是否 又@FeignClient的注解,如果有获取标有该注解的所有属性封装为Map。
根据Map获取@Feign注解的value值,又调用了
registerClientConfiguration方法判断是否有自己定义的Bean,接着调用registerFeignClient方法,这是一个重载方法,该方法的定义如下:
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
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);
String alias = contextId + "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 });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
该方法的逻辑是获取拥有@FeignClient注解的类名,创建一个Bean定义的BeanDefinitionBuilder对象,该对象主要用于创建FeignClientFactoryBean的Bean定义对象,这个类是关键的类,后面会分析它,从Map数据结构中获取url、path、name、contextId、type、decode404、fallback、fallbackFactory属性设置到BeanDefinitionBuilder对象的值上,以后这些属性都会是FeignClientFactoryBean的成员属性。
最后调用BeanDefinitionBuilder对象的getBeanDefinition方法获取FeignClientFactoryBean的Bean定义对象注入到容器中。
到此为止被@Feign修饰的类已经加载到了Spring容器中,此时它的Bean类型已经变为FeignClientFactoryBean类型,但是此时的Bean尚未进行实例化,下一篇文章我主要分析它的实例化过程。
总结:
本节主要分析了Feign是怎么被加载到Spring容器中的,其实主要就是在我们的启动类中添加@EnableFeignClients注解,然后注解又引入了一个FeignClientsRegistrar,它是Spring容器会回调的一个类,它是去指定的包路径下寻找所有子包标有@FeginClient注解的类并把他们封装为FeignClientFactoryBean类型的Bean加载到spring容器中。好了,我是程序员老徐,如果喜欢我的文章,请点赞关注。