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

2019-11-25  本文已影响0人  海蟾子_null

前言

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

  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的源码及原理有一些了解。

上一篇下一篇

猜你喜欢

热点阅读