14.SpringShell源碼分析-ApplicationRunner

SpringShell應(yīng)用啟動時, 會默認(rèn)向IOC容器中注入兩個ApplicationRunner: ScriptShellApplicationRunner 和 InteractiveShellApplicationRunner, 其中ScriptShellApplicationRunner 的優(yōu)先級要高于InteractiveShellApplicationRunner.

1. ApplicationRunner 定義

1.1 InteractiveShellApplicationRunner

InteractiveShellApplicationRunner 指定順序?yàn)?

@Order(InteractiveShellApplicationRunner.PRECEDENCE)
public class InteractiveShellApplicationRunner implements ApplicationRunner {
    // ...
}

1.2 ScriptShellApplicationRunner

定義時, 指定順序?yàn)镮nteractiveShellApplicationRunner 的順序-100, 也就是優(yōu)先級大于InteractiveShellApplicationRunner, 通過這種方式限制優(yōu)先注冊ScriptShellApplicationRunner.

@Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner
public class ScriptShellApplicationRunner implements ApplicationRunner {
    // ...
}

1.3 配置類JLineShellAutoConfiguration

配置類JLineShellAutoConfiguration中注入 interactiveApplicationRunner 和 scriptApplicationRunner 組件, 有條件注入, 默認(rèn)為true.

@Configuration
public class JLineShellAutoConfiguration {
    @Bean
    @ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
    public ApplicationRunner interactiveApplicationRunner(Parser parser, Environment environment) {
        return new InteractiveShellApplicationRunner(lineReader(), promptProvider, parser, shell, environment);
    }

    @Bean
    @ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
    public ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
        return new ScriptShellApplicationRunner(parser, shell, environment);
    }
}

2. 源碼分析

本文只設(shè)計(jì)ApplicationRunner的運(yùn)行流程, 不涉及bean 初始化等其它流程.

1. 啟動容器, 遍歷執(zhí)行自定義ApplicationRunner

  • SpringShell 應(yīng)用通過 SpringApplication.run(SpringShellApplication.class, args)啟動項(xiàng)目, 會調(diào)用org.springframework.boot.SpringApplication#run(java.lang.String...) 方法
  • org.springframework.boot.SpringApplication#run(java.lang.String...) 會對容器進(jìn)行初始化, 創(chuàng)建bean 等操作, 然后會調(diào)用所有自定義的ApplicationRunner 的run方法
// 源碼: org.springframework.boot.SpringApplication#callRunners
 private void callRunners(ApplicationContext context, ApplicationArguments args) {
    // 獲取容器中定義的所有ApplicationRunner
    List<Object> runners = new ArrayList();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    Iterator var4 = (new LinkedHashSet(runners)).iterator();

    // 遍歷所有ApplicationRunner, 執(zhí)行其run方法.
    while(var4.hasNext()) {
        Object runner = var4.next();
        if (runner instanceof ApplicationRunner) {
            // callRunner()方法里就是執(zhí)行runner.run(args) 方法
            this.callRunner((ApplicationRunner)runner, args);
        }

        if (runner instanceof CommandLineRunner) {
            this.callRunner((CommandLineRunner)runner, args);
        }
    }
}

2. 優(yōu)先執(zhí)行ScriptShellApplicationRunner.run(args)方法

  1. 首先對所有啟動參數(shù)做一遍過濾, 獲取所有的腳本參數(shù), 并創(chuàng)建文件列表. 也就是說, 只要啟動參數(shù)包含腳本參數(shù), 則忽略所有其它參數(shù).
  2. 如果啟動參數(shù)中,包含腳本參數(shù):
    1. 關(guān)閉交互式方式, 在執(zhí)行InteractiveShellApplicationRunner.run 方法時, 會先判斷是否
    2. 遍歷腳本列表, 為每個腳本文件創(chuàng)建一個InputProvider, 每個腳本都作為一個獨(dú)立的輸入源, 執(zhí)行sell.run()方法
    3. 每個腳本讀到最后都返回null, shell.run()讀到null時, 結(jié)束shell.run()中的循環(huán).
  3. 如果啟動參數(shù), 不包含腳本參數(shù), 則此ApplicationRunner運(yùn)行結(jié)束, 接下來運(yùn)行下一個ApplicationRunner, 即InteractiveShellApplicationRunner
//  org.springframework.shell.jline.ScriptShellApplicationRunner#run**
@Override
public void run(ApplicationArguments args) throws Exception {
    # 1.獲取啟動參數(shù)中所有以@開頭的參數(shù), 為每個腳本參數(shù)創(chuàng)建一個File對象, 組成File列表
    List<File> scriptsToRun = args.getNonOptionArgs().stream()
            .filter(s -> s.startsWith("@"))
            .map(s -> new File(s.substring(1)))
            .collect(Collectors.toList());

    boolean batchEnabled = environment.getProperty(SPRING_SHELL_SCRIPT_ENABLED, boolean.class, true);

    # 2.如果腳本參數(shù)不為空
    if (!scriptsToRun.isEmpty() && batchEnabled) {
        // 3.設(shè)置關(guān)閉交互式方式, 保證運(yùn)行為腳本的所有命令之后, 直接關(guān)閉程序
        InteractiveShellApplicationRunner.disable(environment);
        // 4.遍歷每個腳本, 為每個腳本創(chuàng)建一個命令提供源, 依次執(zhí)行shell的run方法.
        for (File file : scriptsToRun) {
            try (Reader reader = new FileReader(file);
                    FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
                shell.run(inputProvider);
            }
        }
    }
}

