欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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,看名字还是蛮明显的吧
SpringBoot读取配置文件源码探究

    @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
SpringBoot读取配置文件源码探究

    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.namedefault_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结尾的用另一个
    SpringBoot读取配置文件源码探究
    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;
            }
        }
    }
  • 继续深入
    SpringBoot读取配置文件源码探究
    SpringBoot读取配置文件源码探究
  • 可以看到,总算把我配合文件的内容给读到了,然后放入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

SpringBoot读取配置文件源码探究

  • 这样之后返回去看,你就会看到它存入了mappropertysource属性资源,在之后就会被用上了

1.3. 总结

  • 我通过一步步的代码跟踪,解析了springboot读取application.yml的整个流程,代码虽然贴的比较多,但可以让初学者也可以跟着这个步骤完整的理解一遍,代码中的关键步骤我都用中文标明了,其它没标注部分不是这章的重点,想研究的自行研究