java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > FeignClientFactoryBean动态代理

FeignClientFactoryBean创建动态代理详细解读

作者:dalianpai

这篇文章主要介绍了FeignClientFactoryBean创建动态代理详细解读,当直接进去注册的方法中,一步步放下走,都是直接放bean的定义信息中放入值,然后转成BeanDefinitionHolder,最后在注册到IOC容器中,需要的朋友可以参考下

FeignClientFactoryBean创建动态代理

探索FeignClient的注册流程

当直接进去注册的方法中,一步步放下走,都是直接放bean的定义信息中放入值,然后转成BeanDefinitionHolder,最后在注册到IOC容器中。 具体的信息可以看下面断点的图。

image-20211019140122997

image-20211019140213072

在仔细看一下就会发现很奇怪,为什么此处传入的是FeignClientFactoryBean,然后把feignclient的信息放它的里面,那我们就进去看看。

那我们就进行看一下FeignClientFactoryBean,里面的内容

image-20211019140829389

首先发现它实现了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,他们都是接⼝

image-20211019150645159

发现在同包下面还有一个FeignClientsConfiguration配置类,那就点进去看看。

image-20211019150847945

image-20211019151005982

Decoder、Encoder、Contract、FeignLoggerFactory的实际类型都很顺利的找到了。 因为Feign最终还是要发送HTTP请求,这⾥就难免要涉及到序列化和反序列化、这个过程就会牵扯到编码和解码的过程,Encoder和Decoder就派上⽤场了。 因为Feign⾃带的注解@FeignClient、以及SpringMVC注解它们是被谁处理的呢?Contract的实现类SpringMVCContract就是来解析它们的,解析所有的注解信息、然后拼凑成⼀个完整的HTTP请求所需要的信息。

image-20211019144245884

在最后一行,有一个这个方法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);
		}
	}

下图为配置类中的信息

image-20211020101245947

下面为指定服务的yml中的信息

image-20211020101338558

image-20211020101454713

服务的配置信息优先级是要⽐默认配置要⾼的,所以也会覆盖默认的配置信息。

分析到这⾥,Feign要创建⼀个对象的所需要的信息都设置的差不多了,简单来说就四⼤部分:

(1)默认的组件

(2)⾃定义组件

(3)默认的配置信息

(4)指定服务的配置信息

创建Feign的动态代理对象

然后我们就继续放行,最终得到了Feign.Builder对象,然后我们继续放行,来到了截图的哪行,这里会返回一个对象。

image-20211019144325708

点进去,我们继续分析,第一行我们直接过,但是看idea的提示类型是LoadBalancerFeignClient,LoadBalancer是ribbon的三大核心组件之一,应该是和reibbon负载均衡有关系。

image-20211019144547474

那我们就找一下它是在那个配置类被创建的。

image-20211020104444844

接下来,我们看到它直接从容器中获取了⼀个Targeter对象,这个对象是HystrixTargeter对象,在FeignAutoConfiguration 中找到、然后直接调⽤target⽅法,如下图所示:

image-20211020102344252

那我们就来到它的这个方法中,继续放下放行:

image-20211020104727702

⼀路顺势跟到⾥⾯后发现,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毕竟是一个接口,所以给每个接口创建了一个动态代理,然后调用接口时,去让动态代理来执行

image-20211019155531287

一进入到这个方法中,会看到有2个Map和一个List,那我们就先看一下第一个map,里面的数据如下图:

image-20211019160113229

当我们点进apply方法的时候,进⼊到apply⽅法中、发现contract通过调⽤parseAndValidateMetadata⽅法得到了、List 、也就是接⼝中的所有⽅法的元数据信息,然后遍历这些⽅法,通过factory创建MethodHandler,⽽这⾥的 factory就是我们前⾯在build⽅法中看到的SynchronousMethodHandler.Factory 。

image-20211019161015024

后面的话,会遍历所有的nameToHandler,把它转成methodToHandler。image-20211020110050039

最终来到了这行InvocationHandler handler = factory.create(target, methodToHandler);把上面获取到的方法和要代理的对象都放到了FeignInvocationHandler中。

最终创建出了代理对象。

到此这篇关于FeignClientFactoryBean创建动态代理详细解读的文章就介绍到这了,更多相关FeignClientFactoryBean动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文