基于
spring-cloud-openfeign-core-2.2.5.RELEASE
。
本文以spring-cloud-openfeign-core-2.2.5.RELEASE
为基础,介绍spring cloud如何对feign进行扩展,简化接入复杂度,降低接入成本。
@EnableFeignClients
SpringCloud项目中对Feign使用,基于一贯的注解驱动方式。 通过简单地配置注解@EnableFeignClients
,SpringCloud就会扫描指定package,生成可供直接调用的feign实现类。
FeignClientsRegistrar
类型@EnableFeignClients
注解最终导致FeignClientsRegistrar
被引入到Spring容器,参与Spring生命周期。
FeignClientsRegistrar
实现了诸多Spring典型接口,其中最关键的是对于ImportBeanDefinitionRegistrar
接口的实现。
// ===================================== FeignClientsRegistrar.registerBeanDefinitions(...)
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 向 Spring容器中注入一个 FeignClientSpecification Bean(该Bean 名词以 default. 开头)。
// 注意 FeignClientSpecification 实现了 NamedContextFactory.Specification 接口, 形成Spring父子容器.
registerDefaultConfiguration(metadata, registry);
// 根据注解EnableFeignClients指定的package, 扫描其下注解@FeignClient的接口类型, 根据接口和注解信息, 1:1向Spring容器中注册一个FeignClientFactoryBean实例。
// 参见下方讲解
registerFeignClients(metadata, registry);
}
// ===================================== FeignClientsRegistrar.registerFeignClients(...)
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
......
// 扫描用户指定package
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
// 被@FeignClient注解的必须是接口类型
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);
// 为每个@FeignClient注解的接口类型, 向Spring父容器中注册一个对应的子容器FeignClientSpecification.
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 针对每个`@FeignClient`注解的接口, 生成并向容器中注册一个对应实现类的实例Bean, 这项工作由FeignClientFactoryBean完成
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
FeignClientFactoryBean
类型该类负责为被@FeignClient
注解的接口, 生成一个对应的实现类,供使用者调用。
FeignClientFactoryBean
继承自Spring中的典型接口FactoryBean
,所以核心方法为getObject()
。
// FeignClientFactoryBean.getObject()
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
// 容器中的FeignContext实例, 在 FeignAutoConfiguration 中实现注入。
FeignContext context = applicationContext.getBean(FeignContext.class);
// 根据用户配置, 从Spring容器中取出一系列feign组件, 组装构造出一个 Feign.Builder 实例。
// 注意这里就应用到了上面提到过的父子容器关系, Spring将优先从服务对应的子容器中取配置项.
Feign.Builder builder = feign(context);
// ...... 减少复杂度, 这里我们先不考虑LoadBalancer
// 从容器中取出Targeter实现类, 来生成@FeignClient注解接口的实现类.
// 这里依然是为了减少干扰项, 我们禁用默认的hystrix, 所以这里的Targeter实现类为`DefaultTargeter`
// 另外需要注意的一点是Targeter和Target的区别:
// 1. Targeter定义在spring-cloud-openfeign-core中。 主要用途是作为生成@FeignClient接口实现类的统一调用入口, 正如下面这两行代码。
// 2. Target定义在feign-core中。 代表@FeignClient接口在feign内部的实体表现。 (其中的type就是@FeignClient接口的类型); Targeter.target(...) 最后一个方法参数正是Target实现类HardCodedTarget。
Targeter targeter = get(context, Targeter.class);
// 构建出@FeignClient实现类实例的逻辑, 在这里由spring-cloud-openfeign-core中的Targeter转交给feign-core中的Feign.Builder。
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
Feign.newInstance(Target target)
(生成Proxy实例)本方法主要负责基于JDK的Proxy功能,生成@FeignClient所注解接口的实现类。
// `Targeter.target(...)` 只是简单地将处理逻辑调度给了`Feign.newInstance(Target target)`
// 在本文场景下,Feign实现类为ReflectiveFeign 。
// ===================================== ReflectiveFeign.newInstance(...)
@Override
public <T> T newInstance(Target<T> target) {
// 使用内部类型ParseHandlersByName, 解析当前@FeignClient接口类型, 将所定义的接口方法解析为MethodHandler实例(借助`Contract`实现类), 并最终以Map数据结构返回.
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)) { // 如果为default方法
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 借助自定义接口InvocationHandlerFactory, 使用工厂模式提供对外统一的InvocationHandler实例生成。正是借助InvocationHandlerFactory接口, 实现了对于 Hystrix 的支持.
// 当前场景下, InvocationHandler实现类为ReflectiveFeign.FeignInvocationHandler, 另外一个则是Hystrix相关的HystrixInvocationHandler。 再补充一个的就是Sentinel相关的SentinelInvocationHandler。
// 基于@FeignClient接口方法发起的http调用执行, 最终都会跳转到`FeignInvocationHandler`对于接口InvocationHandler接口方法`invoke(Object proxy, Method method, Object[] args)`的实现中。
InvocationHandler handler = factory.create(target, methodToHandler);
// 熟悉的JDK Proxy特性。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
// 这里返回的实例, 正是@FeignClient接口的实现类实例。 使用者通过@Autowired引入的@FeignClient实例正是它。
return proxy;
}
以上处理流程,串起来就是下面这副feign_init时序图了。
组件类型 | 说明 |
---|---|
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 。 |
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的集成。 |
方法参数:
Contract.BaseContract.parseAndValidateMetadata(Class> targetType, Method method)
负责解析。BuildTemplateByResolvingArgs.create(Object[] argv)
负责确保调用接口方法传参时候,参数不为null,且类型满足URI要求。SynchronousMethodHandler.findOptions(Object[] argv)
。被@FeignClient
注解的接口类型:
Contract
契约接口实现类来完成。Proxy.newProxyInstance
构建出该接口实现类( 典型源码位置为: T ReflectiveFeign.newInstance(Target target)
)。 相应的 InvocationHandler
实现类为: ReflectiveFeign.FeignInvocationHandler
和 HystrixInvocationHandler
。(抽象出专门的InvocationHandlerFactory
工厂接口来提供相应的对外扩展)。