Spring源碼3:封裝命令行參數DefaultApplicationArguments

上篇回顧

上一篇發布啟動事件ApplicationStartingEvent, 我們分析springboot發布了啟動事件, 其執行步驟如下

  1. 首先調用getRunListeners()方法, 獲得一個SpringApplicationRunListeners對象,
    • SpringApplicationRunListeners的成員變量listeners是通過getSpringFactoriesInstances()方法獲取的SpringApplicationRunListener子類列表
    • 當前只能獲取EventPublishingRunListener,
  2. 調用SpringApplicationRunListeners對象的starting()方法, 發布SpringApplication啟動事件
    • 內部EventPublishingRunListener#starting()方法
    • 最終調用SimpleApplicationEventMulticaster#multicastEvent()方法
    • 發布了ApplicationStartingEvent事件, 最后執行每個監聽器的onApplicationEvent方法
  3. 對ApplicationStartingEvent事件感興趣的監聽器
    • LoggingApplicationListener 日志監聽器,配置日志
    • BackgroundPreinitializer 后臺初始化器, 多線程加載耗時任務
    • DelegatingApplicationListener 代理監聽器, 繼續發布事件
    • LiquibaseServiceLocatorApplicationListener 將liquibas替換為可以和spring配合工作的版本

目錄

1. DefaultApplicationArguments
2. Source
????2.1 PropertySource
????2.2 CommandLinePropertySource
????2.3 SimpleCommandLinePropertySource
3. SimpleCommandLineArgsParser
4. 總結

1.DefaultApplicationArguments

這一步的主要作用是處理啟動類main函數的參數, 將其封裝為一個DefaultApplicationArguments對象, 為prepareEnvironment()提供參數

public class SpringApplication {
    //run方法
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            //本文重點
            //封裝命令行參數, 傳入參數為main函數的參數args
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
        //...
    }
}

默認的應用參數, args保存原本的命令行參數, source成員變量保存解析后的命令行參數

//默認Application命令行參數類
public class DefaultApplicationArguments implements ApplicationArguments {

    //命令行參數解析封裝類
    private final Source source;

    //命令行參數
    private final String[] args;

    public DefaultApplicationArguments(String[] args) {
        Assert.notNull(args, "Args must not be null");
        //保存解析后的命令行參數
        this.source = new Source(args);
        //保存原命令行有參數
        this.args = args;
    }

    //私有靜態內部類 Source類
    private static class Source extends SimpleCommandLinePropertySource {

        Source(String[] args) {
            //調用父類方法
            //解析并封裝命令行參數
            super(args);
        }

        @Override
        public List<String> getNonOptionArgs() {
            //調用父類方法
            return super.getNonOptionArgs();
        }
        @Override
        public List<String> getOptionValues(String name) {
            //調用父類方法
            return super.getOptionValues(name);
        }
    }
}

2. Source

Spring支持多種形式的配置, 并按照固定的順序加載, 實現了資源的合理覆蓋, Source類繼承了CommandLinePropertySource, 用來保存命令行參數, 類繼承關系圖:

2.1 PropertySource

所有的資源都繼承了抽象類PropertySource, PropertySource以一個鍵值對的形式來保存spring配置的屬性, 提供了獲取屬性, 屬性名, containsProperty等基本方法

public abstract class PropertySource<T> {

    protected final Log logger = LogFactory.getLog(getClass());

    //屬性名稱
    protected final String name;

    //屬性值
    protected final T source;
}
2.2 CommandLinePropertySource

抽象命令行參數類, 定義了兩種類型的命令行參數key

  1. 以--開頭的命令行參數, 保存到key為commandLineArgs的PropertySource中
  2. 不以--開頭的命令行參數, 保存到key為nonOptionArgs的PropertySource中
public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {

    //命令行參數key
    //保存所有的命令行參數
    public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";

    public static final String DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME = "nonOptionArgs";

    private String nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME;

    public CommandLinePropertySource(T source) {
        //調用父類SimpleCommandLinePropertySource(String name, String[] args)構造函數
        //返回一個name為commandLineArgs
        //值為source的PropertySource對象
        super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
    }
}
2.3 SimpleCommandLinePropertySource

構造函數中, 調用了SimpleCommandLineArgsParser#parse, 用來解析啟動類main函數中傳入的參數args

public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {

    //調用父類CommandLinePropertySource(T source)構造函數
    public SimpleCommandLinePropertySource(String... args) {
        //SimpleCommandLineArgsParser#parse解析命令行參數
        super(new SimpleCommandLineArgsParser().parse(args));
    }
}

3 SimpleCommandLineArgsParser

命令行解析類, 返回一個CommandLineArgs對象, CommandLineArgs內部有兩個成員變量

  • optionArgs, HashMap類型, 用來保存以--開頭的屬性
  • nonOptionArgs, ArrayList類型, 用來保存沒有以--開頭的屬性
//命令行參數解析類
class SimpleCommandLineArgsParser {

    //解析方法
    public CommandLineArgs parse(String... args) {
        CommandLineArgs commandLineArgs = new CommandLineArgs();
        for (String arg : args) {
            if (arg.startsWith("--")) {
                //如果是"--"開頭的字符串
                //取出"--"之后的字符串optionText
                String optionText = arg.substring(2, arg.length());
                String optionName;
                String optionValue = null;
                if (optionText.contains("=")) {
                    //如果字符串optionText包含"="
                    //使用"="分割
                    //"="前面的字符串作為optionName
                    optionName = optionText.substring(0, optionText.indexOf('='));
                    //"="后面的字符串作為optionValue
                    optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
                }
                else {
                    //字符串不包含"="
                    //整個字符串optionText作為optionName
                    //此時optionValue為null
                    optionName = optionText;
                }
                if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
                    throw new IllegalArgumentException("Invalid argument syntax: " + arg);
                }
                //最后將name和value放入commandLineArgs的optionArgs中
                //name為HashMap的key, value為Hashmap的value
                commandLineArgs.addOptionArg(optionName, optionValue);
            }
            else {
                //如果不是"--"開頭的
                //直接放入到commandLineArgs的nonOptionArgs ArrayList中
                commandLineArgs.addNonOptionArg(arg);
            }
        }
        return commandLineArgs;
    }
}

4. 總結

這一步的主要作用是處理啟動類main函數的參數, 將其封裝為一個DefaultApplicationArguments對象, 為接下來準備環境提供參數

項目中傳入的命令行參數為:

--spring.profiles.active=test
--server.port=9080
--user.name=yangx
--test
yanggx

處理之后返回的DefaultApplicationArguments對象

{
    "args":[
        "--spring.profiles.active=test",
        "--server.port=9080",
        "--user.name=yangx",
        "--test",
        "yanggx"
    ],
    //Source extends SimpleCommandLinePropertySource
    "source": {//CommandLinePropertySource最終實現PropertySource
        {
            "name":"commandLineArgs",//CommandLineArgs
            "source":{
                "optionArgs":[//Map
                    {"key":"test","value":null},
                    {"key":"server.port","value":"9080"},
                    {"key":"user.name","value":"yangx"},
                    {"key":"spring.profiles.active","value":"test"},
                ],
                "nonOptionArgs":[ //List
                    "yanggx"
                ],
            }
        }
    }    
}

下一篇

我們將會在下一篇prepareEnvironment()準備環境, 研究DefaultApplicationArgument的使用, 以及spring各個PropertySource的加載順序和屬性替換

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容