随着公司业务线不断壮大和发展,项目种类不断增多,对于配置的统一管理和监控以及支持动态发布等一些特性变成越来越重要的事情。所以需要引入配置中心进行与公司业务流程的适配。方向选型主要是Nacos和Apollo,二者区别这边不做赘述,最终落地选择了Apollo。在信息安全要求中,包含这么一条规则,要求放在配置中心的一些关键配置,比如数据库密码,各种中间件,MQ、以及服务器地址之类的数据,需要进行加密存储,以确保安全性。
Apollo本身是没有提供加解密的特性的,所以需要我们进行一些改造。
改造的方向有两点:1应用自身进行配置解密的动作 2在Apollo-client统一实现。
基于第一点,我在springboot和Mvc项目中都分别做了实现,但是业务代码耦合相对比较严重,并且加解密的动作最好还是做一个统一的地方处理,符合一种服务化的思想。
所以基于第二点,做了如下探索和改造。
其实网络上可以搜索到对应的一些改造方式,不亚于就是在DefaultConfig的updateConfig方法中添加上自己的解密逻辑。

那么为什么要在这个地方加上解密的逻辑呢?
其实很简单,这个类是Apollo启动流程中默认注入的类,并且在两个地方有所调用
其一:初始化的时候调用
其二:远程配置中心有变更监听到配置变更消息的时候调用
所以我们在这个方法中加入解密逻辑是没有问题的。我本地也进行了对应的测试实现。
但是做完之后我们思考一个逻辑,我们在引入一个框架解决某些问题的时候,本质上应该本着不去修改它源代码的角度上去进行扩展功能的实现,这样有利于我们后期做一些框架升级的动作,因为无论随着公司人员流转调动或者历史因素,总会有一些代码是没有人维护的,所以我们应该将后期的升级成本尽可能的缩小。所以这时候基于这样的思考点,我们应该想想能不能自己基于Apollo-client做一层封装,去实现加解密的功能呢?答案自然是可以的。
这时候我们应该从研究一个客户端在启动并且调用Apollo-client获取配置的流程性上去考虑。
基于这种流程,我们在IDE工具中进行对应的debug调试和源码跟踪,其实可以比较方便的跟踪到各个调用类。Apollo源码中提供了很多demo类给我们进行测试,这边不做过多赘述。


我们可以看出来,从我的ApolloConfigDemo,会调用到ConfigService中,然后再调用到DefaultConfigManager中去。这是在我的 ApolloConfigDemo的调用逻辑,那么平时我们Apollo接入spring的时候,也是走这样的逻辑吗?我们可以验证一下:
这边我直接说结论,最终Apollo利用了spring框架特性,在initializePropertySources方法里一样会走ConfigService的调用逻辑。



他通过了ApolloInjector,对configManager的实现类实现了注入并且赋值到了m_configManager变量上。到这里我们可能还没什么逻辑。但是我们从调用链路继续往下走,会发现ApolloInjector在各个地方都进行了接口实现类的调用。

所以这时候我们可以先猜测一下,ApolloInjector这个类的作用,应该是约定和绑定了初始化的时候应该加载什么类型的ConfigManager、ConfigFactoryManager、ConfigFactory 的实现类。所以这时候我们可以开始研究一下ApolloInjector这个类了。
当我们点进去之后我们看到这样的代码:

ServiceBootstrap是Apollo自己做的一层封装,有点类加载器的味道。点进去之后我们可以看到
他的loadFirst方法只会加载第一个实例。并且我们看到这样一个有趣的异常信息:
"No implementation defined in /META-INF/services/%s, please check whether the file exists and has the right implementation class!",
很明显,这是一个SPI机制注入的东西。那么我们去看看它SPI注入了什么类。

对于这个接口,他其实默认使用SPI注入了DefaultInjector,再对DefaultInjector做了一层封装,包装在了ApolloInjector中去调用,其实最后还是会走到DefaultInjector的逻辑中。那么DefaultInjector又做了哪些事呢?

我们可以看到,它在初始化的时候调用了ApolloModule(),去初始化了ApolloModule这个类。而ApolloModule这个类是做什么用的?

到这边就一目了然了,Apollo利用了SPI注入了DefaultConfig,然后又初始化了一个静态的内部类ApolloModule,这个类定义和封装好了Apollo之后在client启动链路上需要使用到的各个组件的默认实现。
所以了解到这里,已经差不多了。很明显,我们想要的改造是DefaultConfig。那么我们需要怎么改?
第一:我们在ide中新建一个module,封装Apollo-client组件。
第二:在我们的module中,我们写一个自己的Injector类,去替换掉默认的DefaultInjector.我们也一样的使用SPI机制注入进去,因为Apollo默认只会拿第一个,所以一定会拿到我们自己module中的实现类。自己的Injector类可以直接将DefaultInjector拷贝过来。
第三:在调用链路上,真正调用到DefaultConfig的类其实是DefaultConfigFactory

所以我们重写 DefaultConfigFactory以及DefaultConfig,将我们自己重写的DefaultConfigFactory调用到我们自己重写的DefaultConfig,并且在我们自己重写的DefaultConfig中的updateConfig()方法实现解密的逻辑即可。
最后,我们需要在第一步我们自己的Injector类中,将DefaultConfigFactory替换成我们自己的ConfigFactory,就能够实现解密的动作了。
主要实现的东西就这么几个

最后我们可以进行一些测试:在工程中引入自己的jar包。

在我改造的代码中我做了如下打印,将获取到加密的值打印出来,并且将解密的值也打印出来
