源码解读

Spring源码3:封装命令行参数DefaultApplicat

2019-07-01  本文已影响0人  ygxing

上篇回顾

上一篇发布启动事件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内部有两个成员变量

//命令行参数解析类
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的加载顺序和属性替换

上一篇 下一篇

猜你喜欢

热点阅读