zoukankan      html  css  js  c++  java
  • 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中。

    最终创建出了代理对象。

  • 相关阅读:
    使用kerl安装erlang遇到的问题及解决办法-bak
    zip无法解压
    利用正则表达式统计访问频率
    windows开dump
    ftp禁止切换回上级目录
    windows组策略屏蔽
    对于超体的一点思考
    测试php语句执行时间
    php中点击下载按钮后待下载文件被清空
    mysql_fetch_assoc查询多行数据
  • 原文地址:https://www.cnblogs.com/dalianpai/p/15428050.html
Copyright © 2011-2022 走看看