使用过Spring Boot,咱们都知道经过java -jar能够快速启动Spring Boot项目。同时,也能够经过在执行jar -jar时传递参数来进行配置。本文带你们系统的了解一下Spring Boot命令行参数相关的功能及相关源码分析。java
启动Spring Boot项目时,咱们能够经过以下方式传递参数:spring
java -jar xxx.jar --server.port=8081
默认状况下Spring Boot使用8080端口,经过上述参数将其修改成8081端口,并且经过命令行传递的参数具备更高的优先级,会覆盖同名的其余配置参数。数组
启动Spring Boot项目时传递参数,有三种参数形式:微信
选项参数,上面的示例即是选项参数的使用方法,经过“–-server.port”来设置应用程序的端口。基本格式为“--name=value”(“--”为连续两个减号)。其配置做用等价于在application.properties中配置的server.port=8081。app
非选项参数的使用示例以下:ide
java -jar xxx.jar abc def
上述示例中,“abc”和“def”即是非选项参数。spring-boot
系统参数,该参数会被设置到系统变量中,使用示例以下:源码分析
java -jar -Dserver.port=8081 xxx.jar
选项参数和非选项参数都可以经过ApplicationArguments接口获取,具体获取方法直接在使用参数的类中注入该接口便可。this
@RestController public class ArgumentsController { @Resource private ApplicationArguments arguments; }
经过ApplicationArguments接口提供的方法便可得到对应的参数。关于该接口后面会详细讲解。.net
另外,选项参数,也能够直接经过@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接口得到相关参数,下面看一下具体的使用示例:
@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的相关源码解析完成。
原文连接:《Spring Boot启动命令参数详解及源码分析》
CSDN学院:《Spring Boot 视频教程全家桶》