1. 程式人生 > >springboot原始碼分析5-springboot之命令列引數以及原理

springboot原始碼分析5-springboot之命令列引數以及原理

摘要:本文我們重點分析一下Springboot框架中的命令列引數的使用以及框架內部處理的命令列引數的原理。

眾所周知,springboot專案可以有兩種方式啟動,第一種使用jar包;第二種使用war包。在使用jar方式的時候,我們可以在啟動jar包的時候設定一些命令引數。

1.1 命令列引數使用

首先我們看一下如何使用在專案啟動的時候設定命令列引數以及值。我這裡使用的開發工具是Spring Tool Suite 版本是: 3.9.0.RELEASE。我們先建立一個工程檔案,目錄結構如下圖所示:

                             

Application內容如下:

1 @EnableConfigurationProperties

2 public class Application {

3  public static void main(String[] args) {

4  ConfigurableApplicationContext configurableApplicationContext =    SpringApplication.run(Application.class, args);

5  }

6 }

怎麼啟動上述的類呢?操作步驟如下:

第一步:點選 Debug AS-->>Debug Configurations

 

第二步:

 

首先,我們需要點選①箭頭處的新增按鈕,然後就會建立一個Spring Boot App、其次我們輸入命令列的引數,然後點選App即可完成設定並啟動專案。上圖中我們設定了三個變數,如下所示:

--foo=bar  -foo=bar1  --foo=bar2

   上述這三個變數我們該如何獲取呢?這也是校驗引數是否設定成功的一個途徑吧相信大家都想知道下一步的操作,下面的例項程式碼還是在上文的兩個步驟為前提下進行。例項程式碼如下:

1 @EnableConfigurationProperties

2 public class Application {

3  public static void main(String[] args) {

4  ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(Application.class, args);

5  String property = configurableApplicationContext.getEnvironment().getProperty("foo");

6  System.out.println(property);

7  }

8 }

執行上述的程式碼,控制檯的輸出資訊如下:

bar,bar2

   我們通過ConfigurableApplicationContext例項獲取環境ConfigurableEnvironment例項物件,然後通過ConfigurableEnvironment獲取屬性foo。ConfigurableEnvironment大家暫時先有個印象,我們通過這個例項物件獲取到專案中所有的配置屬性資訊。這個我們後續也會詳細的進行講解。看到上面的輸出,發現輸出的是bar,bar2

,然而bar1並沒有輸出?為什麼呢?大家看下這三個命令列引數有何迥異,很顯然bar,bar2的引數屬性都是--開頭的,而bar1是-開頭的。那我們就很好奇springboot是如何獲取這些引數值以及解析的呢?

   上述中的啟動類是Application,該類中的main方法去啟動Springboot專案,既然是main方法,所以我們上文提到的三個命令列引數最終會被設定到args引數中的。這一點大家一定要注意了。SpringApplication.run方法會將args引數繼續傳遞到SpringApplication類中讓框架處理的。接下來,我們看一下SpringApplication.run方法中關於命令列引數的相關處理邏輯吧。

1.2 命令列引數原理

 我們開始跟進SpringApplication類中的run(String... args)方法,相關的程式碼如下所示:

1 public ConfigurableApplicationContext run(String... args) {

2  ...//省略

3  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

4  ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

5  ...//省略

6  }

1.2.1 DefaultApplicationArguments類

     首先,我們看一下DefaultApplicationArguments類,該類的核心程式碼如下:

