【OpenFeign】@FeignClient 代理对象的创建源码分析

1  前言

我们从上节 【OpenFeign】@FeignClient 注入过程源码分析 继续,来看看它代理对象的创建,以及请求的执行过程。

我们就从它的 FeignClientFactoryBean 看起,那我们这里简单回忆下它都设置了哪些属性,我简单画了个图。

这些属性不了解的话,就先看看上节哈,有详细的说明,我这里就不再啰嗦了哈。

2  源码分析

2.1  入口 FeignClientFactoryBean

看到 FactoryBean 就先看它的 getObject():

// FeignClient#getObject
@Override
public Object getObject() {
    // 调用内部 getTarget()
    return getTarget();
}

继续进去看 getTarget()方法:

/**
 * @param <T> the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
<T> T getTarget() {
    // 先拿到上下文中的 FeignContext Bean出来 这个哪来的呢?
    FeignContext context = beanFactory != null
            ? beanFactory.getBean(FeignContext.class)
            : applicationContext.getBean(FeignContext.class);
    // 拿到构建起对象 里边会设置编码器 解码器 连接器等等
    Feign.Builder builder = feign(context);
      // 如果没有设置 url 属性
    if (!StringUtils.hasText(url)) {
           // 打印下日志
        if (LOG.isInfoEnabled()) {
            LOG.info("For '" + name
                    + "' URL not provided. Will try picking an instance via load-balancing.");
        }
        // 不是 http 开头的 给你补一下
        if (!name.startsWith("http")) {
            url = "http://" + name;
        }
        else {
            url = name;
        }
        url += cleanPath();
        // 服务调用
        return (T) loadBalance(builder, context,
                new HardCodedTarget<>(type, name, url));
    }
    // 设置了 url 属性的 一样不是 http打头的 就给你补一下 看说明 openfeign 最后基本都是走的 http调用是不是
    if (StringUtils.hasText(url) && !url.startsWith("http")) {
        url = "http://" + 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();
        }
        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);
    }
    // 开始调用
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
            new HardCodedTarget<>(type, name, url));
}

可以看到大致分三步:

(1)从上下文获取到 FeignContext 对象,然后构建属于当前 FeignClient 的上下文对象

(2)从属于当前 FeignClient 的上下文对象中,根据当前 FeignClient 的配置创建 Feign 的构建器

(3)根据 URL 的是否设置,来创建相应的代理对象,落点都在 Targeter 对象的 target 方法来创建对象

那我们接下来看看这三步都做了什么。

2.2  FeignContext 的由来

关于 FeignContext 我之前单独写了一节【OpenFeign】【NamedContextFactory】深入剖析 NamedContextFactory 的原理以及使用 ,不知道的可以先看看它是干什么呢,有什么作用,以及怎么获取。主要就是针对每个 @FeignClient 做不同的配置,把它们的配置都隔离到了自己的上下文对象里来达到效果,我们这里就不再啰嗦了。

我们这里就主要看下它是怎么来的,以及它其中的配置的来源。首先,他能从上下文中获取,那么思考一下这个对象是什么跑到上下文的呢?就在 Feign 的自动装配类里,我们看一下:

有个配置列表,谁会提供这个列表呢?或者他的来源是哪里呢?这就需要回想到我们上节看到的 FeignClient 的 BeanDefinition 的注入过程了,我这里直接在上节的图里点缀一下,看红色的两根线:

我这里 debug 看了下,看配置列表会有一个默认的以及我的两个 @FeignClient 的配置:

好,这就是 FeignContext 的作用,主要是构建属于当前 FeignClient 的一个上下文对象。

2.3  Builder 构建器

那我们继续看看构建器对象的创建:

protected Builder feign(FeignContext context) {
    // 从上下文中获取日志
    FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
    // 创建日志对象
    Logger logger = loggerFactory.create(this.type);
    // 创造构建器 编码器 解码器 连接器啥的
    Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
    // 配置
    this.configureFeign(context, builder);
    return builder;
}

在默认什么都不配置的情况下,什么日志工厂、编码器、解码器,都是走的默认的配置,就是 FeignContext 创建该 Feign 的上下文对象中,注册了一些默认的配置,可以看 FeignClientsConfiguration。

2.4  代理对象创建

我们看到根据 URL 属性设置的有无,会走不同的逻辑,但是最后都是通过 Targeter 对象的 target 方法来创建,而 target 方法又是基于构建器对象里的 Feign 对象来创建的:

最后可以看到它是通过 JDK 动态代理的方式创建的。

3  小结

铁子们,我发现这个创建的过程,链路有点长啊,里边的配置很多,会根据不同的配置构建 feign,但是大体思路上我们基本可以看到首先是根据全局默认配置@EnableFeignClients和@FeignClient属性的配置,来构建当前 Feign 的上下文对象,然后从当前上下文中创建它的构造器,继而创建他的代理对象。后边的还没仔细看,不敢断言,所以就草草带过了= =,后续慢慢 debug 看完,再详细阐明,有理解不对的地方欢迎指正哈。

posted @ 2024-03-23 19:21  酷酷-  阅读(28)  评论(0编辑  收藏  举报