3. 其次執(zhí)行 InteractiveShellApplicationRunner.run(args)方法

  1. 首先判斷, 交互方式是否打開. 即在ScriptShellApplicationRunner.run中有沒有關(guān)閉交互時方式
  2. 如果交互方式打開, 則創(chuàng)建控制臺命令輸入源, 執(zhí)行shell的run方法. shell 的run 會進(jìn)入無限循環(huán), 當(dāng)用戶輸入為null 時, 則終止循環(huán), 退出程序
// 源碼: org.springframework.shell.jline.InteractiveShellApplicationRunner#run
@Override
public void run(ApplicationArguments args) throws Exception {
    // 獲取交互方式是否打開, 如果ScriptShellApplicationRunner 中執(zhí)行了第三步, 此處獲取即為false.
    boolean interactive = isEnabled();

    // 如果交互方式打開(為true), 則創(chuàng)建控制臺輸入源, 執(zhí)行shell 的run方法
    if (interactive) {
        InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider);
        shell.run(inputProvider);
    }
}

4. 核心方法Shell.run(InputProvider)

  • Shell的run方法是一個無限循環(huán), 會循環(huán)地從傳入的InputProvider中獲取命令, 如果獲取為空, 則跳出循環(huán), 結(jié)束run方法; 否則解析命令, 執(zhí)行命令,并將返回值輸出.
  • run方法從InputProvider 中是一條命令一條命令讀取的, 且只有當(dāng)返回值為null時, 才會跳出run方法, 結(jié)束方法運(yùn)行. 這一點(diǎn)在自定義Runner時需要注意.
// 源碼: org.springframework.shell.Shell#run
public void run(InputProvider inputProvider) throws IOException {
    // 自定義循環(huán)退出碼
    Object result = null;

    // 無限循環(huán), 知道result為退出嘛
    while (!(result instanceof ExitRequest)) {
        Input input;

        // 從輸入源中讀取一條輸入
        try {
            input = inputProvider.readInput();
        }
        catch (Exception e) {
            resultHandler.handleResult(e);
            continue;
        }

        // 當(dāng)讀取的輸入為null時, 跳出循環(huán), 結(jié)束此shell的運(yùn)行
        if (input == null) {
            break;
        }

        // 執(zhí)行命令, 相應(yīng)結(jié)果
        result = evaluate(input);

        // 處理結(jié)果, 回顯終端
        if (result != NO_INPUT && !(result instanceof ExitRequest)) {
            resultHandler.handleResult(result);
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颊咬,隨后出現(xiàn)的幾起案子务甥,更是在濱河造成了極大的恐慌,老刑警劉巖喳篇,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敞临,死亡現(xiàn)場離奇詭異,居然都是意外死亡麸澜,警方通過查閱死者的電腦和手機(jī)挺尿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炊邦,“玉大人编矾,你說我怎么就攤上這事∠吃牛” “怎么了洽沟?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵以故,是天一觀的道長蜗细。 經(jīng)常有香客問我,道長怒详,這世上最難降的妖魔是什么炉媒? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮昆烁,結(jié)果婚禮上吊骤,老公的妹妹穿的比我還像新娘。我一直安慰自己静尼,他們只是感情好白粉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布传泊。 她就那樣靜靜地躺著,像睡著了一般鸭巴。 火紅的嫁衣襯著肌膚如雪眷细。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天鹃祖,我揣著相機(jī)與錄音溪椎,去河邊找鬼。 笑死恬口,一個胖子當(dāng)著我的面吹牛校读,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祖能,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼歉秫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芯杀?” 一聲冷哼從身側(cè)響起端考,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揭厚,沒想到半個月后却特,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筛圆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年裂明,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片太援。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡闽晦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出提岔,到底是詐尸還是另有隱情仙蛉,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布碱蒙,位于F島的核電站荠瘪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赛惩。R本人自食惡果不足惜哀墓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喷兼。 院中可真熱鬧篮绰,春花似錦、人聲如沸季惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贾漏,卻和暖如春惧辈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磕瓷。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工盒齿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人困食。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓边翁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親硕盹。 傳聞我的和親對象是個殘疾皇子符匾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,381評論 0 5
  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時實(shí)驗(yàn)課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 5,744評論 0 10
  • 第 2 章 SHELL 基礎(chǔ)知識2.1 shell腳本我們在上面簡單介紹了一下什么是shell腳本瘩例,現(xiàn)在我們來進(jìn)一...
    LiWei_9e4b閱讀 1,569評論 0 0
  • 記得在我剛參加工作的時候啊胶,單位有一位警衛(wèi)大哥。他是那種沒有上過什么學(xué)垛贤,很遲鈍的人焰坪。單位所有人都拿他取笑。由于我不喜...
    最重要的是思想閱讀 1,048評論 0 0
  • 癩蛤蟆看到麻雀媽媽如此粗魯?shù)貙Υ约浩傅耄牡子悬c(diǎn)委屈某饰,但是看在麻雀傷心的份上,又覺得好笑善绎。當(dāng)他自鳴得意地回到家...
    隔水望伊人閱讀 640評論 0 50