基于spring-cloud-openfeign-core-2.2.5.RELEASE 。
1. 概述
本文以spring-cloud-openfeign-core-2.2.5.RELEASE 为基础,介绍spring cloud如何对feign进行扩展,简化接入复杂度,降低接入成本。
2. 入口@EnableFeignClients
SpringCloud项目中对Feign使用,基于一贯的注解驱动方式。 通过简单地配置注解@EnableFeignClients ,SpringCloud就会扫描指定package,生成可供直接调用的feign实现类。
2.1 FeignClientsRegistrar 类型
@EnableFeignClients 注解最终导致FeignClientsRegistrar 被引入到Spring容器,参与Spring生命周期。
FeignClientsRegistrar 实现了诸多Spring典型接口,其中最关键的是对于ImportBeanDefinitionRegistrar 接口的实现。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
......
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
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);
}
}
}
}
2.2 FeignClientFactoryBean 类型
该类负责为被@FeignClient 注解的接口, 生成一个对应的实现类,供使用者调用。
FeignClientFactoryBean 继承自Spring中的典型接口FactoryBean<T> ,所以核心方法为getObject() 。
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
3. Feign.newInstance(Target<T> target) (生成Proxy实例)
本方法主要负责基于JDK的Proxy功能,生成@FeignClient所注解接口的实现类。
@Override
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
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)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
4. 整体时序图
以上处理流程,串起来就是下面这副feign_init时序图了。 
5. 各组件意义
组件类型 | 说明 |
---|
Targeter | spring-cloud-openfeign-core-2.2.5.RELEASE.jar 中定义。提供了@FeignClient接口实现类生成的对外统一门面。 | Target | feign-core-10.10.1.jar 中定义。代表被@FeignClient注解接口在Feign体系内的内部实例。 | Contract | 负责解析被@FeignClient注解接口类型,将方法上标注的注解解析成 MethodMetadata 。 | MethodMetadata | 被@FeignClient注解接口内定义的方法,解析为MethodMetadata 。 | MethodHandler | 实际http请求发起的入口。类似于InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])} 。 | RequestTemplate | RequestTemplate.Factory 接口实现基于方法参数构建出RequestTemplate 实例。
Target 则负责依据RequestTemplate 实例构建出相应的Request 实例,用作马上要开始的http调用。 | Feign | 负责生成@FeignClient注解接口的实现类。关键方法T newInstance(Target<T> target) 。 | Client | 抽象了发起http调用,获取响应这套流程。 | Request / Response | 作为Client 接口的入参和出参,很明显这两个类型代表了feign内部对于http请求和返回值的抽象。另外这两个类型均被final修饰,说明feign设计里没打算让外界在这个维度进行扩展。 | Logger | feign内部抽象出来的记录日志的接口。将日志记录功能拆分为logRequest(...) ,logAndRebufferResponse(...) ,logRetry(...) ,logIOException(...) 阶段。子类需要实现唯一的抽象方法log(...) 。 | Encoder / Decoder | 分别负责入参的编码,以及出参的解码。默认情况未生效。 | Retryer | 抽象了发生异常时候,是否重试的逻辑实现。 | RequestInterceptor | 典型的拦截器模式,用于http请求发起前的自定义扩展需求。生效位置:SynchronousMethodHandler.targetRequest(RequestTemplate template) | InvocationHandlerFactory | InvocationHandler 工厂模式。Hystri和Sentinel正是基于本扩展接口实现与feign的集成。 |
6. 补充
6.1 @FeignClient注解接口中所定义方法的一些高级用法
方法参数:
- URI类型。用作动态host。
a. Contract.BaseContract.parseAndValidateMetadata(Class<?> targetType, Method method) 负责解析。 b. BuildTemplateByResolvingArgs.create(Object[] argv) 负责确保调用接口方法传参时候,参数不为null,且类型满足URI要求。 - Options类型。
a. 生效位置: SynchronousMethodHandler.findOptions(Object[] argv) 。
7. 总结
被@FeignClient 注解的接口类型:
- 内部定义的每个方法,对应一个http调用。
- 方法本身的定义,以及方法上的注解每个配置项,对应解析为http调用时的一个参数项。该职责由
Contract 契约接口实现类来完成。 - 基于JDK 的
Proxy.newProxyInstance 构建出该接口实现类( 典型源码位置为: T ReflectiveFeign.newInstance(Target<T> target) )。 相应的 InvocationHandler 实现类为: ReflectiveFeign.FeignInvocationHandler 和 HystrixInvocationHandler 。(抽象出专门的InvocationHandlerFactory 工厂接口来提供相应的对外扩展)。 - 使用者拿着该接口实现类,调用接口定义的方法,最终实现对于相应Http远程服务调用。
- 通过强类型化的面向接口编程习惯,确保了接口调用方式的一致性,减缓系统腐朽速度。
8. 参考
- 关于FeignClient的使用大全——进阶篇
- 深入理解 OpenFeign 的架构原理
- Spring Cloud Feign设计原理
|