Feign在Spring Cloud中主要用于封装Http请求细节,让微服务之间的调用同服务内调用一样便捷。
Open Feign的源码实现过程主要可以概括为以下几点
- 通过@EnableFeignClients引入FeignClientsRegistrar。
- FeignClientsRegistrar实现了ImportBeanDefinition接口,扫描对应路径下被@EnableFeign注解修饰的接口(必须是接口),生成对应的FeignClientSpecifition。
- FeignAutoConfiguration注入所有的FeignClientSpecification的实例,注入到FeignContext中。
- 当接口调用时,通过CGLIB动态代理的形式,与已有的RestTemplate整合请求参数,生成最后的RestTemplate。
- RestTemplate被转换为Request,使用了Ribbon还会被转换为RibbonRequest。
- 通过Ribbon进行负载均衡,将url中的应用名转化为ip。
- 调用Request内的Client执行真正的Http请求,client可以是ApacheHttpClient等实例。
- 返回结果response,使用了Ribbon还会转化为RibbonResponse。
- 根据返回结果的状态码使用Decoder或者ErrorDecoder进行解析。Decoder等组件都维护在FeignContext上下文中。
由此得到Feign的启用和请求调用的大体步骤,这些步骤又可以分为三个阶段:BeanDefinition的注册、实例的初始化、函数的调用和网络请求三个部分,分别以FeignClientRegistrar、FeignClientFactoryBean、SynchronousMethodHandler为链路起点。
1、BeanDefinition的注册
1.1、@EnableFeignClients注解启用Feign
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) // 引入FeignClientsRegistrar,用来处理@FeignClient
public @interface EnableFeignClients {
/**
* 用来指定自动扫描的包
*/
String[] value() default {};
String[] basePackages() default {};
Class>[] basePackageClasses() default {};
/**
* 自定义配置
*/
Class>[] defaultConfiguration() default {};
/**
* 指定FeignClient注解修饰的类,如果不为空,将禁用FeignClient自动扫描
*/
Class>[] clients() default {};
}
1.2、FeignClientsRegistrar注册
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 处理@EnableFeignClients注解,从配置参数来构建Feign的自定义Configuration来注册
registerDefaultConfiguration(metadata, registry);
// 扫描@FeignClient注解修饰的类,注册BeanDefinition到BeanDefinitionRegistrar中
registerFeignClients(metadata, registry);
}
1.2.1、处理@EnableFeignClients注解,注册配置
@EnableFeignClients的自定义配置类是被@Configuration注解修饰的配置类,它会提供一系列组装FeignClient的各类组件实例。这些组件包括:Client、Targeter、Decoder、Encoder和Contract等。
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();
}
// 注册BeanDefinition到Spring容器中
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
1.2.2、处理@FeignClient注解,注册FeignClient
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
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)));
}
// 通过scanner从扫描路径下扫描候选FeignClient
for (String basePackage : basePackages) {
Set 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 attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册BeanDefinition到Spring容器中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
2.3、FeignAutoConfiguration,将配置的实例放到FeignContext上下文
public class FeignAutoConfiguration {
// 自动注入所有的FeignClientSpecification配置Bean
@Autowired(required = false)
private List configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
// 把这些配置Bean放到上下文中,创建FeignContext到Spring容器
context.setConfigurations(this.configurations);
return context;
}
//...
}
2、实例的初始化
前一步有FeignClientRegistrar完成BeanDefinition的注册后,FeignClientFactoryBean接过使命,进行Bean的初始化。
2.1、FeignClientFactoryBean
FeignClientFactoryBean实现了FactoryBean接口,作为工厂类,Spring容器通过调用它的getObject方法来获取对应的Bean实例。通过判断@FeignClient注解是否有url配置,决定生成的是LoadBalancerFeignClient还是普通的FeignClient,因为前者更常见,这里着重介绍LoadBalancerFeignClient。LoadBalancerFeignClient的负载均衡由Ribbon支持,在这里不做详细介绍。
@Override
public Object getObject() throws Exception {
return getTarget();
}
// 获取FeignClient实例
T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 从上下文中获取Encode,Contract等各种组件放到Feign.Builder中
Feign.Builder builder = feign(context);
// 判断对应FeignClient是否有url
// 有url会生成LoadBalancerFeignClient
// 否则返回普通的FeignClient
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
// 生成LoadBalancerFeignClient,带有负载均衡
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
// 没有配置url,返回普通的FeignClient
// ...
}
// 获取LoadBalancerFeignClient
// 这里会从上下文中获取Client和Targeter,如果尚未初始化会触发他们的实例化
// 在FeignClientAutoConfiguration中有Targeter的
protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget target) {
// 从上下文中获取Client实例,默认为ApacheHttpClient
Client client = getOptional(context, Client.class);
if (client != null) {
// 在Builder中放入Client
builder.client(client);
// 获取Client实例
Targeter targeter = get(context, Targeter.class);
// target()调用Builder.build()方法,然后调用newInstance方法生成Proxy
return targeter.target(this, builder, context, target);
}
// ...
}
2.2、Targeter
Targeter是一个接口,它的target方法生成对应的实例对象。他有两个实现类,分别为DefaultTargeter和HystrixTargeter。OpenFeign使用HystrixTargeter这一层抽象来封装关于Hystrix的实现。
DefaultTargeter
DefaultTargeter只是调用了Feign.Builder的target方法。
class DefaultTargeter implements Targeter {
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget target) {
return feign.target(target);
}
}
Feign.Builder#target()
public T target(Target target) {
// newInstance()调用的是ReflectiveFeign的newInstance()
return build().newInstance(target);
}
2.3、 重要的ReflectiveFeign
2.3.1、 newInstance()的两件事
newInstance()主要做了两件事:
- 扫描了该FeignClient接口类中的函数信息,生成对应的MethodHandler。
targetToHandlersByName.apply(target);
- 使用Proxy生成FeignClient的实例对象。
- Contract
在生成对应的MethodHandler过程中,BaseContract的parseAndValidateMetadata方法会依次解析接口类的注解,函数注解和函数的参数注解,将这些注解包含的信息封装到MethodMetadata对象中,将@RequestMapping的uri拼接到函数的uri中等操作。
在MethodHandler中包含了SynchronizedMethodHandler,内含RestTemplate.Factory用来生成最后的请求RestTemplate。
@Override
public T newInstance(Target target) {
// 这里生成所有方法MethodHandler
// nameToHandler由Contract解析target得到
// 在MethodHandler中存在对应方法的RestTemplate.Factory和SynchronizedMethodHandler
Map nameToHandler = targetToHandlersByName.apply(target);
Map methodToHandler = new LinkedHashMap();
List defaultMethodHandlers = new LinkedList();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 这里把target和MethodHandler的映射关系
// 创建出一个FeignInvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// 使用Proxy生成FeignClient的实例对象。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class>[] {target.type()}, handler);
// 把defaultMethodHandler绑定到proxy
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
2.3.2、第一件事:扫描了该FeignClient接口类中的函数信息
2.3.3、第二件事:使用Proxy生成FeignClient的实例对象
3、函数的调用和网络请求
3.1、SynchronousMethodHandler#invoke
调用RestTemplate.Factory的create方法生成RestTemplate方法。
@Override
public Object invoke(Object[] argv) throws Throwable {
// 调用RestTemplate.Factory的create方法生成RestTemplate实例
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// Target将template转换为request实例
// client调用request,返回response
return executeAndDecode(template);
} catch (RetryableException e) {
// ...
}
}
}
3.2、RestTemplate.Factory#create
根据实际的调用参数和之前的RestTemplate结合,生成RestTemplate实例。
@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map varBuilder = new LinkedHashMap();
// 遍历MethodMetadata中所有关于参数的索引及其对应名称的配置信息。
for (Entry> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
RequestTemplate template = resolve(argv, mutable, varBuilder);
// 设置queryMap参数
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
// 调用Encoder进行入参解析
Map queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
// 设置headerMap参数
if (metadata.headerMapIndex() != null) {
template =
addHeaderMapHeaders((Map) argv[metadata.headerMapIndex()], template);
}
return template;
}
3.3、SynchronousMethodHandler#executeAndDecode
在这个流程里用到了Encoder、Decoder、ErrorDecoder组件;
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 1、通过RequestInterceptor机制,
// 通过拦截器为每个请求增加固定的header信息
Request request = targetRequest(template);
Response response;
long start = System.nanoTime();
try {
// 2、调用client的execute方法进行http请求
response = client.execute(request, options);
} catch (IOException e) {
throw errorExecuting(request, e);
}
boolean shouldClose = true;
try {
if (Response.class == metadata.returnType()) {
// ... 省略进行一些body校验的代码
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
// http请求状态码为在[200, 300)代表调用成功
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
// 调用异常,且返回类型为Void
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
// 调用失败,errorDecoder组件解析返回信息
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
RequestInterceptor
OpenFeign也提供了RequestInterceptor机制,在由RestTemplate生成Request的过程中,会调用所有的RequestInterceptor对RestTemplate进行处理。
Request targetRequest(RequestTemplate template) {
// 使用请求拦截器为每个请求增加固定的header信息
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
Client
Client用来进行真正的Http请求,前文提到当@FeignClient中有url时,getTarget()中会生成LoadBalancerFeignClient,Ribbon实现了负载均衡决定调用哪台机器。
// LoadBalancerFeignClient#execute
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 将request封装成了RibbonRequest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
// 这里涉及到Ribbon如何实现负载均衡,具体内容将在Ribbon源码中展开。
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
// ... 不重要
}
}
真正执行的Client有OkHttpClient和RibbonClient两个子类。OkhttpClient调用OkHttp的相关组件进行网络请求的发送。
参考
[Spring Cloud微服务架构进阶