你好,我是Guide。此刻我正在回家的路上,祝大家小年快乐,幸福安康!
今天咱们来动手写代码实现一个简易的配置中心,这对于我们理解配置中心的原理很有帮助!
下面是正文。
配置中心是我们平常使用微服务架构时重要的一个模块,常用的配置中心组件也比较多,从早期的SpringCloudConfig,到Disconf、Apollo、Nacos等,它们支持的功能、产品的性能以及给用户的体验也各有不同。
虽然说功能上有不少差异,但是它们解决的最核心问题,无疑是配置文件修改后的实时生效,有时候在搬砖之余我就在好奇实时生效是如何实现的、如果让我来设计又会怎么去实现,于是这几天抽出了点空闲时间,摸鱼摸出了个简易版的单机配置中心,先来看看效果:
之所以说是简易版本,首先是因为实现的核心功能就只有配置修改后实时生效,并且代码的实现也非常简单,一共只用了8个类就实现了这个核心功能,看一下代码的结构,核心类就是core包中的这8个类:
看到这是不是有点好奇,虽说是低配版,就凭这么几个类也能实现一个配置中心?那么先看一下总体的设计流程,下面我们再细说代码。
代码简要说明下面对8个核心类进行一下简要说明并贴出核心代码,有的类中代码比较长,可能对手机浏览的小伙伴不是非常友好,建议收藏后以后电脑浏览器打开(骗波收藏,计划通!)。另外,我已经把项目的全部代码上传到了git,有需要的小伙伴可以移步文末获取地址。
1、ScanRunnerScanRunner实现了CommandLineRunner接口,可以保证它在springboot启动最后执行,这样就能确保其他的Bean已经实例化结束并被放入了容器中。至于为什么起名叫ScanRunner,是因为这里要实现的主要就是扫描类相关功能。先看一下代码:
ComponentpublicclassScanRunnerimplementsCommandLineRunner{Overridepublicvoidrun(String...args)throwsException{doScanComponent();}privatevoiddoScanComponent(){StringrootPath=this.getClass().getResource("/").getPath();ListStringfileList=FileScanner.findFileByType(rootPath,null,FileScanner.TYPE_CLASS);doFilter(rootPath,fileList);EnvInitializer.init();}privatevoiddoFilter(StringrootPath,ListStringfileList){rootPath=FileScanner.getRealRootPath(rootPath);for(StringfullPath:fileList){StringshortName=fullPath.replace(rootPath,"").replace(FileScanner.TYPE_CLASS,"");StringpackageFileName=shortName.replaceAll(Matcher.quoteReplacement(File.separator),"\\.");try{Classclazz=Class.forName(packageFileName);if(clazz.isAnnotationPresent(Component.class)clazz.isAnnotationPresent(Controller.class)
clazz.isAnnotationPresent(Service.class)){VariablePool.add(clazz);}}catch(ClassNotFoundExceptione){e.printStackTrace();}}}}
真正实现文件扫描功能是调用的FileScanner,它的实现我们后面具体再说,在功能上它能够根据文件后缀名扫描某一目录下的全部文件,这里首先扫描出了target目录下全部以.class结尾的文件:
扫描到全部class文件后,就可以利用类的全限定名获取到类的Class对象,下一步是调用doFilter方法对类进行过滤。这里我们暂时仅考虑通过
Value注解的方式注入配置文件中属性值的方式,那么下一个问题来了,什么类中的Value注解会生效呢?答案是通过Component、Controller、Service这些注解交给spring容器管理的类。综上,我们通过这些注解再次进行过滤出符合条件的类,找到后交给VariablePool对变量进行处理。
2、FileScannerFileScanner是扫描文件的工具类,它可以根据文件后缀名筛选出需要的某个类型的文件,除了在ScanRunner中用它扫描了class文件外,在后面的逻辑中还会用它扫描yml文件。下面,看一下FileScanner中实现的文件扫描的具体代码:
publicclassFileScanner{publicstaticfinalStringTYPE_CLASS=".class";publicstaticfinalStringTYPE_YML=".yml";publicstaticListStringfindFileByType(StringrootPath,ListStringfileList,StringfileType){if(fileList==null){fileList=newArrayList();}FilerootFile=newFile(rootPath);if(!rootFile.isDirectory()){addFile(rootFile.getPath(),fileList,fileType);}else{String[]subFileList=rootFile.list();for(Stringfile:subFileList){StringsubFilePath=rootPath+"\\"+file;FilesubFile=newFile(subFilePath);if(!subFile.isDirectory()){addFile(subFile.getPath(),fileList,fileType);}else{findFileByType(subFilePath,fileList,fileType);}}}returnfileList;}privatestaticvoidaddFile(StringfileName,ListStringfileList,StringfileType){if(fileName.endsWith(fileType)){fileList.add(fileName);}}publicstaticStringgetRealRootPath(StringrootPath){if(System.getProperty("os.name").startsWith("Windows")rootPath.startsWith("/")){rootPath=rootPath.substring(1);rootPath=rootPath.replaceAll("/",Matcher.quoteReplacement(File.separator));}returnrootPath;}}
查找文件的逻辑很简单,就是在给定的根目录rootPath下,循环遍历每一个目录,对找到的文件再进行后缀名的比对,如果符合条件就加到返回的文件名列表中。
至于下面的这个getRealRootPath方法,是因为在windows环境下,获取到项目的运行目录是这样的:
/F:/Workspace/hermit-purple-config/target/classes/
而class文件名是这样的:
F:\Workspace\hermit-purple-config\target\classes\