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的加载顺序和属性替换

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容