SpringBoot读取配置文件源码探究
程序员文章站
2022-08-28 18:18:29
1. SpringBoot读取配置文件源码探究 1.1. 概览 springboot的源码是再原来的Spring源码上又包了一层,看过spring源码都知道,当我们从入口debug进去的时候,原来的Spring源码都集中在 方法,SpringBoot的主要运行步骤,基本都包含在这个方法里了,而这个方 ......
1. springboot读取配置文件源码探究
1.1. 概览
- springboot的源码是再原来的spring源码上又包了一层,看过spring源码都知道,当我们从入口debug进去的时候,原来的spring源码都集中在
refreshcontext
方法,springboot的主要运行步骤,基本都包含在这个方法里了,而这个方法就是我们运行springboot的主函数springapplication.run(application.class, args);
经过几步后到达的
public configurableapplicationcontext run(string... args) { stopwatch stopwatch = new stopwatch(); stopwatch.start(); configurableapplicationcontext context = null; failureanalyzers analyzers = null; configureheadlessproperty(); springapplicationrunlisteners listeners = getrunlisteners(args); listeners.starting(); try { applicationarguments applicationarguments = new defaultapplicationarguments( args); configurableenvironment environment = prepareenvironment(listeners, applicationarguments); banner printedbanner = printbanner(environment); context = createapplicationcontext(); analyzers = new failureanalyzers(context); preparecontext(context, environment, listeners, applicationarguments, printedbanner); refreshcontext(context); afterrefresh(context, applicationarguments); listeners.finished(context, null); stopwatch.stop(); if (this.logstartupinfo) { new startupinfologger(this.mainapplicationclass) .logstarted(getapplicationlog(), stopwatch); } return context; } catch (throwable ex) { handlerunfailure(context, listeners, analyzers, ex); throw new illegalstateexception(ex); } }
1.2. 配置读取步骤
1.2.1. prepareenvironment
- 配置读取的步骤主要就在
configurableenvironment environment = prepareenvironment(listeners, applicationarguments);
这一步,我们继续深入
private configurableenvironment prepareenvironment( springapplicationrunlisteners listeners, applicationarguments applicationarguments) { // create and configure the environment configurableenvironment environment = getorcreateenvironment(); configureenvironment(environment, applicationarguments.getsourceargs()); //主要是这步 listeners.environmentprepared(environment); if (!this.webenvironment) { environment = new environmentconverter(getclassloader()) .converttostandardenvironmentifnecessary(environment); } return environment; }
- 创建基本的环境容器后,进入
listeners.environmentprepared(environment);
通过监听器来进行环境变量的初始化,同时读取配置也是一部分工作
1.2.2. environmentprepared
- 下一步,看到对监听器进行循环处理,这里的
listeners
中,默认只有一个eventpublishrunlistener
public void environmentprepared(configurableenvironment environment) { for (springapplicationrunlistener listener : this.listeners) { listener.environmentprepared(environment); } }
- 继续如下,这一步它进行广播事件了
@override public void environmentprepared(configurableenvironment environment) { this.initialmulticaster.multicastevent(new applicationenvironmentpreparedevent( this.application, this.args, environment)); }
1.2.3. multicastevent
@override public void multicastevent(applicationevent event) { multicastevent(event, resolvedefaulteventtype(event)); }
继续,重点是invokelistener
方法,去调用监听器事件,可以想象对配置文件来讲,这就是读取配置事件了。同时监听器有很多,读取配置文件的监听器是configfileaplicationlistener
,看名字还是蛮明显的吧
@override public void multicastevent(final applicationevent event, resolvabletype eventtype) { resolvabletype type = (eventtype != null ? eventtype : resolvedefaulteventtype(event)); for (final applicationlistener<?> listener : getapplicationlisteners(event, type)) { executor executor = gettaskexecutor(); if (executor != null) { executor.execute(new runnable() { @override public void run() { invokelistener(listener, event); } }); } else { //重点 invokelistener(listener, event); } } }
继续,和上一步类似,do真正的事件了
protected void invokelistener(applicationlistener<?> listener, applicationevent event) { errorhandler errorhandler = geterrorhandler(); if (errorhandler != null) { try { doinvokelistener(listener, event); } catch (throwable err) { errorhandler.handleerror(err); } } else { //重点 doinvokelistener(listener, event); } }
private void doinvokelistener(applicationlistener listener, applicationevent event) { try { // 入口 listener.onapplicationevent(event); } catch (classcastexception ex) { string msg = ex.getmessage(); if (msg == null || matchesclasscastmessage(msg, event.getclass())) { // possibly a lambda-defined listener which we could not resolve the generic event type for // -> let's suppress the exception and just log a debug message. log logger = logfactory.getlog(getclass()); if (logger.isdebugenabled()) { logger.debug("non-matching event type for listener: " + listener, ex); } } else { throw ex; } } }
1.2.4. onapplicationevent
- 在
configfileapplicationlistener
类中继续
@override public void onapplicationevent(applicationevent event) { if (event instanceof applicationenvironmentpreparedevent) { //配置入口 onapplicationenvironmentpreparedevent( (applicationenvironmentpreparedevent) event); } if (event instanceof applicationpreparedevent) { onapplicationpreparedevent(event); } }
继续,可以看到处理器有这些,我们关注configfileapplicationlistener
private void onapplicationenvironmentpreparedevent( applicationenvironmentpreparedevent event) { list<environmentpostprocessor> postprocessors = loadpostprocessors(); postprocessors.add(this); annotationawareordercomparator.sort(postprocessors); for (environmentpostprocessor postprocessor : postprocessors) { //入口 postprocessor.postprocessenvironment(event.getenvironment(), event.getspringapplication()); } }
1.2.5. postprocessenvironment
@override public void postprocessenvironment(configurableenvironment environment, springapplication application) { //重点入口 addpropertysources(environment, application.getresourceloader()); configureignorebeaninfo(environment); bindtospringapplication(environment, application); }
继续
protected void addpropertysources(configurableenvironment environment, resourceloader resourceloader) { randomvaluepropertysource.addtoenvironment(environment); //总算看到加载入口了 new loader(environment, resourceloader).load(); }
1.2.6. load
public void load() { this.propertiesloader = new propertysourcesloader(); this.activatedprofiles = false; this.profiles = collections.aslifoqueue(new linkedlist<profile>()); this.processedprofiles = new linkedlist<profile>(); // pre-existing active profiles set via environment.setactiveprofiles() // are additional profiles and config files are allowed to add more if // they want to, so don't call addactiveprofiles() here. set<profile> initialactiveprofiles = initializeactiveprofiles(); this.profiles.addall(getunprocessedactiveprofiles(initialactiveprofiles)); if (this.profiles.isempty()) { for (string defaultprofilename : this.environment.getdefaultprofiles()) { profile defaultprofile = new profile(defaultprofilename, true); if (!this.profiles.contains(defaultprofile)) { this.profiles.add(defaultprofile); } } } // the default profile for these purposes is represented as null. we add it // last so that it is first out of the queue (active profiles will then // override any settings in the defaults when the list is reversed later). this.profiles.add(null); while (!this.profiles.isempty()) { profile profile = this.profiles.poll(); for (string location : getsearchlocations()) { if (!location.endswith("/")) { // location is a filename already, so don't search for more // filenames load(location, null, profile); } else { for (string name : getsearchnames()) { //加载入口 load(location, name, profile); } } } this.processedprofiles.add(profile); } addconfigurationproperties(this.propertiesloader.getpropertysources()); }
- 可以看到它的加载名从
getsearchnames
获取,那就看看这个方法
private set<string> getsearchnames() { if (this.environment.containsproperty(config_name_property)) { return asresolvedset(this.environment.getproperty(config_name_property), null); } return asresolvedset(configfileapplicationlistener.this.names, default_names); }
-
config_name_property
值为spring.config.name
,default_names
值为application
,所以可以看出application
这个名字就是默认的配置名了,但也可以用spring.config.name
属性来修改 其实到这一步,后面已经没难度了,可以想象,接下去应该是拼接出完整的路径,找到文件读取,还是走完流程把
继续
private void load(string location, string name, profile profile) { string group = "profile=" + ((profile != null) ? profile : ""); if (!stringutils.hastext(name)) { // try to load directly from the location loadintogroup(group, location, profile); } else { // search for a file with the given name for (string ext : this.propertiesloader.getallfileextensions()) { if (profile != null) { // try the profile-specific file loadintogroup(group, location + name + "-" + profile + "." + ext, null); for (profile processedprofile : this.processedprofiles) { if (processedprofile != null) { loadintogroup(group, location + name + "-" + processedprofile + "." + ext, profile); } } // sometimes people put "spring.profiles: dev" in // application-dev.yml (gh-340). arguably we should try and error // out on that, but we can be kind and load it anyway. loadintogroup(group, location + name + "-" + profile + "." + ext, profile); } // also try the profile-specific section (if any) of the normal file //加载重点 loadintogroup(group, location + name + "." + ext, profile); } } }
- 继续
private propertysource<?> loadintogroup(string identifier, string location, profile profile) { try { //入口 return doloadintogroup(identifier, location, profile); } catch (exception ex) { throw new illegalstateexception( "failed to load property source from location '" + location + "'", ex); } }
- do开头都是正式要干事了
private propertysource<?> doloadintogroup(string identifier, string location, profile profile) throws ioexception { resource resource = this.resourceloader.getresource(location); propertysource<?> propertysource = null; stringbuilder msg = new stringbuilder(); if (resource != null && resource.exists()) { string name = "applicationconfig: [" + location + "]"; string group = "applicationconfig: [" + identifier + "]"; // 加载入口 propertysource = this.propertiesloader.load(resource, group, name, (profile != null) ? profile.getname() : null); if (propertysource != null) { msg.append("loaded "); handleprofileproperties(propertysource); } else { msg.append("skipped (empty) "); } } else { msg.append("skipped "); } msg.append("config file "); msg.append(getresourcedescription(location, resource)); if (profile != null) { msg.append(" for profile ").append(profile); } if (resource == null || !resource.exists()) { msg.append(" resource not found"); this.logger.trace(msg); } else { this.logger.debug(msg); } return propertysource; }
- load内容,这里有两个加载器,看名字也知道了,
yml
结尾的文件肯定用yamlpropertysourceloader
才能加载,properties
结尾的用另一个
public propertysource<?> load(resource resource, string group, string name, string profile) throws ioexception { if (isfile(resource)) { string sourcename = generatepropertysourcename(name, profile); for (propertysourceloader loader : this.loaders) { if (canloadfileextension(loader, resource)) { // 干事的入口 propertysource<?> specific = loader.load(sourcename, resource, profile); addpropertysource(group, specific); return specific; } } } return null; }
- 我用的是
yml
,所以在yamlpropertysourceloader
类
@override public propertysource<?> load(string name, resource resource, string profile) throws ioexception { if (classutils.ispresent("org.yaml.snakeyaml.yaml", null)) { processor processor = new processor(resource, profile); //真正的处理类 map<string, object> source = processor.process(); if (!source.isempty()) { return new mappropertysource(name, source); } } return null; }
public map<string, object> process() { final map<string, object> result = new linkedhashmap<string, object>(); //接近了 process(new matchcallback() { @override public void process(properties properties, map<string, object> map) { result.putall(getflattenedmap(map)); } }); return result; }
1.2.7. process
protected void process(matchcallback callback) { yaml yaml = createyaml(); for (resource resource : this.resources) { //更近了 boolean found = process(callback, yaml, resource); if (this.resolutionmethod == resolutionmethod.first_found && found) { return; } } }
- 继续深入
- 可以看到,总算把我配合文件的内容给读到了,然后放入
callback
private boolean process(matchcallback callback, yaml yaml, resource resource) { int count = 0; try { if (logger.isdebugenabled()) { logger.debug("loading from yaml: " + resource); } //读取文件 reader reader = new unicodereader(resource.getinputstream()); try { for (object object : yaml.loadall(reader)) { //总算拿到了 if (object != null && process(asmap(object), callback)) { count++; if (this.resolutionmethod == resolutionmethod.first_found) { break; } } } if (logger.isdebugenabled()) { logger.debug("loaded " + count + " document" + (count > 1 ? "s" : "") + " from yaml resource: " + resource); } } finally { reader.close(); } } catch (ioexception ex) { handleprocesserror(resource, ex); } return (count > 0); }
- 结果就存入了
result
- 这样之后返回去看,你就会看到它存入了
mappropertysource
属性资源,在之后就会被用上了
1.3. 总结
- 我通过一步步的代码跟踪,解析了
springboot
读取application.yml
的整个流程,代码虽然贴的比较多,但可以让初学者也可以跟着这个步骤完整的理解一遍,代码中的关键步骤我都用中文标明了,其它没标注部分不是这章的重点,想研究的自行研究
下一篇: C# Mqtt 断线重连
推荐阅读
-
SpringBoot读取配置文件源码探究
-
springboot读取自定义配置文件时出现乱码解决方案
-
关于Springboot打成JAR包后读取外部配置文件的问题
-
springboot如何读取配置文件test.properties
-
springboot整合Nacos多环境以及项目配置文件动态读取
-
springBoot之配置文件的读取以及过滤器和拦截器的使用
-
SpringBoot2 java配置方式 Configuration和PropertySource结合读取配置文件
-
springboot读取配置文件中的参数具体步骤
-
SpringBoot 如何读取配置文件?看完十分钟就够了
-
springboot如何读取配置文件到静态工具类