【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 看完,再详细阐明,有理解不对的地方欢迎指正哈。