Spring Boot启动命令参数详解及源码分析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Spring Boot启动命令参数详解及源码分析

image.png使用过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接口获取,具体获取方法直接在使用参数的类中注入该接口即可。






@RestControllerpublic class ArgumentsController {  @Resource  private ApplicationArguments arguments;}

通过ApplicationArguments接口提供的方法即可获得对应的参数。关于该接口后面会详细讲解。

另外,选项参数,也可以直接通过@Value在类中获取,如下:







@RestControllerpublic 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接口获得相关参数,下面看一下具体的使用示例:























       @RestControllerpublicclassArgumentsController {
 @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的相关源码解析完成。

目录
相关文章
|
6月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
145 0
|
6月前
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
164 0
|
5天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
17 2
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
53 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
58 2
|
5月前
|
运维 Java 关系型数据库
Spring运维之boot项目bean属性的绑定读取与校验
Spring运维之boot项目bean属性的绑定读取与校验
53 2
|
5月前
|
存储 运维 Java
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
63 2
|
5月前
|
Java Maven
springboot项目打jar包后,如何部署到服务器
springboot项目打jar包后,如何部署到服务器
423 1
|
5月前
|
XML 运维 Java
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
54 1
|
5月前
springboot2.4.5使用pagehelper分页插件
springboot2.4.5使用pagehelper分页插件
152 0