Feign源码解析——执行过程
在上一篇Feign源码解析——初始化流程中我们从注解为起点介绍了Feign是如何进行初始化参数、将Bean注入到Spring容器中的。接下来我们就介绍Feign是如何开始执行的。
回顾
在上一篇中我们了解到了Feign将自身的参数注入到Spring容器中是分两种类型进行注入的。
-
FeignClientSpecification
:主要为名字和Configuration的对应,包括@FeignClient
中的Configuration,名字为value值,还包括@EnableFeignClients
注解中的name和DefaultConfiguration对应 -
FeignClientFactoryBean
:它其中包含了所有@FeignClient
注解上的参数,他实现了Spring中的FactoryBean
接口。他是一个工厂类,用于创建实例。
不了解FactoryBean
接口的,可以看如何使用Spring的FactoryBean接口
FeignClientFactoryBean
介绍
作为一个实现了FactoryBean
的工厂类,那么每次在Spring Context 创建实体类的时候会调用它的getObject()
方法。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
private Class<?> type;
private String name;
private String url;
private String path;
private boolean decode404;
private ApplicationContext applicationContext;
private Class<?> fallback = void.class;
private Class<?> fallbackFactory = void.class;
-------其余代码省略
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, 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 lod balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
-------其余代码省略
}
这里的getObject()
其实就是将@FeinClient
中设置value值进行组装起来,此时或许会有疑问,因为在配置FeignClientFactoryBean
类时特意说过并没有将Configuration传过来,那么Configuration中的属性是如何配置的呢?看其第一句是
FeignContext context = applicationContext.getBean(FeignContext.class);
意思就是从Spring容器中获取FeignContext.class
的类,我们可以看下这个类是从哪加载的。我们可以看FeignAutoConfiguration
此类,源码如下,我们可以看到在此类中自动配置了FeignContext
类,并且将FeignClientSpecification
类型的类全部加入此类的属性中。
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.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
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
-------省略
}
至此我们已经完成了配置属性的装配工作,那么是如何执行的呢?我们可以看getObject()
最后一句可以看到返回了Targeter.target
的方法。
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
那么这个Targeter
是哪来的?我们还是看上面的FeignAutoConfiguration
类,可以看到其中有两个Targeter
类,一个是DefaultTargeter
,一个是HystrixTargeter
。当配置了feign.hystrix.enabled= true
的时候,Spring容器中就会配置HystrixTargeter
此类,如果为false那么Spring容器中配置的就是DefaultTargeter
我们以DefaultTargeter
为例介绍一下接下啦是如何通过创建代理对象的
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
public static class Builder {
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);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
}
查看ReflectiveFeign
类中newInstance
方法是返回一个代理对象
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;
}
最终都是执行了SynchronousMethodHandler
拦截器中的invoke
方法
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
invoke
方法方法主要是应用 encoder,decoder 以及 retry 等配置, 并且自身对于调用结果有一定的处理逻辑。我们最关心的请求实现,实际上是在组装 SynchronousMethodHandler 的 client 参数上,即前面提到的,如果当前路径里面有 Ribbon,就是 LoadBalancerFeignClient,如果没有,根据配置生成 ApacheHttpClient 或者 OKHttpClient。在 Ribbon 里面,实现了 Eureka 服务发现以及进行请求等动作。当然 Ribbon 里面还带了负载均衡逻辑。
SynchronousMethodHandler
其实是不关心调用过程的,他只是处理调用的结果。调用过程是实现了Client
的实现类来做的。例如Ribbon
的LoadBalancerFeignClient
。
总结
到此为止,Feign的配置和执行流程已经简单的说完了。
调用接口为什么会直接发送请求?
原因就是Spring扫描了@FeignClient
注解,并且根据配置的信息生成代理类,调用的接口实际上调用的是生成的代理类。
Feign的整体工作流程
- 扫描
@EnableFeignClients
注解中配置包路径。 - 扫描
@FeignClient
注解,并将注解配置的信息注入到Spring容器中,类型为FeignClientFactoryBean
。 - 根据
FeignClientFactoryBean
的getObject()
方法得到不同动态代理的类。 - 根据不同的代理执行不同的
invoke()
方法。