# 《RPC手撸专栏》第fix-01章:修复服务消费者读取配置优先级的问题

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客1:https://binghe001.github.io (opens new window)
博客2:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

大家好,我是冰河~~

在写《RPC手撸专栏》的过程中,针对专栏版本的代码,在书写的过程中,会提前埋一些坑进去,使各位星球的小伙伴在调试代码的过程中,能够自己去发现问题,并且分析问题,最好也能够自己解决问题。经过自己发现问题->分析问题->解决问题的过程,能够提升大家对于RPC框架源码的参与过程,更重要的是,能够不断提升大家自己发现问题、分析问题和解决问题的能力,这种能够力才是程序员最核心的竞争力。

# 一、问题描述

本章要解决什么问题呢?

服务消费者在读取配置文件的设计中,在设计会采取优先级的模式来读取配置的值,优先级的顺序为:字段@RpcReference注解属性>yml文件的配置>@RpcReference注解默认属性,但是在实际测试过程中,会出现:服务消费者yml文件会覆盖@RpcReference注解属性。

# 二、问题分析

这个问题是如何产生的呢?

在服务消费者端,基于Spring整合服务消费者时,bhrpc-consumer-spring工程下,基于Spring解析@RpcReference注解属性的逻辑会优先于bhrpc-spring-boot-starter-consumer工程对yml文件解析的执行,在原本的实现逻辑中,bhrpc-spring-boot-starter-consumer工程对yml文件解析的值会覆盖bhrpc-consumer-spring工程基于Spring解析@RpcReference注解属性的值。

存在问题时,相关代码如下所示。

(1)setApplicationContext()方法

setApplicationContext()方法的源码详见:bhrpc-consumer-spring工程下的io.binghe.rpc.consumer.spring.RpcConsumerPostProcessor#setApplicationContext(),如下所示。

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext;
}
1
2
3
4

(2)postProcessBeanFactory()方法

