FeignClientFactoryBean创建动态代理
探索FeignClient的注册流程
当直接进去注册的方法中,一步步放下走,都是直接放bean的定义信息中放入值,然后转成BeanDefinitionHolder,最后在注册到IOC容器中。 具体的信息可以看下面断点的图。
在仔细看一下就会发现很奇怪,为什么此处传入的是FeignClientFactoryBean,然后把feignclient的信息放它的里面,那我们就进去看看。
那我们就进行看一下FeignClientFactoryBean,里面的内容
首先发现它实现了FactoryBean接口,可以返回bean的实例的工厂bean,通过实现该接口可以对bean进行一些额外的操作。此处肯定重写了getObject方法,就是在此处,这个方法可以往容器中注入Bean的。
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
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));
}
在getObject⽅法中,⾸先从applicationContext中、也就是从Spring容器中获取了⼀个FeignContext组件,应该是Feign存放⼯具类的⼀个上下⽂组件,然后从FeignContext中获取到了FeignLoggerFactory组件,⼀路追进去发现,原来在底层也是维护了⼀个Spring容器的缓存Map<String, AnnotationConfigApplicationContext>。Feign在执⾏时涉及到⼀系列的组件,所以Feign⼲脆为每个服务创建了⼀个Spring容器类ApplicationContext,⽤来存放每个服务各⾃所需要的组件,每个服务的这些⼯具类、组件都是互不影 响的,所以我们看到它在底层是通过⼀个Map来缓存的,key为服务名,value为服务所对应的的spring
容器ApplicationContext。
接下来我们陆续看到了其他的组件如:Decoder、Encoder、Contract等都被创建了出来,都设置到了Feign.Builder组件中,看来Feign.Builder应该是要为创建对象设置⼀些必要的组件信息。
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;
}
但是发现FeignContext、FeignLoggerFactory还是Encoder、Decoder、Contract,他们都是接⼝
发现在同包下面还有一个FeignClientsConfiguration配置类,那就点进去看看。
Decoder、Encoder、Contract、FeignLoggerFactory的实际类型都很顺利的找到了。
因为Feign最终还是要发送HTTP请求,这⾥就难免要涉及到序列化和反序列化、这个过程就会牵扯到编码和解码的过程,Encoder和Decoder就派上⽤场了。
因为Feign⾃带的注解@FeignClient、以及SpringMVC注解它们是被谁处理的呢?Contract的实现类SpringMVCContract就是来解析它们的,解析所有的注解信息、然后拼凑成⼀个完整的HTTP请求所需要的信息。
在最后一行,有一个这个方法configureFeign(context, builder);
,看方法名字就是配置feign,猜一猜应该是把配置文件的一些配置绑定到feign的配置类上。
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context,
FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if (properties != null && inheritParentContext) {
if (properties.isDefaultToProperties()) {
//这行默认取的是配置文件的信息。
configureUsingConfiguration(context, builder);
//这行取的是yml中的默认配置
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
//这行是yml中指定服务的配置
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);
}
}
下图为配置类中的信息
下面为指定服务的yml中的信息
服务的配置信息优先级是要⽐默认配置要⾼的,所以也会覆盖默认的配置信息。分析到这⾥,Feign要创建⼀个对象的所需要的信息都设置的差不多了,简单来说就四⼤部分:
(1)默认的组件
(2)⾃定义组件
(3)默认的配置信息
(4)指定服务的配置信息
创建Feign的动态代理对象
然后我们就继续放行,最终得到了Feign.Builder对象,然后我们继续放行,来到了截图的哪行,这里会返回一个对象。
点进去,我们继续分析,第一行我们直接过,但是看idea的提示类型是LoadBalancerFeignClient,LoadBalancer是ribbon的三大核心组件之一,应该是和reibbon负载均衡有关系。
那我们就找一下它是在那个配置类被创建的。
接下来,我们看到它直接从容器中获取了⼀个Targeter对象,这个对象是HystrixTargeter对象,在FeignAutoConfiguration 中找到、然后直接调⽤target⽅法,如下图所示:
那我们就来到它的这个方法中,继续放下放行:
⼀路顺势跟到⾥⾯后发现,target⽅法中、调⽤了build⽅法,⾥⾯创建了SynchronousMethodHandler.Factory,翻译起来也就是同步⽅法处理器的⼯⼚类,具体⼲什么⽤的、⽬前也不是好确定,反正只知道⼯⼚就是⽤来创建对象⽤的,然后它把这个⼯⼚类放到了ParseHandlersByName中,并且new上了⼀个ReflectiveFeign对象并返回了。
紧接着调⽤了newInstance⽅法,看样⼦是要创建⼀个对象,继续跟进看下:
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
@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;
}
看见 InvocationHandler handler = factory.create(target, methodToHandler);这行,基本就可以确定,是创建了动态代理。因为前面解析@FeignClient注解,并且封装了一个Bean的定义信息注册中Ioc容器中,比较ServiceAClient毕竟是一个接口,所以给每个接口创建了一个动态代理,然后调用接口时,去让动态代理来执行
一进入到这个方法中,会看到有2个Map和一个List,那我们就先看一下第一个map,里面的数据如下图:
当我们点进apply方法的时候,进⼊到apply⽅法中、发现contract通过调⽤parseAndValidateMetadata⽅法得到了、List
后面的话,会遍历所有的nameToHandler,把它转成methodToHandler。
最终来到了这行InvocationHandler handler = factory.create(target, methodToHandler);
把上面获取到的方法和要代理的对象都放到了FeignInvocationHandler中。
最终创建出了代理对象。