• SpringCloud原理-OpenFeign篇(三、FeignClient的动态代理原理)


    前言

    本篇是SpringCloud原理系列的 OpenFeign 模块的第三篇。

    主要内容是接第二篇,在将FeignClientFactoryBean 的bean描述器注册到容器中后,我们的容器在初始化时,使用了饥饿模式,直接创建Bean。本文就围绕FeignClientFactoryBean来分析动态代码的应用,以及它本身的初始化过程。

    使用java 17,spring cloud 4.0.4,springboot 3.1.4

    正文

    一、前戏,FeignClientFactoryBean入口方法的分析

    首先看看它的类图:
    在这里插入图片描述
    实现了众多接口,我们先记得它实现了 FactoryBean接口。
    后文分析时有用到。

    1.1 从BeanFactory入手

    我们都知道,从Spring 容器中获取一个bean,都会用到BeanFactory的实现类。

    在众多继承关系中,AbstractBeanFactory 的存在,帮助实现了很多特殊逻辑。也包括对FactoryBean实现类的处理。
    在这里插入图片描述
    在这个抽象Bean工厂中,实现了getBean的方法。

    public Object getBean(String name) throws BeansException {
        return this.doGetBean(name, (Class)null, (Object[])null, false);
    }
    
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return this.doGetBean(name, requiredType, (Object[])null, false);
    }
    
    public Object getBean(String name, Object... args) throws BeansException {
        return this.doGetBean(name, (Class)null, args, false);
    }
    
    public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args) throws BeansException {
        return this.doGetBean(name, requiredType, args, false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看到,都是直接调用了一个 doGetBean 方法。

    1.2 AbstractBeanFactory#doGetBean(…)中对FactoryBean的处理

    这个doGetBean方法太长了,我这里不做粘贴,只挑重点的说。

    在这个方法中,获取Bean的时候,有调用 getObjectForBeanInstance(...)方法。而该方法中就对FactoryBean 做了处理。

    处理逻辑如下:在这里插入图片描述
    做了判断,如果当前实例是factoryBean的类型,就调用了getCachedObjectForFactoryBeangetObjectFromFactoryBean。这里有优化,从缓存中获取不到时,会从工厂中获取,也就是去创建实例。在这里插入图片描述

    1.3 结论 FactoryBean#getObject()

    这里的关键方法,doGetObjectFromFactoryBean 就使用了FactoryBean接口。

        private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
            Object object;
            try {
                object = factory.getObject();
            } catch (FactoryBeanNotInitializedException var5) {
                throw new BeanCurrentlyInCreationException(beanName, var5.toString());
            } catch (Throwable var6) {
                throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", var6);
            }
    
            if (object == null) {
                if (this.isSingletonCurrentlyInCreation(beanName)) {
                    throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");
                }
    
                object = new NullBean();
            }
    
            return object;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以看到调用了FactoryBean#getObject(),并返回了对应的实例。而这也就是本篇的开始。

    二、FeignClientFactoryBean实现的getObject()

    从类的定义上,FeignClientFactoryBean 实现了FactoryBean。
    所以,在从容器中获取该类型实例时,也就会调用到getObject()

    实现如下:

    @Override
    public Object getObject() {
    	return getTarget();
    }
    
    • 1
    • 2
    • 3
    • 4

    你没看错,它就一行代码,但是就这一行代码,其复杂程度却丝毫不小。

    2.1 FeignClientFactoryBean#getTarget()

    源码如下:

    <T> T getTarget() {
    		// 从容器获取FeignClientFactory实例
    		FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
    				: applicationContext.getBean(FeignClientFactory.class);
    		// 获取feign的建造器
    		Feign.Builder builder = feign(feignClientFactory);
    		// 处理url等参数
    		if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {
    
    			if (LOG.isInfoEnabled()) {
    				LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
    			}
    			if (!name.startsWith("http")) {
    				url = "http://" + name;
    			}
    			else {
    				url = name;
    			}
    			url += cleanPath();
    			return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));
    		}
    		if (StringUtils.hasText(url) && !url.startsWith("http")) {
    			url = "http://" + url;
    		}
    
    		// 处理url
    		String url = this.url + cleanPath();
    		// 通过工厂获取client实例
    		Client client = getOptional(feignClientFactory, Client.class);
    		// 生成client
    		if (client != null) {
    			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();
    			}
    			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
    				// not load balancing because we have a url,
    				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
    				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
    			}
    			
    			builder.client(client);
    		}
    
    		// 应用自定义参数
    		applyBuildCustomizers(feignClientFactory, builder);
    
    		// 获取Targeter实例
    		Targeter targeter = get(feignClientFactory, Targeter.class);
    		// 返回解析,使用动态代理绑定MethodHandler,最终返回代理对象
    		return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    以上的核心步骤其实就是以下几步:

    1. 从容器中获取FeignClientFactory 实例;
    2. 依据FeignClientFactory 实例生成Feign.Builder实例;
    3. 拼接有效的url
    4. 获取client
    5. 处理自定义构建参数
    6. 获取Targeter实例
    7. 动态代理获取代理对象

    本文主要关注的是第6、7步。

    2.2 获取Targeter实例

    首先,Targeter有两个实现类:DefaultTargeterFeignCircuitBreakerTargeter
    默认是从容器中直接获取到DefaultTargeter。如果使用了断路器,则会获取到 FeignCircuitBreakerTargeter

    默认实现如下:

    	@Override
    	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
    			Target.HardCodedTarget<T> target) {
    		return feign.target(target);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中 Feign.Builder#target(...) 如下:

    @Override
    public <T> T target(Target<T> target) {
        return this.build().newInstance(target);
    }
    
    • 1
    • 2
    • 3
    • 4

    而这里的build()方法,则会生成一个ReflectiveFeign 实例。
    在这里插入图片描述
    使用创建的ReflectiveFeign 实例调用newInstance(...)

    2.3 ReflectiveFeign#newInstance(…)

    在这里插入图片描述
    分行去分析本部分代码。

    Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = 
    		this.targetToHandlersByName.apply(target, requestContext);
    
    • 1
    • 2

    映射Method 和 新生成MethodHandler为Map:

    public Map<Method, InvocationHandlerFactory.MethodHandler> apply(Target target, C requestContext) {
                Map<Method, InvocationHandlerFactory.MethodHandler> result = new LinkedHashMap();
                // contract解析校验元数据
                List<MethodMetadata> metadataList = this.contract.parseAndValidateMetadata(target.type());
                Iterator var5 = metadataList.iterator();
    
                while(var5.hasNext()) {
                    MethodMetadata md = (MethodMetadata)var5.next();
                    // 获取方法,其实就是接口中的方法封装成了多个MethodMetadata
                    Method method = md.method();
                    if (method.getDeclaringClass() != Object.class) {
                    	// 创建MethodHandler
                        InvocationHandlerFactory.MethodHandler handler = this.createMethodHandler(target, md, requestContext);
                        // map 映射method 和 handler
                        result.put(method, handler);
                    }
                }
    
    			// 处理默认方法
                Method[] var10 = target.type().getMethods();
                int var11 = var10.length;
    
                for(int var12 = 0; var12 < var11; ++var12) {
                    Method method = var10[var12];
                    if (Util.isDefault(method)) {
                        InvocationHandlerFactory.MethodHandler handler = new DefaultMethodHandler(method);
                        result.put(method, handler);
                    }
                }
    
                return result;
            }
    
    
    
    		// 创建MethodHandler,默认是SynchronousMethodHandler类型的
            private InvocationHandlerFactory.MethodHandler createMethodHandler(Target<?> target, MethodMetadata md, C requestContext) {
                return md.isIgnored() ? (args) -> {
                    throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
                } : this.factory.create(target, md, requestContext);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    Method和MethodHandler的映射结果如下:
    可以看到Method是自己定义的FeignClient接口中的一个方法。Handler是SynchronousMethodHandler的实例。
    在这里插入图片描述
    随后将这个Map简单校验后,透传到InvocationHandlerdispatch属性:
    在这里插入图片描述

    2.4 生成代理对象

    T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), 
    new Class[]{target.type()}, handler);
    
    • 1
    • 2

    以接口维度,生成对应接口的代理对象,并绑定 2.3 小节中生成的handler。
    在这里插入图片描述

    三、动态代理原理全流程梳理

    以生成以下接口的代理为例。

    @FeignClient(name = "helloFeignClient", url = "http://localhost:10080")
    public interface HelloFeignClient {
    
        @PostMapping("/hello/post")
        HelloResponse postHello(@RequestBody HelloRequest helloRequest);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    附录

    附1:本系列文章链接

    SpringCloud原理-OpenFeign篇(一、Hello OpenFeign项目示例)
    SpringCloud原理-OpenFeign篇(二、OpenFeign包扫描和FeignClient的注册原理)
    SpringCloud原理-OpenFeign篇(三、FeignClient的动态代理原理)

  • 相关阅读:
    电压放大器如何选型号和参数配置
    vue echarts 镂空饼图配置
    高校云原生安全该如何构建?
    《Java 并发编程实战》—— Java内存模型:看Java如何解决可见性和有序性问题
    微信小程序——常用组件的属性介绍
    Java开发备战 - JavaSe(基础篇)
    hadoop宕机的处理方法
    移动WEB开发之rem布局--苏宁首页案例制作(技术方案1)
    [ArcPy百科]第一节:何为arcpy
    责任链模式(Chain Of Responsibility)
  • 原文地址:https://blog.csdn.net/FBB360JAVA/article/details/134534065