SPRING BOOT启动命令参数及源码详析
前言
使用过SpringBoot,我们都知道通过java-jar可以快速启动SpringBoot项目。同时,也可以通过在执行jar-jar时传递参数来进行配置。本文带大家系统的了解一下SpringBoot命令行参数相关的功能及相关源码分析。
命令行参数使用
启动SpringBoot项目时,我们可以通过如下方式传递参数:
java-jarxxx.jar--server.port=8081
默认情况下SpringBoot使用8080端口,通过上述参数将其修改为8081端口,而且通过命令行传递的参数具有更高的优先级,会覆盖同名的其他配置参数。
启动SpringBoot项目时传递参数,有三种参数形式:
- 选项参数
- 非选项参数
- 系统参数
选项参数,上面的示例便是选项参数的使用方法,通过“–-server.port”来设置应用程序的端口。基本格式为“–name=value”(“–”为连续两个减号)。其配置作用等价于在application.properties中配置的server.port=8081。
非选项参数的使用示例如下:
java-jarxxx.jarabcdef
上述示例中,“abc”和“def”便是非选项参数。
系统参数,该参数会被设置到系统变量中,使用示例如下:
java-jar-Dserver.port=8081xxx.jar
参数值的获取
选项参数和非选项参数均可以通过ApplicationArguments接口获取,具体获取方法直接在使用参数的类中注入该接口即可。
@RestController publicclassArgumentsController{ @Resource privateApplicationArgumentsarguments; }
通过ApplicationArguments接口提供的方法即可获得对应的参数。关于该接口后面会详细讲解。
另外,选项参数,也可以直接通过@Value在类中获取,如下:
@RestController publicclassParamController{ @Value("${server.port}") privateStringserverPort; }
系统参数可以通过java.lang.System提供的方法获取:
StringsystemServerPort=System.getProperty("server.port");
参数值的区别
关于参数值区别,重点看选项参数和系统参数。通过上面的示例我们已经发现使用选项参数时,参数在命令中是位于xxx.jar之后传递的,而系统参数是紧随java-jar之后。
如果不按照该顺序进行执行,比如使用如下方式使用选项参数:
java-jar--server.port=8081xxx.jar
则会抛出如下异常:
Unrecognizedoption:--server.port=8081
Error:CouldnotcreatetheJavaVirtualMachine.
Error:Afatalexceptionhasoccurred.Programwillexit.
如果将系统参数放在jar包后面,问题会更严重。会出现可以正常启动,但参数无法生效。这也是为什么有时候明明传递了参数但是却未生效,那很可能是因为把参数的位置写错了。
这个错误是最坑的,所以一定谨记:通过-D传递系统参数时,务必放置在待执行的jar包之前。
另外一个重要的不同是:通过@Value形式可以获得系统参数和选项参数,但通过System.getProperty方法只能获得系统参数。
ApplicationArguments解析
上面提到了可以通过注入ApplicationArguments接口获得相关参数,下面看一下具体的使用示例:
@RestController publicclassArgumentsController{ @Resource privateApplicationArgumentsarguments; @GetMapping("/args") publicStringgetArgs(){ 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接口中封装了启动时原始参数的数组、选项参数的列表、非选项参数的列表以及选项参数获得和检验。相关源码如下:
publicinterfaceApplicationArguments{ /** *原始参数数组(未经过处理的参数) */ String[]getSourceArgs(); /** *选项参数名称 */ SetgetOptionNames(); /** *根据名称校验是否包含选项参数 */ booleancontainsOption(Stringname); /** *根据名称获得选项参数 */ List getOptionValues(Stringname); /** *获取非选项参数列表 */ List getNonOptionArgs(); }
命令行参数的解析
上面直接使用了ApplicationArguments的注入和方法,那么它的对象是何时被创建,何时被注入Spring容器的?
在执行SpringApplication的run方法的过程中会获得传入的参数,并封装为ApplicationArguments对象。相关源代码如下:
publicConfigurableApplicationContextrun(String...args){ try{ ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args); //... prepareContext(context,environment,listeners,//... }catch(Throwableex){ //... } returncontext; }
在上述代码中,通过创建一个它的实现类DefaultApplicationArguments来完成命令行参数的解析。
DefaultApplicationArguments部分代码如下:
publicclassDefaultApplicationArgumentsimplementsApplicationArguments{ privatefinalSourcesource; privatefinalString[]args; publicDefaultApplicationArguments(String...args){ Assert.notNull(args,"Argsmustnotbenull"); this.source=newSource(args); this.args=args; } //... @Override publicListgetOptionValues(Stringname){ List values=this.source.getOptionValues(name); return(values!=null)?Collections.unmodifiableList(values):null; } privatestaticclassSourceextendsSimpleCommandLinePropertySource{ Source(String[]args){ super(args); } //... } }
通过构造方法,将args赋值给成员变量args,其中接口ApplicationArguments中getSourceArgs方法的实现在该类中便是返回args值。
针对成员变量Source(内部类)的设置,在创建Source对象时调用了其父类SimpleCommandLinePropertySource的构造方法:
publicSimpleCommandLinePropertySource(String...args){ super(newSimpleCommandLineArgsParser().parse(args)); }
在该方法中创建了真正的解析器SimpleCommandLineArgsParser并调用其parse方法对参数进行解析。
classSimpleCommandLineArgsParser{ publicCommandLineArgsparse(String...args){ CommandLineArgscommandLineArgs=newCommandLineArgs(); for(Stringarg:args){ //--开头的选参数解析 if(arg.startsWith("--")){ //获得key=value或key值 StringoptionText=arg.substring(2,arg.length()); StringoptionName; StringoptionValue=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())){ thrownewIllegalArgumentException("Invalidargumentsyntax:"+arg); } //封装入CommandLineArgs commandLineArgs.addOptionArg(optionName,optionValue); }else{ commandLineArgs.addNonOptionArg(arg); } } returncommandLineArgs; } }
上述解析规则比较简单,就是根据“–”和“=”来区分和解析不同的参数类型。
通过上面的方法创建了ApplicationArguments的实现类的对象,但此刻还并未注入Spring容器,注入Spring容器是依旧是通过上述SpringApplication#run方法中调用的prepareContext方法来完成的。相关代码如下:
privatevoidprepareContext(ConfigurableApplicationContextcontext,ConfigurableEnvironmentenvironment, SpringApplicationRunListenerslisteners,ApplicationArgumentsapplicationArguments,BannerprintedBanner){ //... ConfigurableListableBeanFactorybeanFactory=context.getBeanFactory(); //通过beanFactory将ApplicationArguments的对象注入Spring容器 beanFactory.registerSingleton("springApplicationArguments",applicationArguments); //... }
至此,关于SpringBoot中ApplicationArguments的相关源码解析完成。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。