FeignClientFactoryBean创建动态代理

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中。

最终创建出了代理对象。

posted @ 2021-10-20 11:06  天宇轩-王  阅读(891)  评论(0编辑  收藏  举报