美文网首页
springboot源码走读之二----命令行参数封装与上下文环

springboot源码走读之二----命令行参数封装与上下文环

作者: 海蟾子_null | 来源:发表于2019-11-25 23:31 被阅读0次

    前言

    本文主要是接上文源码走读之一

      try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                configureIgnoreBeanInfo(environment);
                Banner printedBanner = printBanner(environment);
                context = createApplicationContext();
                exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                refreshContext(context);
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                }
                listeners.started(context);
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }
    

    这段代码而来。主要走读

    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    

    这两步代码。
    首先来看第一步,参数封装。

    ApplicationArguments生成

    ApplicationArguments的主要处理逻辑在于SimpleCommandLineArgsParserparse方法。方法实现如下:

    public CommandLineArgs parse(String... args) {
            CommandLineArgs commandLineArgs = new CommandLineArgs();
            for (String arg : args) {
                if (arg.startsWith("--")) {
                    String optionText = arg.substring(2, arg.length());
                    String optionName;
                    String optionValue = null;
                    if (optionText.contains("=")) {
                        optionName = optionText.substring(0, optionText.indexOf('='));
                        optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
                    }
                    else {
                        optionName = optionText;
                    }
                    if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
                        throw new IllegalArgumentException("Invalid argument syntax: " + arg);
                    }
                    commandLineArgs.addOptionArg(optionName, optionValue);
                }
                else {
                    commandLineArgs.addNonOptionArg(arg);
                }
            }
            return commandLineArgs;
        }
    

    这里springboot命令行参数包括两种,一种是以--开头的参数,类似key/value形式的。如--profile,--log.level等。一种是直接以value形式的,如foo等。具体参数的意义,跟业务相关。

    最后这个commandLineArgs被放到什么地方了呢?看如下代码:

    public DefaultApplicationArguments(String... args) {
            Assert.notNull(args, "Args must not be null");
            this.source = new Source(args);
            this.args = args;
        }
    

    我们进一步进入Source中,找到如下代码:

    public PropertySource(String name, T source) {
            Assert.hasText(name, "Property source name must contain at least one character");
            Assert.notNull(source, "Property source must not be null");
            this.name = name;
            this.source = source;
        }
    

    这里初始化一个PropertySource,name为:commandLineArgs,value就为上面初始化的CommandLineArgs
    这里,只要业务能获取到applicationArguments,就能获取到我们从命令行中传入的参数。
    通常,我们会通过environment来获取参数。例子如下:

     ApplicationContext ctx = SpringApplication.run(StudyApplication.class);
     System.out.println(ctx.getEnvironment().getProperty("foo"));
    

    这里就会在控制台中打印出key为“foo”的值。那么,environment是如何构造的呢?这就到了接下来要看的代码中了,即environment的构造。

    Environment的构造

    Environment的主要代码如下:

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments) {
            // Create and configure the environment
            // 创建一个environment对象。如果是`SERVLET`,则new一个StandardServletEnvironment,如果是`REACTIVE`,则new一个StandardReactiveWebEnvironment,否则初始化一个StandardEnvironment。初始化对象的时候,创建一些java标准的环境参数,如`systemEnvironment`和`systemProperties`及对应容器特有的环境参数,如`servletConfigInitParams`和`servletContextInitParams`
            ConfigurableEnvironment environment = getOrCreateEnvironment();
    //该步骤为设置一些默认的converter和formatter及设置命令行参数到environment中,还有设置应用程序中设置的profile及active profile。
            configureEnvironment(environment, applicationArguments.getSourceArgs());
    //配置configurationProperties的值
            ConfigurationPropertySources.attach(environment);
    //触发环境准备完成事件,事件的消费者在springboot的代码中,有如下地方:
    // FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener【重要,加载配置文件的地方,下面会专门讲】,LoggingApplicationListener 日志系统,WebEnvironmentPropertySourceInitializer
            listeners.environmentPrepared(environment);
    //绑定环境到springapplication上。spring.main配置【具体作用,以后用到的时候在做说明】
            bindToSpringApplication(environment);
                  //如果不是自定义环境,则转换之。
            if (!this.isCustomEnvironment) {
                environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                        deduceEnvironmentClass());
            }
            ConfigurationPropertySources.attach(environment);
            return environment;
        }
    

    到此,环境准备完成。
    总结代码,环境准备主要做了如下几件事情:
    1.确定active环境。
    2.确定环境参数,jvm参数及os参数。
    3.加载properties,yml配置文件。
    4.确定spring.main配置。

    configureIgnoreBeanInfo

    这里设置spring.beaninfo.ignore的值,若没有指定,则设置为true。具体用处,有待进一步走读代码

    printBanner

    这一步是打印banner图,即


    springboot banner图

    接下来,真正进入springboot的重点部分了,跟spring真正结合的地方来了。
    即如下代码

    //创建spring context
    context = createApplicationContext();
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    //【重点中的重点】调用spring的refresh方法,进行spring的bean的加载。
    refreshContext(context);
    

    下一篇我们将详细介绍spring的context的初始化。这里需要对spring的源码及原理有一些了解。

    相关文章

      网友评论

          本文标题:springboot源码走读之二----命令行参数封装与上下文环

          本文链接:https://www.haomeiwen.com/subject/nffnictx.html