使用过 Spring Boot, 我们都知道通过 java -jar 可以快速启动 Spring Boot 项目. 同时, 也可以通过在执行 jar -jar 时传递参数来进行配置. 本文带大家系统的了解一下 Spring Boot 命令行参数相关的功能及相关源码分析.
命令行参数使用
启动 Spring Boot 项目时, 我们可以通过如下方式传递参数:
java -jar xxx.jar --server.port=8081
默认情况下 Spring Boot 使用 8080 端口, 通过上述参数将其修改为 8081 端口, 而且通过命令行传递的参数具有更高的优先级, 会覆盖同名的其他配置参数.
启动 Spring Boot 项目时传递参数, 有三种参数形式:
选项参数
非选项参数
系统参数
选项参数, 上面的示例便是选项参数的使用方法, 通过 "--server.port" 来设置应用程序的端口. 基本格式为 "--name=value"("--" 为连续两个减号). 其配置作用等价于在 application.properties 中配置的 server.port=8081.
非选项参数的使用示例如下:
java -jar xxx.jar abc def
上述示例中,"abc" 和 "def" 便是非选项参数.
系统参数, 该参数会被设置到系统变量中, 使用示例如下:
java -jar -Dserver.port=8081 xxx.jar
参数值的获取
选项参数和非选项参数均可以通过 ApplicationArguments 接口获取, 具体获取方法直接在使用参数的类中注入该接口即可.
- @RestController
- public class ArgumentsController {
- @Resource
- private ApplicationArguments arguments;
- }
通过 ApplicationArguments 接口提供的方法即可获得对应的参数. 关于该接口后面会详细讲解.
另外, 选项参数, 也可以直接通过 @Value 在类中获取, 如下:
- @RestController
- public class ParamController {
- @Value("${server.port}")
- private String serverPort;
- }
系统参数可以通过 java.lang.System 提供的方法获取:
String systemServerPort = System.getProperty("server.port");
参数值的区别
关于参数值区别, 重点看选项参数和系统参数. 通过上面的示例我们已经发现使用选项参数时, 参数在命令中是位于 xxx.jar 之后传递的, 而系统参数是紧随 java -jar 之后.
如果不按照该顺序进行执行, 比如使用如下方式使用选项参数:
java -jar --server.port=8081 xxx.jar
则会抛出如下异常:
- Unrecognized option: --server.port=8081
- Error: Could not create the Java Virtual Machine.
- Error: A fatal exception has occurred. Program will exit.
如果将系统参数放在 jar 包后面, 问题会更严重. 会出现可以正常启动, 但参数无法生效. 这也是为什么有时候明明传递了参数但是却未生效, 那很可能是因为把参数的位置写错了.
这个错误是最坑的, 所以一定谨记: 通过 - D 传递系统参数时, 务必放置在待执行的 jar 包之前.
另外一个重要的不同是: 通过 @Value 形式可以获得系统参数和选项参数, 但通过 System.getProperty 方法只能获得系统参数.
ApplicationArguments 解析
上面提到了可以通过注入 ApplicationArguments 接口获得相关参数, 下面看一下具体的使用示例:
- @RestController
- public class ArgumentsController {
- @Resource
- private ApplicationArguments arguments;
- @GetMapping("/args")
- public String getArgs() {
- System.out.println("# 非选项参数数量:" + arguments.getNonOptionArgs().size());
- System.out.println("# 选项参数数量:" + arguments.getOptionNames().size());
- System.out.println("# 非选项具体参数:");
- arguments.getNonOptionArgs().forEach(System.out::println);
- System.out.println("# 选项参数具体参数:");
- arguments.getOptionNames().forEach(optionName -> {
- System.out.println("--" + optionName + "=" + arguments.getOptionValues(optionName));
- });
- return "success";
- }
- }
通过注入 ApplicationArguments 接口, 然后在方法中调用该接口的方法即可获得对应的参数信息.
ApplicationArguments 接口中封装了启动时原始参数的数组, 选项参数的列表, 非选项参数的列表以及选项参数获得和检验. 相关源码如下:
- public interface ApplicationArguments {
- /**
- * 原始参数数组 (未经过处理的参数)
- */
- String[] getSourceArgs();
- /**
- * 选项参数名称
- */
- Set<String> getOptionNames();
- /**
- * 根据名称校验是否包含选项参数
- */
- boolean containsOption(String name);
- /**
- * 根据名称获得选项参数
- */
- List<String> getOptionValues(String name);
- /**
- * 获取非选项参数列表
- */
- List<String> getNonOptionArgs();
- }
命令行参数的解析
上面直接使用了 ApplicationArguments 的注入和方法, 那么它的对象是何时被创建, 何时被注入 Spring 容器的?
在执行 SpringApplication 的 run 方法的过程中会获得传入的参数, 并封装为 ApplicationArguments 对象. 相关源代码如下:
- public ConfigurableApplicationContext run(String... args) {
- try {
- ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
- // ...
- prepareContext(context, environment, listeners, // ...
- } catch (Throwable ex) {
- // ...
- }
- return context;
- }
在上述代码中, 通过创建一个它的实现类 DefaultApplicationArguments 来完成命令行参数的解析.
DefaultApplicationArguments 部分代码如下:
- 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;
- }
- // ...
- @Override
- public List<String> getOptionValues(String name) {
- List<String> values = this.source.getOptionValues(name);
- return (values != null) ? Collections.unmodifiableList(values) : null;
- }
- private static class Source extends SimpleCommandLinePropertySource {
- Source(String[] args) {
- super(args);
- }
- // ...
- }
- }
通过构造方法, 将 args 赋值给成员变量 args, 其中接口 ApplicationArguments 中 getSourceArgs 方法的实现在该类中便是返回 args 值.
针对成员变量 Source(内部类) 的设置, 在创建 Source 对象时调用了其父类 SimpleCommandLinePropertySource 的构造方法:
- public SimpleCommandLinePropertySource(String... args) {
- super(new SimpleCommandLineArgsParser().parse(args));
- }
在该方法中创建了真正的解析器 SimpleCommandLineArgsParser 并调用其 parse 方法对参数进行解析.
- class SimpleCommandLineArgsParser {
- public CommandLineArgs parse(String... args) {
- CommandLineArgs commandLineArgs = new CommandLineArgs();
- for (String arg : args) {
- // -- 开头的选参数解析
- if (arg.startsWith("--")) {
- // 获得 key=value 或 key 值
- String optionText = arg.substring(2, arg.length());
- String optionName;
- String optionValue = null;
- // 如果是 key=value 格式则进行解析
- if (optionText.contains("=")) {
- optionName = optionText.substring(0, optionText.indexOf('='));
- optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
- } else {
- // 如果是仅有 key(--foo) 则获取其值
- optionName = optionText;
- }
- // 如果 optionName 为空或者 optionValue 不为空但 optionName 为空则抛出异常
- if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
- throw new IllegalArgumentException("Invalid argument syntax:" + arg);
- }
- // 封装入 CommandLineArgs
- commandLineArgs.addOptionArg(optionName, optionValue);
- } else {
- commandLineArgs.addNonOptionArg(arg);
- }
- }
- return commandLineArgs;
- }
- }
上述解析规则比较简单, 就是根据 "--" 和 "=" 来区分和解析不同的参数类型.
通过上面的方法创建了 ApplicationArguments 的实现类的对象, 但此刻还并未注入 Spring 容器, 注入 Spring 容器是依旧是通过上述 SpringApplication#run 方法中调用的 prepareContext 方法来完成的. 相关代码如下:
- private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
- SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
- // ...
- ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
- // 通过 beanFactory 将 ApplicationArguments 的对象注入 Spring 容器
- beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
- // ...
- }
至此关于 Spring Boot 中 ApplicationArguments 的相关源码解析完成.
来源: https://www.cnblogs.com/secbro/p/12080818.html