postProcessBeanFactory()方法的源码详见:bhrpc-consumer-spring工程下的io.binghe.rpc.consumer.spring.RpcConsumerPostProcessor#postProcessBeanFactory(),如下所示。

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
        String beanClassName = beanDefinition.getBeanClassName();
        if (beanClassName != null) {
            Class<?> clazz = ClassUtils.resolveClassName(beanClassName, this.classLoader);
            ReflectionUtils.doWithFields(clazz, this::parseRpcReference);
        }
    }

    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
    this.rpcRefBeanDefinitions.forEach((beanName, beanDefinition) -> {
        if (context.containsBean(beanName)) {
            throw new IllegalArgumentException("spring context already has a bean named " + beanName);
        }
        registry.registerBeanDefinition(beanName, rpcRefBeanDefinitions.get(beanName));
        logger.info("registered RpcReferenceBean {} success.", beanName);
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(3)rpcClient()方法

rpcClient()方法的源码详见:bhrpc-spring-boot-starter-consumer工程下的io.binghe.rpc.spring.boot.consumer.starter.SpringBootConsumerAutoConfiguration#rpcClient(),如下所示。

@Bean
public RpcClient rpcClient(final SpringBootConsumerConfig springBootConsumerConfig){
    return new RpcClient(各个参数值);
}
1
2
3
4

在执行的过程中,SpringBootConsumerAutoConfiguration类下的rpcClient()方法会在RpcConsumerPostProcessor类的postProcessBeanFactory()方法之后运行,就会导致服务消费者服务yml文件中的值覆盖掉解析的@RpcReference注解中的值。

# 三、问题解决

问题该如何解决呢?

解决问题的总体思路就是,在bhrpc-consumer-spring工程下的io.binghe.rpc.consumer.spring.RpcConsumerPostProcessor#setApplicationContext()方法中保存ApplicationContext对象,在SpringBootConsumerAutoConfiguration类中解析yml文件时,优先读取ApplicationContext对象中存储的RpcReferenceBean对象中的值,如果RpcReferenceBean对象中的值为null或者RpcReferenceBean对象中的值是@RpcReference注解的默认值,并且yml文件中配置了对应的值,则再使用yml文件中的值覆盖掉解析的@RpcReference注解的值。

具体的解决步骤如下所示。

(1)新增RpcConsumerSpringContext类

RpcConsumerSpringContext类的源码详见:bhrpc-consumer-spring工程下的io.binghe.rpc.consumer.spring.context.RpcConsumerSpringContext,如下所示。

public class RpcConsumerSpringContext {
    private ApplicationContext context;
    private RpcConsumerSpringContext(){

    }
    private static class Holder{
        private static final RpcConsumerSpringContext INSTANCE = new RpcConsumerSpringContext();
    }
    public static RpcConsumerSpringContext getInstance(){
        return Holder.INSTANCE;
    }
    public ApplicationContext getContext() {
        return context;
    }
    public void setContext(ApplicationContext context) {
        this.context = context;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

(2)修改setApplicationContext()方法

setApplicationContext()方法的源码详见:bhrpc-consumer-spring工程下的io.binghe.rpc.consumer.spring.RpcConsumerPostProcessor#setApplicationContext(),在setApplicationContext()方法中使用RpcConsumerSpringContext类存储ApplicationContext对象,如下所示。

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext;
    RpcConsumerSpringContext.getInstance().setContext(applicationContext);
}
1
2
3
4
5

(3)新增getRpcReferenceBean()方法

getRpcReferenceBean()方法的源码详见:bhrpc-spring-boot-starter-consumer工程下的io.binghe.rpc.spring.boot.consumer.starter.SpringBootConsumerAutoConfiguration#getRpcReferenceBean(),如下所示。

private RpcReferenceBean getRpcReferenceBean(final SpringBootConsumerConfig springBootConsumerConfig){
    ApplicationContext context = RpcConsumerSpringContext.getInstance().getContext();
    RpcReferenceBean referenceBean = context.getBean(RpcReferenceBean.class);
    //###############解析referenceBean中的值,如果为null或者为false则读取yml文件中的值==============
    //###############解析referenceBean中的值,如果是@RpcReference注解中属性的默认值,并且yml文件中配置了对应的属性值,则读取yaml文件中的值#############
}

1
2
3
4
5
6
7

(4)新增parseRpcClient()方法

parseRpcClient()方法源码详见:bhrpc-spring-boot-starter-consumer工程下的io.binghe.rpc.spring.boot.consumer.starter.SpringBootConsumerAutoConfiguration#parseRpcClient(),如下所示。

private RpcClient parseRpcClient(final SpringBootConsumerConfig springBootConsumerConfig){
    RpcReferenceBean rpcReferenceBean = getRpcReferenceBean(springBootConsumerConfig);
    rpcReferenceBean.init();
    return rpcReferenceBean.getRpcClient();
}
1
2
3
4
5

(5)修改rpcClient()方法

rpcClient()方法的源码详见:bhrpc-spring-boot-starter-consumer工程下的io.binghe.rpc.spring.boot.consumer.starter.SpringBootConsumerAutoConfiguration#rpcClient(),在rpcClient()方法中,直接调用parseRpcClient()方法返回数据。

@Bean
public RpcClient rpcClient(final SpringBootConsumerConfig springBootConsumerConfig){
    return parseRpcClient(springBootConsumerConfig);
}
1
2
3
4

经过上述步骤的修改后,问题解决。

# 四、问题总结

修改完问题不总结下怎么行?

我们自己手写的RPC框架不是一蹴而就的,它是一个不断优化和不断调整的过程,冰河也会将这些调整的过程整理好分享给各位星球的小伙伴。

总之,我们写的RPC框架正在一步步实现它该有的功能。

最后,我想说的是:学习《RPC手撸专栏》一定要塌下心来,一步一个脚印,动手实践,认真思考,遇到不懂的问题,可以直接到星球发布主题进行提问。一定要记住:纸上得来终觉浅,绝知此事要躬行的道理。否则,一味的CP,或者光看不练,不仅失去了学习的意义,到头来更是一无所获。

好了,本章就到这里吧,我是冰河,我们下一章见~~

# 五、关于星球

大家可以加入 冰河技术 知识星球,和星球小伙伴们一起学习《SpringCloud Alibaba实战》专栏和《RPC手撸专栏》,冰河技术知识星球的《RPC手撸专栏》是个连载大几十篇的专栏(目前已更新几十大篇章,110+篇文章,110+工程源码,120+源码Tag分支,真正的企业级、分布式、高并发、高性能、高可用,可扩展的RPC框架,仍在持续更新)。

另外,星球中《企业级大规模分布式调度系统》和《企业级大规模分布式IM系统》也已经提升日程,期待你的加入,与星球小伙伴一起开发企业级中间件项目,一起提升硬核技术!

# 星球提供的服务

冰河整理了星球提供的一些服务,如下所示。

加入星球,你将获得:

1.学习从零开始手撸可用于实际场景的高性能RPC框架项目

2.学习SpringCloud Alibaba实战项目—从零开发微服务项目

3.学习高并发、大流量业务场景的解决方案,体验大厂真正的高并发、大流量的业务场景

4.学习进大厂必备技能:性能调优、并发编程、分布式、微服务、框架源码、中间件开发、项目实战

5.提供站点 https://binghe.gitcode.host 所有学习内容的指导、帮助

6.GitHub:https://github.com/binghe001/BingheGuide - 非常有价值的技术资料仓库,包括冰河所有的博客开放案例代码

7.提供技术问题、系统架构、学习成长、晋升答辩等各项内容的回答

8.定期的整理和分享出各类专属星球的技术小册、电子书、编程视频、PDF文件

9.定期组织技术直播分享,传道、授业、解惑,指导阶段瓶颈突破技巧

# 如何加入星球

加入星球:扫描优惠券二维码即可加入星球。

sa-2022-04-21-007

  • 扫码 :通过扫描优惠券二维码加入星球。
  • 链接 :打开链接 http://m6z.cn/6aeFbs (opens new window) 加入星球。
  • 回复 :在公众号 冰河技术 回复 星球 领取优惠券加入星球。

特别提醒: 苹果用户进圈或续费,请加微信 hacker_binghe 扫二维码,或者去公众号 冰河技术 回复 星球 扫二维码加入星球。

好了,今天就到这儿吧,我是冰河,我们下期见~~

# 写在最后

如果你觉得冰河写的还不错,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发、分布式、微服务、大数据、互联网和云原生技术,「 冰河技术 」微信公众号更新了大量技术专题,每一篇技术文章干货满满!不少读者已经通过阅读「 冰河技术 」微信公众号文章,吊打面试官,成功跳槽到大厂;也有不少读者实现了技术上的飞跃,成为公司的技术骨干!如果你也想像他们一样提升自己的能力,实现技术能力的飞跃,进大厂,升职加薪,那就关注「 冰河技术 」微信公众号吧,每天更新超硬核技术干货,让你对如何提升技术能力不再迷茫!

# 加群交流

本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “冰河” 微信(hacker_binghe),备注:学习加群

冰河微信

# 公众号

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

公众号:冰河技术

# 星球

加入星球 冰河技术 (opens new window),可以获得本站点所有学习内容的指导与帮助。如果你遇到不能独立解决的问题,也可以添加冰河的微信:hacker_binghe, 我们一起沟通交流。另外,在星球中不只能学到实用的硬核技术,还能学习实战项目

关注 冰河技术 (opens new window)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术