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


    使用过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的相关源码解析完成。

    原文链接:《Spring Boot启动命令参数详解及源码分析

    Spring技术视频

    CSDN学院:《Spring Boot 视频教程全家桶》


    程序新视界:精彩和成长都不容错过
    ![程序新视界-微信公众号](https://img2018.cnblogs.com/blog/1742867/201910/1742867-20191013111755842-2090947098.png)
  • 相关阅读:
    cvBox2D和RotatedRect中返回的角度angle详解
    opencv源码阅读之——iOS的两条接口UIImageToMat()和MatToUIImage()
    3、设置jsp上的类容自动更新
    2、搭建一个简单的Web项目
    1、IDEA的常用快捷键
    2、jQuery的Ajax简单示例
    1、jQuery的使用入门
    13、Ajax的使用
    JSP和后台交互时的乱码问题
    12、Filter(拦截器)
  • 原文地址:https://www.cnblogs.com/secbro/p/12080818.html
Copyright © 2020-2023  润新知