SpringBoot源码解析(四)PropertySource

上篇文章分析了ApplicationStartingEvent事件的处理,我们回到SpringApplication.run()方法的流程中

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
        ........
        ........
        ........
    }

可以看到try之前的代码我们都已经分析过了,接下来我们看下try模块中的第一行代码,将启动参数args传给了DefaultApplicationArguments类的构造方法

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

我们将通过对这行代码的分析,引出一个比较重要的概念:PropertySource

先看下DefaultApplicationArguments类的结构

public class DefaultApplicationArguments implements ApplicationArguments {
    private final DefaultApplicationArguments.Source source;
    private final String[] args;

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

内部类Source是一个PropertySource的派生类,继承结构如图
在这里插入图片描述
PropertySource可以看作是一组属性的集合,name是这组属性的组名,source是属性的载体,可以是Map,可以是List,也可以是复杂的自定义类型,总而言之就是存储了一系列属性,具体用什么存储,子类自己去决定

public abstract class PropertySource<T> {
	......
    protected final String name;
    protected final T source;

后面我们会看到很多PropertySource的派生类,存储系统属性、系统环境变量、配置文件属性等等
下图列举了一些常用的派生类
在这里插入图片描述
不同的子类可能使用了不同的存储结构,也会对PropertySource做一些扩展,比如EnumerablePropertySource

public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
	......
    ......
    public abstract String[] getPropertyNames();
}

从名字来看,它是可枚举的PropertySource,提供了一个获取所有属性名的方法,由于它也没有确定属性存储的结构,所以如何枚举属性,也是交给子类去实现

我们看一个它的派生类MapPropertySource

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
	......
	......
    public String[] getPropertyNames() {
        return StringUtils.toStringArray(((Map)this.source).keySet());
    }
}

它通过类型参数指定了属性的存储结构为Map,所以遍历属性名自然就是获取map的keySet了

后续的流程会见到各种各样的PropertySource,可以简单地把他们看作一组属性的容器,根据属性的来源、特点、用途等,用不同的容器来存储

回到SpringBoot的源码来,我们这里遇到的PropertySource是一个内部类,它继承自SimpleCommandLinePropertySource ,这个类又继承了CommandLinePropertySource,并指定了属性的载体类型为CommandLineArgs,在构造函数中对系统的启动参数做了解析

private static class Source extends SimpleCommandLinePropertySource {
    Source(String[] args) {
        super(args);
    }
    ......
}
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {

    public SimpleCommandLinePropertySource(String... args) {
        super((new SimpleCommandLineArgsParser()).parse(args));
    }

先看下CommandLineArgs的结构,有一个Map和一个List

class CommandLineArgs {
    private final Map<String, List<String>> optionArgs = new HashMap();
    private final List<String> nonOptionArgs = new ArrayList();

解析的方法parse,简而言之,就是遍历启动参数,如果是 - -开头,再根据=号切割成key-value,添加到map,map的value也是一个List,如果是- -开头的参数,可以用同一个参数名传递多个值,不同值存储在参数名对应的List中
如果没有- -开头,直接添加到List

public CommandLineArgs parse(String... args) {
        CommandLineArgs commandLineArgs = new CommandLineArgs();
        String[] var3 = args;
        int var4 = args.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String arg = var3[var5];
            if (arg.startsWith("--")) {
                String optionText = arg.substring(2, arg.length());
                String optionValue = null;
                String optionName;
                if (optionText.contains("=")) {
                    optionName = optionText.substring(0, optionText.indexOf(61));
                    optionValue = optionText.substring(optionText.indexOf(61) + 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;
    }

两个add方法

    public void addOptionArg(String optionName, @Nullable String optionValue) {
        if (!this.optionArgs.containsKey(optionName)) {
            this.optionArgs.put(optionName, new ArrayList());
        }
        if (optionValue != null) {
            ((List)this.optionArgs.get(optionName)).add(optionValue);
        }
    }
    public void addNonOptionArg(String value) {
        this.nonOptionArgs.add(value);
    }

然后在父类的构造方法中,指定了组名为commandLineArgs

    public CommandLinePropertySource(T source) {
        super("commandLineArgs", source);
    }

总结来说,new出来的DefaultApplicationArguments就是用来存储启动参数,也即命令行参数

比如通过 java -jar 命令指定的参数,会传递给启动函数的args中,再转交给DefaultApplicationArguments来存储

debug验证一下,修改SpringBoot应用的启动方法

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        System.out.println("======命令行参数=======");
        Stream.of(args).forEach(System.out::println);
        System.out.println("======命令行参数=======");
        
        SpringApplication.run(Application.class, args);
    }
}

通过java -jar 指令,添加参数 - -test.param=11111,从日志里看到main方法接收到参数
在这里插入图片描述
最终被解析成如下结构
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值