目录
一、介绍二、通过应用程序参数获取配置1. 通过bean获取应用程序参数2. 通过@Value注解获取三、源码解读 - 封装应用程序参数1. DefaultApplicationArguments2. Source类3. SimpleCommandLinePropertySource4. SimpleCommandLineArgsParser5. CommandLinePropertySource6. PropertySource四、源码解读 - 为什么可以通过@Value注解获取参数配置五、源码解读 - 将应用程序参数注册到IOC容器六、总结一、介绍
使用springboot开发的同学们,都一定会从配置文件application.yml
中读取配置。比如我们常常会在上传文件的功能中,把文件的保存路径写在配置文件中,然后在代码中通过@Value()
注解从配置文件读取对应的配置,如下所示:
在配置文件中定义文件路径
file: location: /data/files
在代码中获取保存路径
(相关资料图)
@Component public class upload { @Value("${file.location}") private String fileLocation; // 文件路径/data/files public void upload(File file) { // 将文件保存到fileLocation中。 } }
这种读取配置的方式非常方便,但是有一个让人抓狂的缺点:
在多人协作开发的情况下,同事A在配置文件中修改file.location
的值为E:\\
后将代码提交到git仓库,这时同事B把最新代码拉下来后由于他的电脑中不存在E盘
导致该功能出现bug,很多同学不嫌麻烦,每次拉下最新代码后都会把这种配置重新修改以适合自己电脑的要求。
幸运的是,springboot在读取配置参数方面为我们提供了多种方式,并且不同方式之间存在优先级差异,如命令行配置的优先级大于配置文件的优先级。如下图为springboot官方的描述
从上图可知,命令行配置是在非单元测试环境下优先级最高的。
在我们通过java -jar
命令启动项目时,添加额外的参数,就可以解决上面提及的多人协作开发的问题了。
二、通过应用程序参数获取配置
当我们使用IDEA启动springboot项目时,可以对项目的启动设置命令行参数,命令行参数的格式为--name=value
或 --name
,如下所示
1. 通过bean获取应用程序参数
启动项目后,我们从IOC容器中获取命令行参数对应的beanspringApplicationArguments
,再从该bean中就可以获取到我们在命令行中配置的参数了。
springboot悄悄替我们向IOC容器中注册一个ApplicationArguments
类型的bean,beanName为springApplicationArguments
,该bean中保存着我们设置的应用程序参数。
@SpringBootApplication public class ArgumentApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args); // 获取应用程序参数 ApplicationArguments applicationArguments =(ApplicationArguments)applicationContext .getBean("springApplicationArguments"); // 获取命令行中name的配置 Listname = applicationArguments.getOptionValues("name"); System.out.println(name); } }
输出如下所示
当然,你也可以通过@Autowired
的方式在类里注入ApplicationArguments
实例来获取其中的配置。
2. 通过@Value注解获取
当然我们更常用的方式是通过@Value
注解来获取,如下所示
新建一个ComponentA,并用@Component
注解标注为springBean,然后为其定义@Value
标注的成员变量name
@Component public class ComponentA { @Value("${name}") private String name; public ComponentA() { } public String getName() { return name; } }
项目启动后,从IOC容器中获取ComponentA
,并调用getName()
方法来验证name
的值
@SpringBootApplication public class ArgumentApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args); // 从配置文件中获取 ComponentA componentA = (ComponentA) applicationContext.getBean("componentA"); System.out.println(componentA.getName()); } }
输出,结果符合预期
三、源码解读 - 封装应用程序参数
springboot通过启动类的
main()方法
接收命令行中以--
定义的应用程序参数,将参数按照不同类型以Map
和> List
保存并封装到CommandLineArgs
对象中,然后以name="commandLineArgs",source=CommandLineArgs对象
将其封装到Source
中,而Source
为ApplicationArguments
内部属性,springboot将ApplicationArguments
注入IOC容器。
从上面的例子中我们发现,springboot把我们配置的命令行参数封装到ApplicationArguments
了,而ApplicationArguments
又被springboot注册到IOC容器中,其对应的beanName为"springApplicationArguments"
,下面我们通过分析源码来逐步解开它是如何操作的。
首先,大家在写springboot启动类时,有没有注意到其中main()
方法的参数String[] args
,如下所示
@SpringBootApplication public class ArgumentApplication { public static void main(String[] args) { SpringApplication.run(ArgumentApplication.class, args); } }
但这个参数想必有很多同学不知道它是干嘛用的,它的作用就是用来接收启动命令中设置的--name=key
参数,比如java -jarApplication.jar --name=key
,我们可以通过断点进行验证
在源码run()
方法中我们追踪args
这个参数的调用链如下:
public ConfigurableApplicationContext run(String... args) { // ... SpringApplicationRunListeners listeners = getRunListeners(args); // ... ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // ... }
从源码可以看出,参数args
可以被用来获取运行监听器和 构造应用参数,因此我们把注意力放在构造应用参数上来。
1. DefaultApplicationArguments
看一下该类的结构,从它的构造方法我们得知,该类是把我们传入的--
应用程序参数封装成一个Source
对象,同时也保存一份原始的args
参数,当我们需要获取参数时,都是调用Source
对象提供的方法获取的,因此Source这个类尤其关键,我们需要弄清楚它是如何分析应用程序参数并将其封装到Source
中的。
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; } // ... private static class Source extends SimpleCommandLinePropertySource { Source(String[] args) { super(args); } // ... } }
2. Source类
Source类是DefaultApplicationArguments
的内部类,上面已经展示其具体实现的源码,它的构造函数就是把接收的应用程序参数传递给父类的构造函数。
下面我们看一下他的UML图
由于Source的构造函数直接把参数args
交给其父类的构造函数,而Source本身没有多余的处理,因此我们直接进入其父类SimpleCommandLinePropertySource
。
3. SimpleCommandLinePropertySource
public class SimpleCommandLinePropertySource extends CommandLinePropertySource{ public SimpleCommandLinePropertySource(String... args) { super(new SimpleCommandLineArgsParser().parse(args)); } public SimpleCommandLinePropertySource(String name, String[] args) { super(name, new SimpleCommandLineArgsParser().parse(args)); } }
在这个类中,又是直接调用父类的构造方法,且没有自身的实现。但不同的,这里将我们设置的应用程序进行转换成CommandLineArgs
对象交给父类构造函数。
它是怎么分析我们传入的应用程序参数的,又将其转换成什么样的结构呢?
4. SimpleCommandLineArgsParser
该类只有一个静态方法parse()
,从命名也可以看出,该类的功能就是对命令行参数提供简单的转换器。
class SimpleCommandLineArgsParser { public CommandLineArgs parse(String... args) { CommandLineArgs commandLineArgs = new CommandLineArgs(); for (String arg : args) { // 以 -- 开头的应用程序参数 if (arg.startsWith("--")) { String optionText = arg.substring(2); String optionName; String optionValue = null; int indexOfEqualsSign = optionText.indexOf("="); if (indexOfEqualsSign > -1) { // --key=value这种形式的参数 optionName = optionText.substring(0, indexOfEqualsSign); optionValue = optionText.substring(indexOfEqualsSign + 1); } else { // --key这种形式的参数 optionName = optionText; } if (optionName.isEmpty()) { throw new IllegalArgumentException("Invalid argument syntax: " + arg); } commandLineArgs.addOptionArg(optionName, optionValue); } else { // 不以 -- 开头的应用程序参数 commandLineArgs.addNonOptionArg(arg); } } return commandLineArgs; } }
从源码得知,应用程序参数的转换过程非常简单,就是根据--
和 =
进行字符串裁剪,然后将这些参数封装到CommandLineArgs
里。而在CommandLineArgs
中用不同的字段来保存不同类型的应用程序参数。如下
class CommandLineArgs { // 保存 --key=value 和 --key这两种类型的应用程序参数 private final Map> optionArgs = new HashMap<>(); // 保存 key 这一种类型的应用程序参数 private final List nonOptionArgs = new ArrayList<>(); }
回到上一节SimpleCommandLinePropertySource
,它的构造函数就是将应用程序参数转换为CommandLineArgs
然后交给父类构造函数,那下面我们看其父类CommandLinePropertySource
。
5. CommandLinePropertySource
在CommandLinePropertySource
中,我们主要看其构造函数。
public abstract class CommandLinePropertySourceextends EnumerablePropertySource { public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; public CommandLinePropertySource(T source) { super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source); } }
很显然,又是直接调用父类的构造函数,而且向其父类构造函数传入的是"commandLineArgs"
字符串 和 CommandLineArgs
对象。那我们继续,进入父类EnumerablePropertySource
,然后又将这两个参数继续传递给父类PropertySource
public abstract class EnumerablePropertySourceextends PropertySource { public EnumerablePropertySource(String name, T source) { super(name, source); } }
6. PropertySource
通过前面一系列对父类构造函数的调用,最终将name初始化为"commandLineArgs"
字符串 ,将source初始化为 CommandLineArgs
对象。
public abstract class PropertySource{ protected final String name; protected final T 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; } }
四、源码解读 - 为什么可以通过@Value注解获取参数配置
在前面我们将应用程序参数封装到ApplicationArguments
对象中后,springboot又将这些应用程序参数添加到environment
对象中,并且对已存在的配置进行覆盖,因此与配置文件中定义的参数类似,都可以通过@Value注解获取。
在下面的源码中,主要表达的是应用程序参数在各个方法调用中的传递,最关键的部分我们要看configurePropertySources()
方法。该方法将应用程序参数配置到运行环境environment
。
public ConfigurableApplicationContext run(String... args) { // ... ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // ... } private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); } protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { // ... configurePropertySources(environment, args); // ... } // 将应用程序设置到environment对象中,与配置文件中的参数处于同一environment对象中,因此可以通过@Value注解获取参数配置 protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast); if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { // 环境中已存在相同的配置,则进行覆盖 PropertySource> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
五、源码解读 - 将应用程序参数注册到IOC容器
在前面的章节,我们通过源码分析得出结论,springboot将应用程序参数封装到ApplicationArguments
和运行环境Environment
中。接下来我们看它是如何注册到IOC容器的。
public ConfigurableApplicationContext run(String... args) { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // ... prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // ... } private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // ... ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); // ... }
springboot将应用程序参数ApplicationArguments
直接通过beanFactory.registerSingleton()
方法手动地注册到IOC容器中,beanName为springApplicationArguments
。
六、总结
springboot将我们配置的命令行参数封装到ApplicationArguments
,并使用"springApplicationArguments"
作为beanName将其注册到IOC容器。
设置应用程序参数时,符合要求的设置为:--key=value
、--key
以及 key
。可以通过@Value
注解直接获取应用程序参数。可以通过@Autowired
依赖注入一个ApplicationArguments
实例来读取应用程序参数。
到此这篇关于springboot加载命令行参数ApplicationArguments的实现的文章就介绍到这了,更多相关springboot加载命令行参数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
关键词:
(责任编辑:黄俊飞)推荐内容
- springboot加载命令行参数ApplicationArg
- 嘿_我真的好想你是什么歌 这首歌的完整
- 当前头条:院校代号四位数 查询2021_院
- 这项国内顶级赛事在汉连赛三天_天天快资讯
- 公共|太湖文化交流研讨会召开 “走读伯
- 加热后的橙汁为什么苦?-世界观速讯
- 环球新资讯:自制肉桂油的方法_自制肉桂
- 9个复韵母有哪些_9个复韵母有哪些字母
- 潮州冻蟹_关于潮州冻蟹介绍
- 安全专家称苏丹的政变企图带有俄罗斯的印
- 莱斯信息科创板IPO审核状态更新为“注册
- 股票行情快报:沃顿科技(000920)4月21
- 快播:墨西哥宣布将前总统专机卖给塔吉克
- 烧烤火了,淄博财政收入增速由负转正、一
- 股票行情快报:首开股份(600376)4月21
- 注意!盘龙药业将于5月19日召开股东大会
- 快资讯:中超-罗马里奥破门费莱尼扳平
- 今日筱素清生活中老公年龄(筱素清老公是
- “冰墩墩”概念股元隆雅图2022年净利润增
- 文学名家变身阅读推广人:“读好书让人有
- 新民主主义革命新道路_新民主主义革命新
- 有哪些好用的股票小工具?炒股必备的几个
- maya图霸天f_maya玛雅 图霸天下
- 珀莱雅实控人及高管拟合计减持不超3.039%
- 智能电网重大科技产业化工程“十二五”专
- 观天下!工信部:今年计划在全国范围内再
- 双色球23045期聪爷说彩推荐:三区分析-播报
- 全球最新:欧拉积分
- 大宗交易:瑞德智能成交681.3万元,折价1
- 泰达投资10.7亿元超短期融资券将兑付 利
- 世界热文:放置食品车什么时候出 公测上
- 大动作!运河“C位”这样更新
- 磁性套索工具怎么抠图步骤图解_磁性套索
- 浙江美大今年一季度净利润约1.01亿元
- 全球微速讯:每天搬运2吨以上货物,敬礼
- 蓝图基金会:带大山里的孩子看大海 全球
- 现在自我调节是人工智能的控制标准 每日
- 2023寒地龙药发展论坛开幕:发挥寒地龙药
- 拓展社区养老服务功能(健康焦点) 天天
- 前沿资讯!今年一季度安徽出口增幅居长三
- 辽中区气象局发布大风蓝色预警【Ⅳ级/一
- 每日热议!冲突持续,首都传出激烈枪声
- 天天观焦点:探访浙江“未来社区”
- 山东路桥董秘回复:公司可转债将于2023年
- 当前关注:双台子储气库双向输气管道工程
- 【当前热闻】农业农村部部署2023年大豆玉
- 理樱书香 畅阅美好-全球消息
- CBA最水外援!季后赛生死战得0分,他把首
- 当前速讯:2021-22赛季欧冠半决赛,曼城
- 热点评!Sopapillas 的历史
- 上海老庙铂金多少钱一克(2023年04月21日
- 【环球时快讯】全国首单绿色及能源保供双
- 「世界说」欧媒:美西方霸权“失效” 中
- 网页截图怎么截全屏_网页截图怎么截
- 环球实时:九名工人身陷滩涂 民警联合多
- 布朗:我们整个赛季都注重防守 注重在攻
- 今天最新消息 浙江第800例造血干细胞捐
- 环球观天下!远兴能源(000683)4月20日
- 世界快讯:五一假期热门城市出炉!有你要
- 乌克兰开始威胁中国了!-每日报道
- 新民主主义革命新道路_新民主主义革命新
- 有哪些好用的股票小工具?炒股必备的几个
- maya图霸天f_maya玛雅 图霸天下
- 珀莱雅实控人及高管拟合计减持不超3.039%
- 智能电网重大科技产业化工程“十二五”专
- 观天下!工信部:今年计划在全国范围内再
- 双色球23045期聪爷说彩推荐:三区分析-播报
- 全球最新:欧拉积分
- 大宗交易:瑞德智能成交681.3万元,折价1
- 泰达投资10.7亿元超短期融资券将兑付 利
- 世界热文:放置食品车什么时候出 公测上
- 大动作!运河“C位”这样更新
- 磁性套索工具怎么抠图步骤图解_磁性套索
- 浙江美大今年一季度净利润约1.01亿元
- 全球微速讯:每天搬运2吨以上货物,敬礼
- 蓝图基金会:带大山里的孩子看大海 全球
- 现在自我调节是人工智能的控制标准 每日
- 2023寒地龙药发展论坛开幕:发挥寒地龙药
- 拓展社区养老服务功能(健康焦点) 天天
- 前沿资讯!今年一季度安徽出口增幅居长三
- 辽中区气象局发布大风蓝色预警【Ⅳ级/一
- 每日热议!冲突持续,首都传出激烈枪声
- 天天观焦点:探访浙江“未来社区”
- 山东路桥董秘回复:公司可转债将于2023年
- 当前关注:双台子储气库双向输气管道工程
- 【当前热闻】农业农村部部署2023年大豆玉
- 理樱书香 畅阅美好-全球消息
- CBA最水外援!季后赛生死战得0分,他把首
- 当前速讯:2021-22赛季欧冠半决赛,曼城
- 热点评!Sopapillas 的历史
- 上海老庙铂金多少钱一克(2023年04月21日
- 【环球时快讯】全国首单绿色及能源保供双
- 「世界说」欧媒:美西方霸权“失效” 中
- 网页截图怎么截全屏_网页截图怎么截
- 环球实时:九名工人身陷滩涂 民警联合多
- 布朗:我们整个赛季都注重防守 注重在攻
- 今天最新消息 浙江第800例造血干细胞捐
- 环球观天下!远兴能源(000683)4月20日
- 世界快讯:五一假期热门城市出炉!有你要
- 乌克兰开始威胁中国了!-每日报道
- 安徽理工大学新增2个新工科本科专业-环球
- 风李峤古诗拼音_风李峤 带拼音
- 卫生间装风暖浴霸和电暖气片哪种好_电暖
- 拒绝不文明!这些事别做!
- 刚刚,高120米的“星舰”,首次升空后不
- 世界微动态丨年薪10万 VS 年薪100万:
- 天天速看:电商如何细化运营?分享1对1问
- 焦点报道:长沙市雨花区枫树山东南海小学
- 天天热头条丨寻找最红车主播!2023湖南车
- 天天实时:买它还是Model Y?小鹏G6亮相
- 外汇百晓生:黄金短线空单止盈,中期2045
- 现在博士严重过剩,高校的就业岗位缺乏,
- 开口两侧绿化过高 挡住开车视线_聚看点
- 男团成员被发现在家中死亡,年仅25岁!警
- 长峰医院股票被停牌
- 研究机构惠誉警告:全球恐面临20年来最严
- 不用华为方案也无妨!比亚迪地平线达成合
- 环球焦点!第十届全省民族运动会在江华开
- 商务部:1-3月全国实际使用外资同比增长4
- 社保个人交好还是挂单位交好?交的钱怎么