在上一篇文章《Feign源码之@EnableFeignClients》
- 通过registerClientConfiguration我们知道了每一个client的configuration都是作为FeignClientSpecification的一个属性,真正被注入的类型是FeignClientSpecification。
- registerFeignClients方法注册的bean是FeignClientFactoryBean类型,还不是feign最终使用的动态代理对象。
本文则一探究竟,这两个类到底有什么用?
FeignClientFactoryBean
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
可以看到该类实现了FactoryBean接口,那就看一下他的getObject()方法
@Override
public Object getObject() throws Exception {
return getTarget();
}
该方法调用getTarget方法
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) 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 load 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 (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
获取上下文环境FeignContext
首先获得context,因为该类实现了ApplicationContextAware接口,会使用setApplicationContext方法给成员变量applicationContext赋值获得context上下文(注意与后面讲的FeignContext区别开来,前者是spring容器共用,后者的作用是为每一个feignClint单独创建一个AnnotationConfigApplicationContext上下文环境)
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
}
从上下文直接获得FeignContext的bean
FeignContext context = applicationContext.getBean(FeignContext.class);
打开FeignContext,继承了NamedContextFactory,从之前的ribbon源码学习中我们可以了解到,NamedContextFactory可以为每一个ribbonClient创建单独的context,做到每个client有自己单独的上下文环境,不被别的client配置影响。那么在这里作用是为每一个feignClint单独创建一个AnnotationConfigApplicationContext上下文环境
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
其实contexts的底层还是一个map,key是contextId,value存放的是AnnotationConfigApplicationContext
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
其getContext方法就是直接从map中取值
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);
}
若map中不存在,则调用createContext方法为当前FeignClient创建一个context出来,key即位contextId,关于contextId的取值逻辑可以参考文首链接重温一遍
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);
}
}
}
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) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
//刷新容器
context.refresh();
return context;
}
从这里可以看到configurations的身影了
private Map<String, C> configurations = new ConcurrentHashMap<>();
那么这个ConcurrentHashMap的集合是什么时候初始化的呢?
FeignAutoConfiguration
打开FeignAutoConfiguration,找到如下代码,同时也知道了容器中FeignContext是如何注册的
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
setConfigurations方法对configurations进行了赋值
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
问题又来了,参数传进来的configurations是怎么来的呢?
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
可以看到,使用@Autowired自动注入了FeignClientSpecification的list,而FeignClientSpecification的注册我们已经在上一篇文章讲过,不再赘述
创建Feign的建造器Builder
protected Feign.Builder feign(FeignContext context) {
//从FeignContext中获取到了FeignLoggerFactory组件
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;
}
首先获得FeignLoggerFactory,从context中获得encoder,decoder以及contract,都设置到了Feign.Builder组件中,那么他们的实现类是如何注册的呢?发现在同包下面还有一个FeignClientsConfiguration配置类,那就点进去看看。
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(logger);
}
Decoder、Encoder、Contract、FeignLoggerFactory的实际类型都很顺利的找到了。
因为Feign最终还是要发送HTTP请求,这⾥就难免要涉及到序列化和反序列化、这个过程就会牵扯到编码和解码的过程,Encoder和Decoder就派上⽤场了。
因为Feign⾃带的注解@FeignClient、以及SpringMVC注解它们是被谁处理的呢?Contract的实现类SpringMVCContract就是来解析它们的,解析所有的注解信息、然后拼凑成⼀个完整的HTTP请求所需要的信息。
最后调用过configureFeign方法把配置文件的一些配置绑定到feign的配置类上。
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
- defaultToProperties默认为true
configureUsingConfiguration(context, builder);
读取配置类的信息configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
配置文件的默认信息configureUsingProperties(properties.getConfig().get(this.contextId), builder);
配置文件中指定服务的配置信息- 默认情况下先读取配置来的信息,则配置文件的信息优先级更高,可以通过defaultToProperties配置为false,提高配置文件的优先级到最高
服务的配置信息优先级是要⽐默认配置要⾼的,所以也会覆盖默认的配置信息。
生成动态代理对象
至此,终于得到了builder对象,接下来执行代码到
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
给builder对象设置了client属性
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?");
}
接下来,我们看到它直接从容器中获取了⼀个Targeter对象,这个对象是HystrixTargeter对象,在FeignAutoConfiguration 中找到
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
然后直接调⽤target⽅法,如下图所示:
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 <T> T target(Target<T> target) {
return build().newInstance(target);
}
首先调用build方法,new 了一个SynchronousMethodHandler.Factory,创建了一个ReflectiveFeign对象并返回,这两个对象的作用,这里先不讲
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);
}
继续查看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;
}
进⼊到apply⽅法中,发现contract通过调⽤parseAndValidateMetadata⽅法得到了List,也就是接⼝中的所有⽅法的元数据信息,然后遍历这些⽅法,通过factory创建MethodHandler,⽽这⾥的 factory就是我们前⾯在build⽅法中看到的SynchronousMethodHandler.Factory 。
public Map<String, MethodHandler> apply(Target key) {
//contract通过调⽤parseAndValidateMetadata⽅法得到了接⼝中的所有⽅法的元数据信息
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
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(),
factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
接下来可以看到InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);
这两行代码,终于创建了动态代理。
接下来回到getTarget方法
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
以上是url为空的时候,使用负载均衡来调用,那么如果url属性不为空的时候,因为有ribbon的存在,需要解封装
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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
最后仍然是调用targeter.target方法获得代理对象。
最后,我们上文提到了SynchronousMethodHandler和ReflectiveFeign这两个对象,下文我们将继续探索。