<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用springboot開發的同學們,都一定會從組態檔application.yml
中讀取設定。比如我們常常會在上傳檔案的功能中,把檔案的儲存路徑寫在組態檔中,然後在程式碼中通過@Value()
註解從組態檔讀取對應的設定,如下所示:
在組態檔中定義檔案路徑
file: location: /data/files
在程式碼中獲取儲存路徑
@Component public class upload { @Value("${file.location}") private String fileLocation; // 檔案路徑/data/files public void upload(File file) { // 將檔案儲存到fileLocation中。 } }
這種讀取設定的方式非常方便,但是有一個讓人抓狂的缺點:
在多人共同作業開發的情況下,同事A在組態檔中修改file.location
的值為E:\
後將程式碼提交到git倉庫,這時同事B把最新程式碼拉下來後由於他的電腦中不存在E槽
導致該功能出現bug,很多同學不嫌麻煩,每次拉下最新程式碼後都會把這種設定重新修改以適合自己電腦的要求。
幸運的是,springboot在讀取設定引數方面為我們提供了多種方式,並且不同方式之間存在優先順序差異,如命令列設定的優先順序大於組態檔的優先順序。如下圖為springboot官方的描述
從上圖可知,命令列設定是在非單元測試環境下優先順序最高的。
在我們通過java -jar
命令啟動專案時,新增額外的引數,就可以解決上面提及的多人共同作業開發的問題了。
當我們使用IDEA啟動springboot專案時,可以對專案的啟動設定命令列引數,命令列引數的格式為--name=value
或 --name
,如下所示
啟動專案後,我們從IOC容器中獲取命令列引數對應的beanspringApplicationArguments
,再從該bean中就可以獲取到我們在命令列中設定的引數了。
springboot悄悄替我們向IOC容器中註冊一個ApplicationArguments
型別的bean,beanName為springApplicationArguments
,該bean中儲存著我們設定的應用程式引數。
@SpringBootApplication public class ArgumentApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args); // 獲取應用程式引數 ApplicationArguments applicationArguments =(ApplicationArguments)applicationContext .getBean("springApplicationArguments"); // 獲取命令列中name的設定 List<String> name = applicationArguments.getOptionValues("name"); System.out.println(name); } }
輸出如下所示
當然,你也可以通過@Autowired
的方式在類裡注入ApplicationArguments
範例來獲取其中的設定。
當然我們更常用的方式是通過@Value
註解來獲取,如下所示
新建一個ComponentA,並用@Component
註解標註為springBean,然後為其定義@Value
標註的成員變數name
@Component public class ComponentA { @Value("${name}") private String name; public ComponentA() { } public String getName() { return name; } }
專案啟動後,從IOC容器中獲取ComponentA
,並呼叫getName()
方法來驗證name
的值
@SpringBootApplication public class ArgumentApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ArgumentApplication.class, args); // 從組態檔中獲取 ComponentA componentA = (ComponentA) applicationContext.getBean("componentA"); System.out.println(componentA.getName()); } }
輸出,結果符合預期
springboot通過啟動類的
main()方法
接收命令列中以--
定義的應用程式引數,將引數按照不同型別以Map<String, List<String>>
和List<String>
儲存並封裝到CommandLineArgs
物件中,然後以name="commandLineArgs",source=CommandLineArgs物件
將其封裝到Source
中,而Source
為ApplicationArguments
內部屬性,springboot將ApplicationArguments
注入IOC容器。
從上面的例子中我們發現,springboot把我們設定的命令列引數封裝到ApplicationArguments
了,而ApplicationArguments
又被springboot註冊到IOC容器中,其對應的beanName為"springApplicationArguments"
,下面我們通過分析原始碼來逐步解開它是如何操作的。
首先,大家在寫springboot啟動類時,有沒有注意到其中main()
方法的引數String[] args
,如下所示
@SpringBootApplication public class ArgumentApplication { public static void main(String[] args) { SpringApplication.run(ArgumentApplication.class, args); } }
但這個引數想必有很多同學不知道它是幹嘛用的,它的作用就是用來接收啟動命令中設定的--name=key
引數,比如java -jarApplication.jar --name=key
,我們可以通過斷點進行驗證
在原始碼run()
方法中我們追蹤args
這個引數的呼叫鏈如下:
public ConfigurableApplicationContext run(String... args) { // ... SpringApplicationRunListeners listeners = getRunListeners(args); // ... ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // ... }
從原始碼可以看出,引數args
可以被用來獲取執行監聽器 和 構造應用引數,因此我們把注意力放在構造應用引數上來。
看一下該類的結構,從它的構造方法我們得知,該類是把我們傳入的--
應用程式引數封裝成一個Source
物件,同時也儲存一份原始的args
引數,當我們需要獲取引數時,都是呼叫Source
物件提供的方法獲取的,因此Source這個類尤其關鍵,我們需要弄清楚它是如何分析應用程式引數並將其封裝到Source
中的。
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; } // ... private static class Source extends SimpleCommandLinePropertySource { Source(String[] args) { super(args); } // ... } }
Source類是DefaultApplicationArguments
的內部類,上面已經展示其具體實現的原始碼,它的建構函式就是把接收的應用程式引數傳遞給父類別的建構函式。
下面我們看一下他的UML圖
由於Source的建構函式直接把引數args
交給其父類別的建構函式,而Source本身沒有多餘的處理,因此我們直接進入其父類別SimpleCommandLinePropertySource
。
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> { public SimpleCommandLinePropertySource(String... args) { super(new SimpleCommandLineArgsParser().parse(args)); } public SimpleCommandLinePropertySource(String name, String[] args) { super(name, new SimpleCommandLineArgsParser().parse(args)); } }
在這個類中,又是直接呼叫父類別的構造方法,且沒有自身的實現。但不同的,這裡將我們設定的應用程式進行轉換成CommandLineArgs
物件交給父類別建構函式。
它是怎麼分析我們傳入的應用程式引數的,又將其轉換成什麼樣的結構呢?
該類只有一個靜態方法parse()
,從命名也可以看出,該類的功能就是對命令列引數提供簡單的轉換器。
class SimpleCommandLineArgsParser { public CommandLineArgs parse(String... args) { CommandLineArgs commandLineArgs = new CommandLineArgs(); for (String arg : args) { // 以 -- 開頭的應用程式引數 if (arg.startsWith("--")) { String optionText = arg.substring(2); String optionName; String optionValue = null; int indexOfEqualsSign = optionText.indexOf('='); if (indexOfEqualsSign > -1) { // --key=value這種形式的引數 optionName = optionText.substring(0, indexOfEqualsSign); optionValue = optionText.substring(indexOfEqualsSign + 1); } else { // --key這種形式的引數 optionName = optionText; } if (optionName.isEmpty()) { throw new IllegalArgumentException("Invalid argument syntax: " + arg); } commandLineArgs.addOptionArg(optionName, optionValue); } else { // 不以 -- 開頭的應用程式引數 commandLineArgs.addNonOptionArg(arg); } } return commandLineArgs; } }
從原始碼得知,應用程式引數的轉換過程非常簡單,就是根據--
和 =
進行字串裁剪,然後將這些引數封裝到CommandLineArgs
裡。而在CommandLineArgs
中用不同的欄位來儲存不同型別的應用程式引數。如下
class CommandLineArgs { // 儲存 --key=value 和 --key這兩種型別的應用程式引數 private final Map<String, List<String>> optionArgs = new HashMap<>(); // 儲存 key 這一種型別的應用程式引數 private final List<String> nonOptionArgs = new ArrayList<>(); }
回到上一節SimpleCommandLinePropertySource
,它的建構函式就是將應用程式引數轉換為CommandLineArgs
然後交給父類別建構函式,那下面我們看其父類別CommandLinePropertySource
。
在CommandLinePropertySource
中,我們主要看其建構函式。
public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> { public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; public CommandLinePropertySource(T source) { super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source); } }
很顯然,又是直接呼叫父類別的建構函式,而且向其父類別建構函式傳入的是"commandLineArgs"
字串 和 CommandLineArgs
物件。那我們繼續,進入父類別EnumerablePropertySource
,然後又將這兩個引數繼續傳遞給父類別PropertySource
public abstract class EnumerablePropertySource<T> extends PropertySource<T> { public EnumerablePropertySource(String name, T source) { super(name, source); } }
通過前面一系列對父類別建構函式的呼叫,最終將name初始化為"commandLineArgs"
字串 ,將source初始化為 CommandLineArgs
物件。
public abstract class PropertySource<T> { protected final String name; protected final T source; public PropertySource(String name, T source) { Assert.hasText(name, "Property source name must contain at least one character"); Assert.notNull(source, "Property source must not be null"); this.name = name; this.source = source; } }
在前面我們將應用程式引數封裝到ApplicationArguments
物件中後,springboot又將這些應用程式引數新增到environment
物件中,並且對已存在的設定進行覆蓋,因此與組態檔中定義的引數類似,都可以通過@Value註解獲取。
在下面的原始碼中,主要表達的是應用程式引數在各個方法呼叫中的傳遞,最關鍵的部分我們要看configurePropertySources()
方法。該方法將應用程式引數設定到執行環境environment
。
public ConfigurableApplicationContext run(String... args) { // ... ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // ... } private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); } protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { // ... configurePropertySources(environment, args); // ... } // 將應用程式設定到environment物件中,與組態檔中的引數處於同一environment物件中,因此可以通過@Value註解獲取引數設定 protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast); if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { // 環境中已存在相同的設定,則進行覆蓋 PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
在前面的章節,我們通過原始碼分析得出結論,springboot將應用程式引數封裝到ApplicationArguments
和執行環境Environment
中。接下來我們看它是如何註冊到IOC容器的。
public ConfigurableApplicationContext run(String... args) { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // ... prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // ... } private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // ... ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); // ... }
springboot將應用程式引數ApplicationArguments
直接通過beanFactory.registerSingleton()
方法手動地註冊到IOC容器中,beanName為springApplicationArguments
。
springboot將我們設定的命令列引數封裝到ApplicationArguments
,並使用"springApplicationArguments"
作為beanName將其註冊到IOC容器。
設定應用程式引數時,符合要求的設定為:--key=value
、--key
以及 key
。可以通過@Value
註解直接獲取應用程式引數。可以通過@Autowired
依賴注入一個ApplicationArguments
範例來讀取應用程式引數。
到此這篇關於springboot載入命令列引數ApplicationArguments的實現的文章就介紹到這了,更多相關springboot載入命令列引數內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45