引
众所周知,Feign
是一个声明式web服务客户端。它使编写web服务客户端更容易。要使用Feign
,只需要创建接口并且在接口上添加注解。
Feign还支持可插拔编码器和解码器。
Spring Cloud
增加了对Spring MVC注解的支持,并支持使用
与Spring Web中默认使用的 HttpMessageConverters
。
Spring Cloud集成了Eureka
、Spring Cloud CircuitBreaker
以及Spring Cloud LoadBalance
,在使用时提供负载均衡的Http客户端。
那么,功能如此强大的Spring Cloud OpenFeign
,到底是如何实现上述的功能?本文通过源码解析,对上述功能逐一解释。
一、使用方式
在介绍源码前,先简单介绍Spring Cloud OpenFeign是如何使用的。
1.1 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.2 添加注解@EnableFeignClients
为了开启Feign,需要在被添加了@Configuration
的类上面添加注解@EnableFeignClients
。
可以通过该注解指定FeignClient需要被扫描的包路径(比如@EnableFeignClients(basePackages = "com.example.clients")
),或者列举需要被添加的FeignClient(比如@EnableFeignClients(clients = InventoryServiceFeignClient.class)
)。当不指定包路径时,默认为添加@EnableFeignClients
注解的类所属的包。
可通过defaultConfiguration
字段指定全局生效的FeignClient的配置类,比如拦截器RequestInterceptor
、编解码器。详见官方文档1.2小节
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.3 添加FeignClient接口
注解 @FeignClient
添加到接口上,声明这是一个FeignClient,也就是Feign客户端。
- 默认字段
value
(别名name
)用于指定服务名称,无论是否提供url,必须为客户端提供服务的名称。可以指定为属性,比如${propertyKey}
。当contextId
不存在时,会将其作为bean名称的前缀。 - 可通过
contextId
字段指定FeignClient的bean名称。使用场景是当存在多个FeignClient的name
一样时,可以通过设置不同的contextId
避免出现由于bean名称相同而导致服务启动失败的情况。 - 可通过字段
url
指定服务端的URL或可解析主机名 - 通过
configuration
字段指定只作用于本Feign客户端的配置。详见官方文档1.2小节 - 通过
fallback
、fallbackFactory
字段指定请求出现异常之后的兜底方案。详见官方文档1.7小节
负载均衡客户端会根据value
(别名name
)字段,发现服务端的物理地址。如果当前应用是Eureka的客户端,它会通过Eureka的服务注册中心解析服务端的地址。
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
void delete(@PathVariable Long storeId);
}
二、源码解析
2.1 @EnableFeignClients
第一章介绍到,通过注解 @EnableFeignClients
开启FeignClient,并扫描指定路径/指定的FeignClient。
注解的字段第一章基本已介绍,该注解的重点是组合了另一个注解@Import(FeignClientsRegistrar.class)
,将FeignClientsRegistrar
通过@Import
的方式加载到容器中。
FeignClientsRegistrar
是FeignClient非常重要的一个类,是所有FeignClient的注册器。
@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 {};
}
2.2 FeignClientsRegistrar
2.2.1 实现接口ImportBeanDefinitionRegistrar
FeignClientsRegistrar
实现了Spring的接口 ImportBeanDefinitionRegistrar
。
通过实现接口ImportBeanDefinitionRegistrar
,可以在应用启动时,动态地将生成BeanDefinition
,并注册到BeanDefinitionRegistry
。后续ApplicationContext
可以根据这些BeanDefinition
实例化Bean
,并且注册到容器中。(具体流程可通过Spring源码了解。挖个坑,后面单独写一篇)
简单来说,通过FeignClientsRegistrar
,可以做到在应用开发者只提供接口的情况下,框架自动实现接口,并且自动注册到容器中。
从重写的方法registerBeanDefinitions
可以看到,这个方法主要做的事情就是将DefaultConfiguration
、FeignClient
注册到BeanDefinitionRegistry
中。方法内部做了什么事情,后续小节介绍。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// 省略....
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
// 省略....
}
2.2.2 实现接口 ResourceLoaderAware
这个接口比较简单,主要是将ApplicationContext
的ResourceLoader
传入FeignClientsRegistrar
中。ResourceLoader
主要用于包扫描、资源加载。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// 省略....
private ResourceLoader resourceLoader;
// 省略....
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
// 省略....
}
2.2.3 实现接口EnvironmentAware
这个接口也比较简单,主要是将ApplicationContext
的Environment
传入FeignClientsRegistrar
中。Environment
简单理解就是和配置有关的类,通过Environment
可以取出配置。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// 省略....
private Environment environment;
// 省略....
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
// 省略....
}
至此,已对FeignClientsRegistrar
实现的三个接口进行了简单介绍。接下来将介绍registerBeanDefinitions
方法,分析到底是怎么将@EnableFeignClients
、@FeignClient
的字段值作为构造FeignClient
的参数的。
2.2.4 方法registerDefaultConfiguration
从2.2.1小节中,我们看到了方法registerBeanDefinitions
里面有两个方法,第一个就是registerDefaultConfiguration
。
从方法名可以看出,这个方法主要做的事情就是将DefaultConfiguration
注册到BeanDefinitionRegistry
中。还记得1.2小节中介绍的@EnableFeignClients
的参数defaultConfiguration
,就是在这里发挥作用的。
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 从注解EnableFeignClients中取出所有字段的值
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
// 构建bean的名称
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 将BeanDefinitionRegistry、bean名称、defaultConfiguration对应的Class对象传入
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 通过 FeignClientSpecification 包装 configuration
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 注册
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
2.2.5 方法registerFeignClients
这个方法看似很长,实际上就干了几件事情:
- 构造扫描器
ClassPathScanningCandidateComponentProvider
用于在包路径中找到候选组件(也就是FeignClient
接口) - 给扫描器添加过滤器(筛选条件),包括筛选添加了
@FeignClient
注解的类;筛选指定的类 - 扫描器找出所有候选组件,并且将注解
@FeignClient
对应的configuration
注册到BeanDefinitionRegistry
;将注解@FeignClient
的参数值作为BeanDefinition
的参数,将FeignClientFactoryBean
注册到BeanDefinitionRegistry
。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 添加扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// 从EnableFeignClients取出字段值
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 构造过滤器,用于过滤添加了FeignClient的类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
// 从从EnableFeignClients的字段值中拿出clients字段,看看有没有指定FeignClient客户端的类
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
// 如果没有指定clients,则从EnableFeignClients取出包路径
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
// 如果指定了clients,则为其添加一个特定的过滤器,只保留clients内的FeignClient客户端
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");
// 从FeignClient中取出字段值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 为FeignClient注册特定的配置类
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
上述代码64行的方法registerFeignClient
,做的事情也比较简单,详见代码。
值得说明的是,当前注册的beanClass是FeignClientFactoryBean
,它实现了Spring的接口FactoryBean
,真正的FeignClient
是通过它进行创建的。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// FeignClientFactoryBean 作为 beanClass
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 校验fallback和fallbackFactory不是接口
validate(attributes);
// 将url和path参数取出,支持从配置中取出
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
//将服务名取出,优先级为serviceId > name > value
String name = getName(attributes);
definition.addPropertyValue("name", name);
//取出contextId,当contextId不指定时,与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);
}
至此,完成了FeignClientsRegistrar
的介绍。通过上面的介绍,我们了解了框架到底是如何通过上述几个重要的注解从包路径中找到我们创建的FeignClient
接口,并将其注册到BeanDefinitionRegistry
中。
那么,我们下一步要解决的是,框架到底是怎么将接口实现为具体的类的。
2.3 FeignClientFactoryBean
2.3.1 实现接口FactoryBean
对于构建一些复杂的bean,BeanDefinition
不是特别方便。Spring提供了一个特殊的接口FactoryBean
,可以将复杂的创建bean的逻辑写在FactoryBean
实现类上。在注册BeanDefinition
时,只需注册FactoryBean
实现类。后续实例化bean时,ApplicationContext可以自动调用接口的getObject()
方法取出真正的bean。(具体原理,再挖一个坑,后续写一篇文章分析)
因此,我们可以下一个定论,构造FeignClient
的逻辑,就在FeignClientFactoryBean
的getObject()
方法中。
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
// 省略....
private Class<?> type;
// 省略....
@Override
public Object getObject() throws Exception {
return getTarget();
}
// 省略....
@Override
public Class<?> getObjectType() {
return this.type;
}
// 省略....
@Override
public boolean isSingleton() {
return true;
}
// 省略....
}
2.3.2 实现接口InitializingBean
做了简单的参数校验,在这不做介绍,直接跳过
2.3.3 实现接口ApplicationContextAware
将ApplicationContext
传给FeignClientFactoryBean
,比较简单,不作过多介绍。
2.3.4 方法getTarget()
方法getObject()
调用了getTarget()
创建FeignClient
。方法主要做了以下几件事:
- 从
ApplicationContext
中取出FeignContext
。FeignContext
简单来说就是Feign
的上下文对象,每个FeignClient
都有自己私有的容器,这些容器通过一个map保存在FeignContext
中,key为contextId
。(后面的小节具体介绍,现在只需要知道可以通过FeignContext
实现依赖查找即可) - 从
FeignContext
中取出当前contextId
的Feign.Build
,并且配置编解码器、拦截器、日志打印级别等。 - 通过url是否为空,判断是否为RPC请求。如果是RPC请求,使用负载均衡的客户端;非RPC请求则使用默认的客户端。
- 从
FeignContext
取出Targeter
,Targeter
的target()
方法调用Feign.Builder
的target()
方法,从而触发构建FeignClient
。
<T> T getTarget() {
// 从ApplicationContext中取出FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 从FeignContext取出Feign建造器
Feign.Builder builder = feign(context);
// 一系列与url有关的操作
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
// 执行创建的方法
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
// 调用Feign.Builder的target方法创建Feign
return feign.target(target);
}
// 省略
}
至此,我们梳理完了FeignClientFactoryBean
的getTarget()
方法。相信读到这里的大家,一定能猜到,我们下一步就要分析 Feign
了。
可能大家读到这里的时候,会有一些疑惑,
FeignContext
是什么,什么时候创建的?Targeter
、Client
、Feign.Builder
又是什么时候创建的?有关这些信息,后面的小节会一一说明。这里先剧透一下,
FeignContext
、Targeter
、Client
都是通过自动配置创建的;Feign.Builder
是每个Feign
私有的,是在创建Feign
的子容器时创建注册到子容器的。
2.4 Feign
终于,我们来到了Feign
,真正实现FeignClient
接口的位置。
值得注意的是,上述几小节讲述的类、方法都是
spring-cloud-openfeign
这个包的。但是从
Feign
开始,就不是spring-cloud-openfeign
,而是单独的feign-form-spring
的。也就是说,前几节我们大费周章讲述的逻辑,都是为了将
Feign
封装成Spring
支持的形式,使得Feign
可以被Spring管理。前面介绍的一些逻辑,都是通用的将组件接入Spring的方法,具有很大的参考意义。
2.4.1 Feign.Builder
上一小节介绍到,通过Feign.Builder
的target()
方法构造FeignClient
。
我们可以看到,这个方法实际上是通过build()
方法构造了Feign
,再通过Feign
的newInstant()
方法实现FeignClient
的接口。
方法build()
做的事情很简单,就是根据builder
的字段值构造一个ParseHandlersByName
,并与invocationHandlerFactory
、queryMapEncoder
一起传进ReflectiveFeign
的构造方法里面。(builder
的字段逻辑是若有设置则使用设置的,没有则使用默认的)。
在这简单说明一下ParseHandlersByName
、InvocationHandlerFactory
作用。
ParseHandlersByName
:作用是对FeignClient
接口的每个方法创建一个处理器MethodHandler
,该处理器是方法真正的执行逻辑。
InvocationHandlerFactory
:从名字就可以看出,这是一个创建动态代理InvocationHandler
实现类的工厂类,通过InvocationHandler
动态代理地实现FeignClient
接口。
public abstract class Feign {
// 省略...
public static class Builder {
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
// 省略...
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
// 省略...
}
}
2.4.2 方法newInstance()
上一节提到Feign
通过newInstance()
返回接口的实现类,那么这一小节就介绍newInstant()
的干了什么事情。
从方法Feign.Builder.build()可以看出ReflectiveFeign是Feign的实现类,因此我们就来分析ReflectiveFeign的接口newInstance()
方法newInstance()
主要干了几件事:
- 为接口的每个方法创建一个处理类
MethodHandler
。在方法被调用时,通过MethodHandler
进行处理。 - 将所有方法的处理类作为入参,构造一个动态代理的
InvocationHandler
- 通过动态代理,实现
FeignClient
接口,并将该接口的实现类返回。
public class ReflectiveFeign extends Feign {
private final ParseHandlersByName targetToHandlersByName;
private final InvocationHandlerFactory factory;
// 省略..
@Override
public <T> T newInstance(Target<T> target) {
// 为接口的每个方法创建一个MethodHandler
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 {
// 将MethodHandler放到另一个map里面
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 创建动态代理的InvocationHandler
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;
}
}
至此,我们终于找到了实现接口的位置,梳理清楚了FeignClient
接口是怎么被实现的。
但是,有一些细节我们暂时还不是太清楚,包括InvocationHandler
的逻辑是怎么样的,每个方法的处理类MethodHandler
是怎么处理方法调用的。
2.4.3 ReflectiveFeign.FeignInvocationHandler
我们从Feign.Builder
可以看出,InvocationHandlerFactory
默认的实现类就是InvocationHandlerFactory.Default
。创建InvocationHandler
的方法create()
创建的就是ReflectiveFeign.FeignInvocationHandler
。
因此想知道InvocationHandler
的逻辑,分析ReflectiveFeign.FeignInvocationHandler
即可。
public abstract class Feign {
public static class Builder {
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
}
}
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
// 默认实现类
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
FeignInvocationHandler
的逻辑是根据方法,从dispatch
的一个map中取出方法的处理器MethodHandler
,并且调用MethodHandler
的invoke()
方法进行处理。
public class ReflectiveFeign extends Feign {
static class FeignInvocationHandler implements InvocationHandler {
// 省略...
private final Map<Method, MethodHandler> dispatch;
// 省略...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
// 根据方法,分发到具体的处理器进行处理
return dispatch.get(method).invoke(args);
}
// 省略...
}
2.4.4 ReflectiveFeign.ParseHandlersByName
从方法ReflectiveFeign.newInstance()
的第一行可以知道,MethodHandler
是通过ParseHandlersByName
的方法apply()
创建的。
那么,我们分析一下apply()
到底做了哪几件事:
- 取出接口各方法的元信息。包括方法的入参、出参、注解等。我们可以在这里画个重点,之所以FeignClient支持Spring MVC的那一套注解,都是因为这个方法做了解析。由于篇幅有限,具体细节在这里不再详细介绍,详见
org.springframework.cloud.openfeign.support.SpringMvcContract
- 创建用于构造
RequestTemplate
的BuildTemplateByResolvingArgs
- 通过
SynchronousMethodHandler.Factory
创建方法处理器MethodHandler
public class ReflectiveFeign extends Feign {
private final Contract contract;
private final SynchronousMethodHandler.Factory factory;
static final class ParseHandlersByName {
public Map<String, MethodHandler> apply(Target key) {
// 取出方法的元信息(包括入参、出参,注解等信息)
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
// 创建用于构造RequestTemplate的BuildTemplateByResolvingArgs
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
result.put(md.configKey(),
// 创建方法处理器MethodHandler
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
}
}
2.4.5 SynchronousMethodHandler
到这里,其实基本上已经分析地差不多了。我们最后再来看一眼,FeignClient
的方法最后到底是怎么被执行的。
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
// 构造RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 发送请求并且接收结果
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
}
到这里,可能有人会问,负载均衡的逻辑呢?
可以在
executeAndDecode()
方法继续往下看,负载均衡的逻辑在feign.Client#execute()
的实现里。由于篇幅有限,本文暂不介绍这部分的内容。
至此,终于把FeignClient
如何从接口变成实现类讲完了。前面几节,为了逻辑的连贯性,忽略了一些Spring Cloud OpenFeign的一些配置相关的内容,下面再介绍一下。
2.5 FeignAutoConfiguration
还记得2.3.4 中,突然冒出来了几个组件。一开始看源码的时候,可能会有疑问,这些组件啥时候被加载进ApplicationContext的?直觉告诉我,应该是自动配置干的。
简单翻了一下spring-cloud-openfeign-core-2.2.1.RELEASE.jar
,找到了一个和自动配置密切相关的文件META-INF/spring.factories
。在应用启动的时候,Spring会从META-INF/spring.factories
读入需要被自动配置的类,并且将符合条件的配置注册到容器中。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
可以看到,刚刚用到的几个bean,FeignContext
、Targeter
都是通过自动配置加载的。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
那么,可能又有人要问了,这才几个bean啊,其他的bean的?这就要提到
FeignContext
了
2.6 FeignContext
上一节看到,FeignContext
在自动配置的时候创建并且注册到ApplicationContext
中。在创建之后,还将configurations
设置到FeignContext
中。
帮大家回忆一下,这些
FeignClientSpecification
就是在2.2节注册BeanDefinition
时,注册的FeignClient
的全局配置类、私有配置类
我们先来简单介绍一下FeignContext
的功能。FeignContext
持有所有FeignClient
的私有容器,可以做到每个FeignClient
可以拥有自己独有的bean,相互之间隔离。
当FeignClient
需要取出私有容器时,可以通过FeignContext
,将自己的唯一标识contextId
传入。
FeignContext
继承了NamedContextFactory
。NamedContextFactory
有两个重要的方法getContext()
、createContext()
。
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
// 将FeignClientsConfiguration传入,作为默认的配置类
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
下面分别介绍这两个方法。
2.6.1 getContext()
这个方法比较简单,就是根据名称从Map取出对应的容器,将将其返回。若该名称的容器还不存在,则立即创建一个,然后返回。
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
protected AnnotationConfigApplicationContext getContext(String name) {
// 每个名称空间有一个单独的容器,若还不存在则触发创建。
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
}
}
2.6.2 createContext()
该方法主要的作用为指定名称创建容器,在创建容器时,主要做了以下几件事:
- 将本名称空间独有的配置类(也就是
@FeignClient
指定的)注册到子容器中 - 将全局的配置类(也就是
@EnableFeignClients
指定的)注册到子容器中 - 将其他自动配置类、配置类注册到子容器中。
FeignClientsConfiguration
就是这个时候被注册到子容器的,Contract
、Feign.Builder
等组件也被顺带注册到子容器中。 - 设置子容器的父容器。将应用的容器作为父容器,这样做的好处是,当需要找某个bean时,若子容器不存在,会自动在父容器中查找。
- 触发刷新子容器。
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// 父容器
private ApplicationContext parent;
// 默认的配置类
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
// 当前spring容器为父容器
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
this.parent = parent;
}
public void setConfigurations(List<C> configurations) {
// 保存每个名称空间的配置
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 将当前名称空间的配置类注册到容器中
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
// 将全局的配置类注册到容器中
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 注册配置类 FeignClientsConfiguration
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// 将Spring容器作为父容器
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
// 刷新子容器
context.refresh();
return context;
}
}
三、总结
至此,我们终于把Spring Cloud OpenFeign的源码的大概脉络梳理完了。我们来简单总结一下:
- 通过
@EnableFeignClients
的@Import(FeignClientsRegistrar.class)
,实现了动态地将指定范围内的@FeignClient
的接口的BeanDefinition
注册到容器中。 - 基于
META-INF/spring.factories
配置文件,自动将一些OpenFeig的组件注册到容器中。 - 基于
FeignContext
,实现了不同FeignClient
的组件隔离的效果。 - 基于
FeignClientFactoryBean
,封装了对Feign
使用。通过调用Feign
,实现了以动态代理的方式实现@FeignClient
接口。