1 public class DefaultApplicationArguments implements ApplicationArguments {

2  private final Source source;

3  private final String[] args;

4  public DefaultApplicationArguments(String[] args) {

5      Assert.notNull(args, "Args must not be null");

6      this.source = new Source(args);

7      this.args = args;

8  }

DefaultApplicationArguments類的建構函式中,首先例項化Source類,並將args引數以及值進行傳遞。然後在自身類中使用args屬性進行引數值的報錯,DefaultApplicationArguments類實現了ApplicationArguments介面。ApplicationArguments 介面中定義了各種命令列引數的操作,比如引數值的獲取、引數名稱的獲取等方法。

上面的程式碼感覺沒有什麼神奇的地方,貌似只是對命令列引數的各種封裝而已。其實Source類大有玄機。該類的程式碼如下所示:

1 private static class Source extends SimpleCommandLinePropertySource {

2  Source(String[] args) {

3  super(args);

4  }

5 ...//省略

6  }

     Source類繼承SimpleCommandLinePropertySource類,並在當前類的建構函式呼叫SimpleCommandLinePropertySource 類的建構函式,SimpleCommandLinePropertySource 類的建構函式如下所示:

1 public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {

2  public SimpleCommandLinePropertySource(String... args) {

3  super(new SimpleCommandLineArgsParser().parse(args));

4  }

    雖然SimpleCommandLinePropertySource類的建構函式繼續呼叫CommandLinePropertySource類的建構函式進行處理,但是我們只需要將new SimpleCommandLineArgsParser().parse(args)這行程式碼搞明白,關於命令列引數以及值的處理我們就可以搞明白了。接下來我們快速看一下parse方法的實現邏輯,例項程式碼如下:

1 public CommandLineArgs parse(String... args) {

2  CommandLineArgs commandLineArgs = new CommandLineArgs();

3  for (String arg : args) {

4    if (arg.startsWith("--")) {

5            String optionText = arg.substring(2, arg.length());

6            String optionName;

7            String optionValue = null;

8           if (optionText.contains("=")) {

9                   optionName = optionText.substring(0, optionText.indexOf("="));

10                   optionValue = optionText.substring(optionText.indexOf("=")+1, optionText.length());

11            }

12          else {

13         optionName = optionText;

14       }

15  if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {

16              throw new IllegalArgumentException("Invalid argument syntax: " + arg);

17  }

18        commandLineArgs.addOptionArg(optionName, optionValue);

19  }

20  else {

21           commandLineArgs.addNonOptionArg(arg);

22  }

23  }

24        return commandLineArgs;

25  }

我們將上述程式碼的邏輯梳理如下:

① 例項化CommandLineArgs類。這個類封裝了命令列解析之後的引數以及值資訊、還有沒有被識別的命令列引數以及值的資訊。

② 迴圈遍歷所有的引數以及值args,比如我們傳遞的是--foo=bar  -foo=bar1  --foo=bar2

③ 如果引數以--開頭,則開始如下的操作;否則將其作為不能識別的引數進行處理,上文我們定義的-foo就是不能被識別的引數。

擷取--字串並使用optionText變數進行儲存,比如引數--foo=bar擷取之後,則optionText值為foo=bar。

optionName為foo,optionValue為bar,通過上述程式碼邏輯可以看出,如果=前邊或者後邊出現了空格就慘了。因為這個地方的程式碼並沒有對空格以及特殊字元進行區分。

1.2.1.1. 識別引數新增

commandLineArgs.addOptionArg(optionName, optionValue);進行springboot可識別的命令列引數的新增工作,其內部實現邏輯如下所示:

1 private final Map<String, List<String>> optionArgs = new HashMap<>();

2 public void addOptionArg(String optionName, @Nullable String optionValue) {

3  if (!this.optionArgs.containsKey(optionName)) {

4  this.optionArgs.put(optionName, new ArrayList<>());

5  }

6  if (optionValue != null) {

7  this.optionArgs.get(optionName).add(optionValue);

8  }

9  }

上述程式碼中,首先校驗optionName引數值是否存在於optionArgs集合中。如果不存在,則直接例項化ArrayList並將其新增到optionArgs集合中。

注意:optionArgs的key為引數的名稱,value是一個List集合,儲存的是該引數的所有值。通過這裡的處理我們可以看出,我們上文輸出的foo引數的值是bar,bar2就很容易理解了。

1.2.1.2. 未知引數新增

commandLineArgs.addNonOptionArg(arg)方法進行未知引數的新增邏輯,比如上文中的-foo=bar1引數就在這個方法進行處理的額,因為該引數不是--開頭的。commandLineArgs.addNonOptionArg(arg)方法如下所示:

1 private final List<String> nonOptionArgs = new ArrayList<>();

2 public void addNonOptionArg(String value) {

3  this.nonOptionArgs.add(value);

4  }

未知引數最終被新增到了nonOptionArgs集合。

講解到這裡基本上命令列引數的設定以及解析原理都搞明白了。

歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Java相關原創技術乾貨。 
掃一掃下方二維碼或者長按識別二維碼,即可關注。
 

   


相關推薦

springboot原始碼分析5-springboot命令引數以及原理

摘要:本文我們重點分析一下Springboot框架中的命令列引數的使用以及框架內部處理的命令列引數的原理。眾所周知,springboot專案可以有兩種方式啟動,第一種使用jar包;第二種使用war包。在使用jar方式的時候,我們可以在啟動jar包的時候設定一些命令引數。1.1

springboot原始碼分析4-springbootSpringFactoriesLoader使用

摘要:本文我們重點分析一下Spring框架中的SpringFactoriesLoader類以及META-INF/spring.factories的使用。在詳細分析之前,我們可以思考一個問題?在我們設計一套API供別人呼叫的時候,如果同一個功能的要求特別多,或者同一個介面要面對

springboot原始碼分析6-springbootPropertySource類初探

在springboot原始碼分析5-springboot之命令列引數以及原理一文中,我們看到了例項化Source類的時候,會去先例項化其父類SimpleCommandLinePropertySource。SimpleCommandLinePropertySource類的建構函

ffmpeg 原始碼學習 -- 命令引數解析

ffmpeg 原始碼學習 -- 之ffmpeg命令列引數解析 大家通過git在安裝好cygwin之後,下載原始碼到(cygwin)home目錄,通過./configure  ...... ,可以新增一堆引數選項,執行可以生成config.mk等編譯使用的檔案,通過命令對工

FFmpeg libswscale原始碼分析2-轉碼命令與濾鏡圖

本文為作者原創,轉載請註明出處: libswscale 原始碼分析系列文章: [1]. [FFmpeg libswscale原始碼分析1-API介紹](https://www.cnblogs.com/leisure_chn/p/14349382.html) [2]. [FFmpeg libsws

Go語言學習筆記(十七)命令引數

24.1命令列引數 os.Args命令列引數的切片 1: func main() { 2: name := "Alice" 3: fmt.Println("Good Morning", name) 4: // 說明使用者傳入了引數 5: if len(os.Args) >

java命令引數

命令列 public class Test { public static void main(String[] args) { for ( int i = 0; i < args.length; i++ ) { System.o

TensorFlow 命令引數

TensorFlow專案例子中經常出現tf.app.flags,這個好像和tf.flags是一樣,加不加中間的app沒區別(要是不是這樣還請大家指出錯誤之處),它支援應用從命令列接受引數,可以用來指定叢集配置等。 import tensorflow as tf

核心配置命令引數以及Uboot設定環境變數bootargs裡面的init=/linuxrc解析

根檔案系統的啟動及配置!(小壽原創)www.diybl.com 時間:2008-07-03 作者:佚名 編輯:本站 點選: 314 [評論] - - 按啟動順序依次介紹相關的檔案: 一、核心啟動完之後,首先執行/linuxrc。 /linuxrc內容: ----------

C/C++中命令引數原理總結

   在c/c++中,命令列引數的傳遞是利用main進行形參傳遞實現 【1】了實現命令列引數我們將使用main(int argc,char* argv[])這樣的形式進行定義argc和argv可以換成你自己喜歡的名稱不一定要用argv,argc這些形式只是習慣而已,char*

springboot學習總結(一)外部配置(命令引數配置、常規屬性配置、型別安全的配置基於properties)

學習的內容主要是汪雲飛的《Spring Boot實戰》 (一)命令列引數配置 springboot專案可以基於jar包執行,開啟jar的程式可以通過下面命令列執行: java -jar xxx.jar 可以通過以下命令修改tomcat埠號 java -jar xxx.jar --server.por

SpringBoot原始碼分析---SpringBoot專案啟動類SpringApplication淺析

原始碼版本 本文原始碼採用版本為SpringBoot 2.1.0BUILD,對應的SpringFramework 5.1.0.RC1 注意:本文只是從整體上梳理流程,不做具體深入分析 SpringBoot入口類 @SpringBootAp

Springboot原始碼分析專案結構

摘要: 無論是從IDEA還是其他的SDS開發工具亦或是https://start.spring.io/ 進行解壓,我們都會得到同樣的一個pom.xml檔案 xml <?xml version="1.0" encoding="UTF-8"?> <project xm

Springboot原始碼分析jar探祕

摘要: 利用IDEA等工具打包會出現springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面說過它們之間的關係了,接下來我們就一探究竟,它們之間到底有什麼聯絡。 檔案對比: 進入target目錄,unzip sprin

Springboot原始碼分析番外篇

摘要: 大家都知道註解是實現了java.lang.annotation.Annotation介面,眼見為實,耳聽為虛,有時候眼見也不一定是真實的。 /** * The common interface extended by all annotation types. Note that

Springboot原始碼分析EnableAspectJAutoProxy

摘要: Spring Framwork的兩大核心技術就是IOC和AOP,AOP在Spring的產品線中有著大量的應用。如果說反射是你通向高階的基礎,那麼代理就是你站穩高階的底氣。AOP的本質也就是大家所熟悉的CGLIB動態代理技術,在日常工作中想必或多或少都用過但是它背後的祕密值得我們去深思。本文主要從Spr

Springboot原始碼分析代理三板斧

摘要: 在Spring的版本變遷過程中,註解發生了很多的變化,然而代理的設計也發生了微妙的變化,從Spring1.x的ProxyFactoryBean的硬編碼島Spring2.x的Aspectj註解,最後到了現在廣為熟知的自動代理。 說明: ProxyConfig代理的相關配置類 AdvisedSupp

Springboot原始碼分析AbstractAdvisorAutoProxyCreator

摘要: Spring的代理在上層中主要分為ProxyCreatorSupport和ProxyProcessorSupport,前者是基於代理工廠,後者是基於後置處理器,也可以認為後置就是自動代理器。當spring容器中需要進行aop進行織入的bean較多時,簡單採用ProxyFacotryBean無疑會增加很

Springboot原始碼分析TargetSource

摘要: 其實我第一次看見這個東西的時候也是不解,代理目標源不就是一個class嘛還需要封裝幹嘛。。。 其實proxy代理的不是target,而是TargetSource,這點非常重要,一定要分清楚!!! 通常情況下,一個代理物件只能代理一個target,每次方法呼叫的目標也是唯一固定的target。但是,如果

Springboot原始碼分析@Transactional

摘要: 對SpringBoot有多瞭解,其實就是看你對Spring Framework有多熟悉~ 比如SpringBoot大量的模組裝配的設計模式,其實它屬於Spring Framework提供的能力。SpringBoot大行其道的今天,基於XML配置的Spring Framework的使用方式註定已成為過去