Java 源碼刨析 – 線程的狀態有哪些?它是如何工作的?

線程(Thread)是併發編程的基礎,也是程序執行的最小單元,它依託進程而存在

一個進程中可以包含多個線程,多線程可以共享一塊內存空間和一組系統資源,因此線程之間的切換更加節省資源、更加輕量化,也因此被稱為輕量級的進程。

   

線程的狀態在 JDK 1.5 之後以枚舉的方式被定義在 Thread 的源碼中,它總共包含以下 6 個狀態:

NEW新建狀態,線程被創建出來,但尚未啟動時的線程狀態;

RUNNABLE就緒狀態,表示可以運行的線程狀態,它可能正在運行,或者是在排隊等待操作系統給它分配 CPU 資源;

BLOCKED阻塞等待鎖的線程狀態,表示處於阻塞狀態的線程正在等待監視器鎖,比如等待執行 synchronized 代碼塊或者使用 synchronized 標記的方法;

WAITING等待狀態,一個處於等待狀態的線程正在等待另一個線程執行某個特定的動作,比如,一個線程調用了 Object.wait() 方法,那它就在等待另一個線程調用 Object.notify() Object.notifyAll() 方法;

TIMED_WAITING計時等待狀態,和等待狀態(WAITING)類似,它只是多了超時時間,比如調用了有超時時間設置的方法 Object.wait(long timeout) Thread.join(long timeout) 等這些方法時,它才會進入此狀態;

TERMINATED終止狀態,表示線程已經執行完成。

線程狀態的源代碼如下:

public enum State {

    /**

     * 新建狀態,線程被創建出來,但尚未啟動時的線程狀態

     */

    NEW,

   

    /**

     * 就緒狀態,表示可以運行的線程狀態,但它在排隊等待來自操作系統的 CPU 資源

     */

    RUNNABLE,

   

    /**

     * 阻塞等待鎖的線程狀態,表示正在處於阻塞狀態的線程

     * 正在等待監視器鎖,比如等待執行 synchronized 代碼塊或者

     * 使用 synchronized 標記的方法

     */

    BLOCKED,

   

    /**

     * 等待狀態,一個處於等待狀態的線程正在等待另一個線程執行某個特定的動作。

     * 例如,一個線程調用了 Object.wait() 它在等待另一個線程調用

     * Object.notify() 或 Object.notifyAll()

     */

    WAITING,

   

    /**

     * 計時等待狀態,和等待狀態 (WAITING) 類似,只是多了超時時間,比如

     * 調用了有超時時間設置的方法 Object.wait(long timeout) 和 

     * Thread.join(long timeout) 就會進入此狀態

     */

    TIMED_WAITING,

   

    /**

     * 終止狀態,表示線程已經執行完成

     */

}

   

線程的工作模式是,首先先要創建線程並指定線程需要執行的業務方法,然後再調用線程的 start() 方法,此時線程就從 NEW(新建)狀態變成了 RUNNABLE(就緒)狀態;

然後線程會判斷要執行的方法中有沒有 synchronized 同步代碼塊,如果有並且其他線程也在使用此鎖,那麼線程就會變為 BLOCKED(阻塞等待)狀態,當其他線程使用完此鎖之後,線程會繼續執行剩餘的方法。

   

當遇到 Object.wait() Thread.join() 方法時,線程會變為 WAITING(等待狀態)狀態;

如果是帶了超時時間的等待方法,那麼線程會進入 TIMED_WAITING(計時等待)狀態;

當有其他線程執行了 notify() notifyAll() 方法之後,線程被喚醒繼續執行剩餘的業務方法,直到方法執行完成為止,此時整個線程的流程就執行完了,執行流程如下圖所示:

BLOCKED WAITING 的區別】

雖然 BLOCKED WAITING 都有等待的含義,但二者有着本質的區別。

首先它們狀態形成的調用方法不同

其次 BLOCKED 可以理解為當前線程還處於活躍狀態,只是在阻塞等待其他線程使用完某個鎖資源

WAITING 則是因為自身調用 Object.wait() 或着是 Thread.join() 又或者是 LockSupport.park() 而進入等待狀態,只能等待其他線程執行某個特定的動作才能被繼續喚醒。

比如當線程因為調用了 Object.wait() 而進入 WAITING 狀態之後,則需要等待另一個線程執行 Object.notify() Object.notifyAll() 才能被喚醒。

   

start() run() 的區別】

首先從 Thread 源碼來看,start() 方法屬於 Thread 自身的方法,並且使用了 synchronized 來保證線程安全,源碼如下:

public synchronized void start() {

    // 狀態驗證,不等於 NEW 的狀態會拋出異常

    if (threadStatus != 0)

        throw new IllegalThreadStateException();

    // 通知線程組,此線程即將啟動

   

    group.add(this);

    boolean started = false;

    try {

        start0();

        started = true;

    } finally {

        try {

            if (!started) {

                group.threadStartFailed(this);

            }

        } catch (Throwable ignore) {

            // 不處理任何異常,如果 start0 拋出異常,則它將被傳遞到調用堆棧上

        }

    }

}

   

run() 方法為 Runnable 的抽象方法,必須由調用類重寫此方法,重寫的 run() 方法其實就是此線程要執行的業務方法,源碼如下:

public class Thread implements Runnable {

 // 忽略其他方法……

  private Runnable target;

  @Override

  public void run() {

      if (target != null) {

          target.run();

      }

  }

}

@FunctionalInterface

public interface Runnable {

    public abstract void run();

}

   

從執行的效果來說,start() 方法可以開啟多線程,讓線程從 NEW 狀態轉換成 RUNNABLE 狀態,而 run() 方法只是一個普通的方法。

   

其次,它們可調用的次數不同,start() 方法不能被多次調用,否則會拋出 java.lang.IllegalStateException;而 run() 方法可以進行多次調用,因為它只是一個普通的方法而已。

   

【線程優先級】

Thread 源碼中和線程優先級相關的屬性有 3 個:

// 線程可以擁有的最小優先級

public final static int MIN_PRIORITY = 1;

   

// 線程默認優先級

public final static int NORM_PRIORITY = 5;

   

// 線程可以擁有的最大優先級

public final static int MAX_PRIORITY = 10

   

線程的優先級可以理解為線程搶佔 CPU 時間片的概率,優先級越高的線程優先執行的概率就越大,但並不能保證優先級高的線程一定先執行。

   

在程序中我們可以通過 Thread.setPriority() 來設置優先級,setPriority() 源碼如下:

public final void setPriority(int newPriority) {

    ThreadGroup g;

    checkAccess();

    // 先驗證優先級的合理性

    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {

        throw new IllegalArgumentException();

    }

    if((g = getThreadGroup()) != null) {

        // 優先級如果超過線程組的最高優先級,則把優先級設置為線程組的最高優先級

        if (newPriority > g.getMaxPriority()) {

            newPriority = g.getMaxPriority();

        }

        setPriority0(priority = newPriority);

    }

}

   

【線程的常用方法】

線程的常用方法有以下幾個。

   

join()

一個線程中調用 other.join() ,這時候當前線程會讓出執行權給 other 線程,直到 other 線程執行完或者過了超時時間之後再繼續執行當前線程,join() 源碼如下:

public final synchronized void join(long millis)

throws InterruptedException {

    long base = System.currentTimeMillis();

    long now = 0;

    // 超時時間不能小於 0

    if (millis < 0) {

        throw new IllegalArgumentException(“timeout value is negative”);

    }

    // 等於 0 表示無限等待,直到線程執行完為之

    if (millis == 0) {

        // 判斷子線程 (其他線程) 為活躍線程,則一直等待

        while (isAlive()) {

            wait(0);

        }

    } else {

        // 循環判斷

        while (isAlive()) {

            long delay = millis  now;

            if (delay <= 0) {

                break;

            }

            wait(delay);

            now = System.currentTimeMillis()  base;

        }

    }

}

   

從源碼中可以看出 join() 方法底層還是通過 wait() 方法來實現的。

   

例如,在未使用 join() 時,代碼如下:

public class ThreadExample {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {

            for (int i = 1; i < 6; i++) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                System.out.println(子線程睡眠: + i + 秒。);

            }

        });

        thread.start(); // 開啟線程

        // 主線程執行

        for (int i = 1; i < 4; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(主線程睡眠: + i + 秒。);

        }

    }

}

程序執行結果為:

複製主線程睡眠:1秒。

子線程睡眠:1秒。

主線程睡眠:2秒。

子線程睡眠:2秒。

主線程睡眠:3秒。

子線程睡眠:3秒。

子線程睡眠:4秒。

子線程睡眠:5秒。

   

從結果可以看出,在未使用 join() 時主子線程會交替執行。

   

然後我們再把 join() 方法加入到代碼中,代碼如下:

public class ThreadExample {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {

            for (int i = 1; i < 6; i++) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                System.out.println(子線程睡眠: + i + 秒。);

            }

        });

        thread.start(); // 開啟線程

        thread.join(2000); // 等待子線程先執行 2 秒鐘

        // 主線程執行

        for (int i = 1; i < 4; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(主線程睡眠: + i + 秒。);

        }

    }

}

程序執行結果為:

   

複製子線程睡眠:1秒。

子線程睡眠:2秒。

主線程睡眠:1秒。 

// thread.join(2000); 等待 2 秒之後,主線程和子線程再交替執行

子線程睡眠:3秒。

主線程睡眠:2秒。

子線程睡眠:4秒。

子線程睡眠:5秒。

主線程睡眠:3秒。

從執行結果可以看出,添加 join() 方法之後,主線程會先等子線程執行 2 秒之後才繼續執行。

   

yield()

Thread 的源碼可以知道 yield() 為本地方法,也就是說 yield() 是由 C C++ 實現的,源碼如下:

public static native void yield();

   

yield() 方法表示給線程調度器一個當前線程願意出讓 CPU 使用權的暗示,但是線程調度器可能會忽略這個暗示。

   

比如我們執行這段包含了 yield() 方法的代碼,如下所示:

public static void main(String[] args) throws InterruptedException {

    Runnable runnable = new Runnable() {

        @Override

        public void run() {

            for (int i = 0; i < 10; i++) {

                System.out.println(線程: +

                        Thread.currentThread().getName() +  I + i);

                if (i == 5) {

                    Thread.yield();

                }

            }

        }

    };

    Thread t1 = new Thread(runnable, “T1”);

    Thread t2 = new Thread(runnable, “T2”);

    t1.start();

    t2.start();

}

   

當我們把這段代碼執行多次之後會發現,每次執行的結果都不相同,這是因為 yield() 執行非常不穩定,線程調度器不一定會採納 yield() 出讓 CPU 使用權的建議,從而導致了這樣的結果。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

福斯汽車、SMA Solar傳聯手發展電動車

福斯汽車(Volkswagen)深陷柴油車排氣造假案使得品牌形象受重創,為了早點擺脫「柴油門」陰影,轉移消費者的眼光,福斯近來大力推動電動車,雄心勃勃的發展電動車的同時,傳出找來德國逆變器大廠SMA Solar 結盟。

SMA Solar 成立於1981 年,公司名來自「系統、測量、系統工程」(System, Mess and Anlagentechnik)德文簡寫,為全球最大逆變器廠,逆變器應用於直流電交流電轉換,不僅應用於太陽能,也應用於電池能源儲存系統,SMA Solar 市場地位穩固,特斯拉(Tesla)於2016 年5 月宣布Powerwall 將進行小規模規格提升,其重點之一就是改為可支援SMA Solar 產品。

福斯在「柴油門」事件後,積極將事業目標轉向電動車,以便洗刷受到作假事件影響的聲譽,投注數十億歐元資金,大力發展零碳排放的純電動車以及隨選共乘服務,計劃在2025 年以前推出30 款電動車,攻佔四分之一的全球電動車市場。

但是分析師指出,福斯在2015 年只不過出貨6.7 萬輛電動車與油電混合車,以這樣的市佔與進度,要在2025 年攻佔四分之一電動車市場的目標可能很難達成,尤其是競爭對手早已起步,為了追上豐田(Toyota)與雷諾(Renault)日產(Nissan)聯盟,福斯需要找尋強大的合作夥伴。

德國地方日報《黑森下薩克森大眾報》(Hessische Niedersaechsische Allgemeine)於2016 年8 月初報導,福斯尋求能源儲存與綠能大廠SMA Solar 的合作。

福斯的電動車廠表示,SMA Solar 在靜態能源儲存領域相當強勢,對福斯來說,尋求正確的合作夥伴,將是發展電動車的重要關鍵。不過,SMA Solar 目前已經與賓士製造商戴姆勒(Daimler)以及特斯拉在能源儲存方面合作,目前對於福斯的可能合作消息不表示意見。

(本文授權自《》──〈〉。照片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司“嚨底家”!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

新能源車夯,比亞迪前三季獲利有望跳增九成

中國電動汽車商比亞迪(BYD)周日公布,2016年前九個月盈餘最高有望年增逾九成(91%),主要受惠於中國政策大力推動節能汽車。

比亞迪有股神巴菲特光環加持,上個月又獲得三星入股,可謂左右逢源。據比亞迪表示,上半年獲利來到23億人民幣,較去年同期跳增384%,若連同本季獲利,今年前三季預估將成長83-91%。

比亞迪以製造電池起家,2003年併購秦川汽車後跨足汽車市場,並為日後進軍電動車鋪路。憑藉著電池技術領先同業,比亞迪在中國新能源車領域已先立於不敗之地,近年隨著環保法規轉嚴,比亞迪生產之油電混合車與全電動車業績更是蒸蒸日上。

三星七月下旬以約30億人民幣參與比亞迪新一輪募資,亦是著眼於中國新能源車的商機。不過在三星正式入股後,巴菲特掌舵的柏克夏海瑟威公司(Berkshire Hathaway)對比亞迪的持股比例,則從原先的9.1%稀釋至8.25%。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

聚甘新

IEA:今之電動車有如10年前的太陽能

英國金融時報31日報導,瑞銀(UBS)預估2021年歐洲未經補貼的純電動車整體持有成本將與傳統內燃機汽車相當、中國也可望在2025年達到這項里程碑,美國則因油價相對低廉、在可預見的未來都無法看到。國際能源署(IEA)首席經濟學家Laszlo Varro指出,電動車目前對商品(原油)市場的影響力大約就像是10年前的太陽能一樣。他說,太陽能現在已是一個規模達數百億美元的市場、擁有龐大的影響力。Varro提到,電動車需達5千萬至1億台的規模、才能取代相當於100萬桶的石油日消費量。

IEA數據顯示,2009年全球40個國家合計僅有不到6千台的電動車、去年已升至120萬台。麥格理集團全球能源策略師Vikas Dwivedi指出,沙烏地阿拉伯對電動車的長期發展存有戒心,這可能就是它為何宣布將讓沙烏地阿拉伯國家石油公司(Saudi Aramco)初次公開發行(IPO)的原因之一。

CNNMoney去年底報導,石油輸出國組織(OPEC)發表的年度「世界石油展望(World Oil Outlook)」報告顯示,2040年高達94%的使用中車輛仍將是依靠石油燃料。OPEC報告顯示,除非出現重大技術性突破,否則在可預見的未來電動車將難以大幅取得市佔率。油國組織預估2040年僅有1%的車輛銷售量是來自純電動車款。

華爾街日報6月報導,根據能源咨詢公司Wood Mackenzie發布的報告,未來20年電動車的普及可能會導致美國汽油需求縮減5-20%。美國目前平均每天的汽油使用量超過900萬桶。報告顯示,如果電動車2035年市占比重因特斯拉(Tesla)等廠商開始推出較低價車款以及續航力和便利性(註:目前大約每100英里就得充電一次)顯著突破而站上35%,美國日需求量可能會減少200萬桶。

即便電動車現在還沒有茁壯到可以侵蝕汽油需求的地步,美國依舊面臨油品供給過剩的問題。

美國石油協會(API)8月18日公布,2016年7月衡量消費者汽油需求的指標(汽油交運量)創下史上最高紀錄、日均量接近970萬桶。美國7月汽油日均產量同樣創下歷史新高、年增1.9%(月增1.3%)至1,020萬桶!

能源情報署(EIA)公布,截至2016年8月19日為止當週美國商用汽油庫存報2.327億桶、較去年同期高出8.5%。EIA公布的數據顯示,美國一般汽油每加侖零售均價8月22日報2.193美元,較一年前短少0.444美元。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

聚甘新

中國大陸推排放標準,控管油電混和車排放

大陸環保部30日發布涉及船舶發動機、摩托車、輕便摩托車、輕型混合動力電動汽車和燒鹼、聚氯乙烯(PVC)工業排放的五項國家污染物排放標準,分別為《船舶發動機排氣污染物排放限值及測量方法(中國第1、2階段)》、《摩托車污染物排放限值及測量方法(中國第4階段)》、《輕便摩托車污染物排放限值及測量方法(中國第4階段)》、《輕型混合動力電動汽車污染物排放控制要求及測量方法》和《燒鹼、聚氯乙烯工業污染物排放標準》。上述標準旨在落實《大氣污染防治行動計畫》和《水污染防治行動計畫》,透過制定、修訂重點行業的排放標準力促產業轉型升級。

大陸環保部科技標準司司長鄒首民表示,實施這五項標準將可望大幅削減顆粒物(PM)、氮氧化物(NOx)、二氧化硫(SO2)的污染,有效促進行業技術進步和環境品質的改善。他指出,新標準適用於具有中國船籍在大陸水域航行或作業的船舶裝用的額定淨功率大於37千瓦、新生產船用發動機的環境管理,不適用於遠洋船舶,遠洋運輸船舶執行國際公約的相關規定。

鄒首民指出,上述新標準實施後,按照每年新增船機1,000萬千瓦,壽命期為25年計算,實施第一階段標準3年,所製造投入使用的船機在全壽命期內將減排NOX約140萬噸,PM約40萬噸;若實施第二階段標準3年,也將進一步減排NOX約115萬噸,PM約6萬噸。

另外,對於產量增長迅速的摩托車污染問題也正在引起重視。鄒首民說,為能有效控制摩托車污染,環保部制定了摩托車和輕便摩托車國四標準,自2019年7月1日起,所有新銷售和註冊登記的摩托車和輕便摩托車,應滿足新的標準;以國四標準實施3年估算,這期間,新生產的全部摩托車在其整個使用壽命內,將比實施國三標準減少CO排放約650萬噸、HC排放約200萬噸、NOx排放約30萬噸。

他也指出,近年來大陸國家積極鼓勵發展包括混合動力電動汽車在內的節能與新能源汽車,但由於有電能的輔助,傳統汽車的測量方法無法準確評判混合動力電動汽車的污染物排放狀況;新標準是對《輕型混合動力電動汽車污染物排放測量方法》的修訂,規定了輕型混合動力電動汽車的污染控制要求和測量方法,新標準適用於國四、國五階段的輕型混合動力汽車的環保管理,該測量方法標準的實施,將不會帶來額外的車輛技術升級成本。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

Tesla台灣旗艦店開幕,Model S 2017年在台上市

不論是在汽車、能源還是科技界,不論發生什麼事情都在大眾目光焦點的特斯拉 (Tesla),既三月份成立臺灣分公司之後有更進一步的動作。特斯拉的臺北旗艦店在今日 (9 月1 日) 在臺北信義商圈開幕,有興趣的人可以到店體驗特斯拉房車Model S,並且預約試駕和進一步訂購,預計明年 (2017) 第一季交車。

特斯拉看中臺灣在科技產業的樞鈕地位,因此在亞太區暨中國、香港、澳門、澳洲之後,選擇臺灣,成為特斯拉全球第25 個設立的國家。而在信義商圈新光三越A11 的台灣旗艦店,則是第204 家店。選在臺灣的首善之都臺北,則是著眼信義商圈是臺北最繁盛的購物商圈,對新事物的接受度高,因此選在新光三越A11 設旗艦店,並且還跟新光三越合作,在百貨公司的地下停車場設立充電站。

電動車要能方便,必須有足夠的的充電站。Tesla香港、澳門及台灣地區區域總監范菁怡表示,目前除了設在車主自家的居家充電站之外,還會與百貨商場、飯店合作,在停車場設置目的地充電站。未來特斯拉也將在高速公路沿線設置超級充電站,服務需長距離駕駛的特斯拉車子。

由於特斯拉代表新一代的潔淨能源運用型式,對於提高都市運輸效率和降低污染有顯著效果,因此除了特斯拉的代表和合作的百貨公司代表以外,臺北市政府也由副市長林欽榮出席。另外還有負責招商相關事務的行政院全球招商及攬才聯合服務中心何怡明執行長,以及經濟部工業局黃裕峰副組長出席。林欽榮致詞時表示,臺北市仍然要繼續推動共享汽車,U-Car 計畫。

儘管目前在臺灣還只能預購特斯拉的Model S 房車車型,但是特斯拉計畫在臺引進平價車款Model 3,定價3 萬5,000 美元,預計明年引進,目前正在商品檢驗階段。

「電動車勢必是未來交通方式的主流,Tesla 一直期望扮演推動永續能源應用的角色。我們樂見台灣對環保、綠色能源的高接受度,也很高興Tesla 能成為這波改革中的一股力量。」Tesla 全球副總裁暨亞太區總裁任宇翔表示:「臺灣特殊的地理和城市發展環境,以及政府對於相關政策和補助的支持,讓Tesla 清楚看到電動車在臺灣的發展性。臺灣廠商不僅是Tesla供應鏈的合作夥伴,更在電動車的推廣上佔有重要地位。隨著Tesla 電動車在台上市與充電站的逐步擴充,Tesla 對臺灣市場的發展潛力深具信心,除了希望對合作夥伴和經濟發展帶來正面影響,更期望與臺灣一起開創電動車的新篇章。」

特斯拉之後將在臺灣設立客服,還有佈建充電網路,當特斯拉的Model S 在臺灣的道路奔馳時,超級充電站和目的地充電站在商圈、景點等地方,形成足夠的網狀分佈,維持特斯拉的輪胎不停轉動。全球有超過9,000 萬臺汽車,目前只有不到0.2% 的汽車是電動車,特斯拉推動電動車,還可以有很大的施力空間。

(首圖:設在新光三越信義A11 地下4 樓停車場的特斯拉目的地充電站,在2017 年特斯拉車子在臺灣道路奔馳之際,這類設在購物商場的充電站將會越來越多。來源:科技新報)

(本文授權轉載自《》──〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

聚甘新

.Net Core服務監控報警指標上報Prometheus+Grafana

前言

簡單集成Prometheus+Grafana,指標的上報收集可視化。

Prometheus

Prometheus是一個監控平台,監控從HTTP端口收集受監控目標的指標。在微服務的架構里Prometheus多維度的數據收集是非常強大的 我們首先下載安裝Prometheusnode_exporter,node_exporter用於監控CPU、內存、磁盤、I/O等信息

  • Prometheus下載地址
  • node_exporter下載地址

下載完成后解壓以管理員運行 prometheus.exe 訪問 http://localhost:9090/ 出現一下頁面說明啟動成功啦

.Net Core獲取指標

有了Prometheus,我們還需要給Prometheus提供獲取監控數據的接口,我們新建一個WebApi項目,並導入prometheus-net.AspNetCore包,在Configure中加入UseMetricServer中間件

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

    app.UseMetricServer();
    
}

啟動項目訪問http://localhost:5000/metrics就可以看基本的一些監控信息啦,包括線程數,句柄數,3個GC的回收計數等信息。

# HELP process_num_threads Total number of threads
# TYPE process_num_threads gauge
process_num_threads 29
# HELP process_working_set_bytes Process working set
# TYPE process_working_set_bytes gauge
process_working_set_bytes 44441600
# HELP process_private_memory_bytes Process private memory size
# TYPE process_private_memory_bytes gauge
process_private_memory_bytes 69660672
# HELP dotnet_total_memory_bytes Total known allocated memory
# TYPE dotnet_total_memory_bytes gauge
dotnet_total_memory_bytes 2464584
# HELP dotnet_collection_count_total GC collection count
# TYPE dotnet_collection_count_total counter
dotnet_collection_count_total{generation="1"} 0
dotnet_collection_count_total{generation="0"} 0
dotnet_collection_count_total{generation="2"} 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1592448124.2853072
# HELP process_open_handles Number of open handles
# TYPE process_open_handles gauge
process_open_handles 413
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2225187631104
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 1.171875

Help 是收集指標的說明,Type收集指標的類型

但是作為HTTP應用怎麼能沒有HTTP的監控和計數呢,只需要加加入UseHttpMetrics中間件就可以對HTTP請求監控和計數,主要注意的是UseHttpMetrics最好放在UseEndpointsUseRouting中間

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMetricServer();
    
    app.UseRouting();
    
    app.UseHttpMetrics();

    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

啟動項目繼續訪問http://localhost:5000/metrics

# HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair.
# TYPE http_requests_in_progress gauge

可以看到已經有了,我們隨便請求一下服務看看效果,會幫我們記錄下總耗時,總請求數,和每次請求的耗時數

但是單單有上面那些數據好像還不太好定位一下很奇葩的問題,這時候我們可以獲取Runtime的一些數據,方法童謠很簡單。導入prometheus-net.DotNetRuntime 包,它可以幫助我們看到如下指標

  • 垃圾回收的收集頻率和時間
  • 服務佔用堆大小
  • 對象堆分配的字節
  • JIT編譯和JIT CPU消耗率
  • 線程池大小,調度延遲以及增長/縮小的原因
  • 鎖爭用情況

我們只需要在ProgramMain方法中啟動收集器就可以啦。

public static void Main(string[] args)
{
    DotNetRuntimeStatsBuilder.Default().StartCollecting();
    CreateHostBuilder(args).Build().Run();
}

啟動項目繼續訪問http://localhost:5000/metrics測試一下

# HELP dotnet_collection_count_total GC collection count
# TYPE dotnet_collection_count_total counter
dotnet_collection_count_total{generation="1"} 0
dotnet_collection_count_total{generation="0"} 0
dotnet_collection_count_total{generation="2"} 0
# HELP process_private_memory_bytes Process private memory size
# TYPE process_private_memory_bytes gauge
process_private_memory_bytes 75141120
# HELP dotnet_gc_pause_ratio The percentage of time the process spent paused for garbage collection
# TYPE dotnet_gc_pause_ratio gauge
dotnet_gc_pause_ratio 0
# HELP http_requests_received_total Provides the count of HTTP requests that have been processed by the ASP.NET Core pipeline.
# TYPE http_requests_received_total counter
# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections
# TYPE dotnet_gc_collection_seconds histogram
dotnet_gc_collection_seconds_sum 0
dotnet_gc_collection_seconds_count 0
dotnet_gc_collection_seconds_bucket{le="0.001"} 0
dotnet_gc_collection_seconds_bucket{le="0.01"} 0
dotnet_gc_collection_seconds_bucket{le="0.05"} 0
dotnet_gc_collection_seconds_bucket{le="0.1"} 0
dotnet_gc_collection_seconds_bucket{le="0.5"} 0
dotnet_gc_collection_seconds_bucket{le="1"} 0
dotnet_gc_collection_seconds_bucket{le="10"} 0
dotnet_gc_collection_seconds_bucket{le="+Inf"} 0
# HELP dotnet_total_memory_bytes Total known allocated memory
# TYPE dotnet_total_memory_bytes gauge
dotnet_total_memory_bytes 4925936
# HELP dotnet_threadpool_num_threads The number of active threads in the thread pool
# TYPE dotnet_threadpool_num_threads gauge
dotnet_threadpool_num_threads 0
# HELP dotnet_threadpool_scheduling_delay_seconds A breakdown of the latency experienced between an item being scheduled for execution on the thread pool and it starting execution.
# TYPE dotnet_threadpool_scheduling_delay_seconds histogram
dotnet_threadpool_scheduling_delay_seconds_sum 0.015556
dotnet_threadpool_scheduling_delay_seconds_count 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.001"} 0
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.01"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.05"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.1"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.5"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="1"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="10"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="+Inf"} 10
# HELP process_working_set_bytes Process working set
# TYPE process_working_set_bytes gauge
process_working_set_bytes 50892800
# HELP process_num_threads Total number of threads
# TYPE process_num_threads gauge
process_num_threads 32
# HELP dotnet_jit_method_seconds_total Total number of seconds spent in the JIT compiler
# TYPE dotnet_jit_method_seconds_total counter
dotnet_jit_method_seconds_total 0
dotnet_jit_method_seconds_total{dynamic="false"} 0.44558800000000004
dotnet_jit_method_seconds_total{dynamic="true"} 0.004122000000000001
# HELP dotnet_gc_pinned_objects The number of pinned objects
# TYPE dotnet_gc_pinned_objects gauge
dotnet_gc_pinned_objects 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1592449942.6063592
# HELP dotnet_gc_heap_size_bytes The current size of all heaps (only updated after a garbage collection)
# TYPE dotnet_gc_heap_size_bytes gauge
# HELP http_request_duration_seconds The duration of HTTP requests processed by an ASP.NET Core application.
# TYPE http_request_duration_seconds histogram
# HELP dotnet_contention_seconds_total The total amount of time spent contending locks
# TYPE dotnet_contention_seconds_total counter
dotnet_contention_seconds_total 0
# HELP dotnet_gc_pause_seconds The amount of time execution was paused for garbage collection
# TYPE dotnet_gc_pause_seconds histogram
dotnet_gc_pause_seconds_sum 0
dotnet_gc_pause_seconds_count 0
dotnet_gc_pause_seconds_bucket{le="0.001"} 0
dotnet_gc_pause_seconds_bucket{le="0.01"} 0
dotnet_gc_pause_seconds_bucket{le="0.05"} 0
dotnet_gc_pause_seconds_bucket{le="0.1"} 0
dotnet_gc_pause_seconds_bucket{le="0.5"} 0
dotnet_gc_pause_seconds_bucket{le="1"} 0
dotnet_gc_pause_seconds_bucket{le="10"} 0
dotnet_gc_pause_seconds_bucket{le="+Inf"} 0
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2225201872896
# HELP dotnet_gc_finalization_queue_length The number of objects waiting to be finalized
# TYPE dotnet_gc_finalization_queue_length gauge
dotnet_gc_finalization_queue_length 0
# HELP dotnet_threadpool_io_num_threads The number of active threads in the IO thread pool
# TYPE dotnet_threadpool_io_num_threads gauge
dotnet_threadpool_io_num_threads 3
# HELP process_open_handles Number of open handles
# TYPE process_open_handles gauge
process_open_handles 436
# HELP dotnet_gc_collection_reasons_total A tally of all the reasons that lead to garbage collections being run
# TYPE dotnet_gc_collection_reasons_total counter
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.890625
# HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair.
# TYPE http_requests_in_progress gauge
# HELP dotnet_threadpool_adjustments_total The total number of changes made to the size of the thread pool, labeled by the reason for change
# TYPE dotnet_threadpool_adjustments_total counter
# HELP dotnet_jit_cpu_ratio The amount of total CPU time consumed spent JIT'ing
# TYPE dotnet_jit_cpu_ratio gauge
dotnet_jit_cpu_ratio 0.5728901224489797
# HELP process_cpu_count The number of processor cores available to this process.
# TYPE process_cpu_count gauge
process_cpu_count 8
# HELP dotnet_build_info Build information about prometheus-net.DotNetRuntime and the environment
# TYPE dotnet_build_info gauge
dotnet_build_info{version="3.3.1.0",target_framework=".NETCoreApp,Version=v5.0",runtime_version=".NET Core 5.0.0-preview.2.20160.6",os_version="Microsoft Windows 10.0.18363",process_architecture="X64"} 1
# HELP dotnet_jit_method_total Total number of methods compiled by the JIT compiler
# TYPE dotnet_jit_method_total counter
dotnet_jit_method_total{dynamic="false"} 830
dotnet_jit_method_total{dynamic="true"} 30
# HELP dotnet_gc_cpu_ratio The percentage of process CPU time spent running garbage collections
# TYPE dotnet_gc_cpu_ratio gauge
dotnet_gc_cpu_ratio 0
# HELP dotnet_threadpool_scheduled_total The total number of items the thread pool has been instructed to execute
# TYPE dotnet_threadpool_scheduled_total counter
dotnet_threadpool_scheduled_total 16
# HELP dotnet_gc_allocated_bytes_total The total number of bytes allocated on the small and large object heaps (updated every 100KB of allocations)
# TYPE dotnet_gc_allocated_bytes_total counter
dotnet_gc_allocated_bytes_total{gc_heap="soh"} 3008088
dotnet_gc_allocated_bytes_total{gc_heap="loh"} 805392
# HELP dotnet_contention_total The number of locks contended
# TYPE dotnet_contention_total counter
dotnet_contention_total 0

可以看到非常多的信息啦,但是我們有時候不需要這麼多指標也可以自定義。


public static void Main(string[] args)
{
    DotNetRuntimeStatsBuilder
        .Customize()
        .WithContentionStats()
        .WithJitStats()
        .WithThreadPoolSchedulingStats()
        .WithThreadPoolStats()
        .WithGcStats()
        .StartCollecting();
    CreateHostBuilder(args).Build().Run();
}

JIT,GC和線程的監控是會影響到一點點性能,我們可以通過sampleRate這個枚舉的值來控制採樣頻率

public static void Main(string[] args)
{
    DotNetRuntimeStatsBuilder
        .Customize()
        //每5個事件個採集一個
        .WithContentionStats(sampleRate: SampleEvery.FiveEvents)
        //每10事件採集一個
        .WithJitStats(sampleRate: SampleEvery.TenEvents)
        //每100事件採集一個
        .WithThreadPoolSchedulingStats(sampleRate: SampleEvery.HundredEvents)
        .WithThreadPoolStats()
        .WithGcStats()
        .StartCollecting();
    CreateHostBuilder(args).Build().Run();
}

有了這些指標我們需要Prometheus來收集我們Api的指標,只需要修改prometheus.yml文件然後重啟Prometheus就可以了。

scrape_configs:  
- job_name: mydemo  
  scrape_interval: 15s  
  scrape_timeout: 10s  
  metrics_path: /metrics  
  scheme: http  
  static_configs:  
  - targets:  
    - localhost:5000   

啟動Api項目和Prometheus,選中dotnet_collection_count_total點擊Excute可以看到Api的指標是正常上報的。

Prometheus有了數據我們就需要一個炫酷的UI去展示上報的數據啦。

Grafana

Prometheus有了數據就差一個漂亮的UI來展示的我們的指標了。Grafana是一個Go編寫的開源應用,用於把指標數據可視化。是當下流行的時序數據展示工具。先下載,直接下載exe安裝,完成后能打開http://localhost:3000/頁面就安裝成功了

  • 下載地址

先添加數據源,選擇Prometheus為數據源,並配置。

添加儀錶盤

Import via panel json中加入下面這個json,點擊load,

  • 儀錶盤json

選擇數據源,點擊Import就能看到儀錶盤了

還可以去這裏添加很多現有的儀錶盤。複製ID添加儀錶盤。

參考文章

prometheus-net
.NetCore下使用Prometheus實現系統監控和警報系列

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

聚甘新

超詳細Maven技術應用指南

該文章,GitHub已收錄,歡迎老闆們前來Star!

GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual

搜索關注微信公眾號“碼出Offer”,送你學習福利資源!

一、前言

在我們的項目資源中,你會發現需要導入的jar包越來越多,讓jar包的管理越來越沉重。它會表現為以下幾個缺點:

  • 每個項目都需要手動搜集和導入所需要的jar包
  • 項目中用到的jar包有版本更新,我們需要重新搜集並導入到項目中
  • 相同的jar包導入到不同的項目中,jar包會在本地存儲多份

針對上述問題,我們就需要使用統一的管理工具:Maven

二、了解Maven

2.1 什麼是Maven

Maven是一個基於項目對象模型(POM)的概念的純Java開發的開源的項目管理工具。主要用來管理Java項目,進行依賴管理(jar包依賴管理)和項目構建(項目編譯、打包、測試、部署)。此外還能分模塊開發,提高開發效率。

2.2 Maven的下載安裝

關於Maven的下載,我們需要下載它的解壓包。

Maven下載地址: https://us.mirrors.quenda.co/apache/maven/maven-3/3.6.3/binaries/

image-20200616171323409

下載后將Maven解壓到目錄中就可以了!

注意: 解壓的目錄與tomact服務器的形式是一樣的,不要有中文及特殊符號!

image-20200616171637526

2.3 Maven目錄結構解析

目錄名稱 描述
bin 存儲mvn的各種可執行文件
boot 含有plexus-classworlds類加載器框架,Maven 使用該框架加載自己的類庫
conf 存放settings.xml等配置文件
lib 存儲Maven運行時所需要的Java類庫
LICENSE/NOTICE/README.txt 針對Maven的版本、第三方軟件等簡要介紹

2.4 配置環境變量

Maven依賴Java環境的配置環境,所以要確保jdk版本在1.7以上,maven版本在3.3以上。

  • 配置環境變量與jdk環境變量配置是一樣的,在本機中創建MAVEN_HOME環境變量,並將maven的解壓路徑設置進去,點擊確定(路徑參考上圖解壓后的結果圖路徑)
  • 修改path環境變量,添加%MAVEN_HOME%\bin后,一路點擊確定即可!

2.5 測試

下載解壓、配置環境變量后,我們打開DOS命令窗口,鍵入mvn -v查看maven版本信息

  • 如果看到如下圖片maven的版本信息,證明maven安裝配置成功!
  • 在Maven的版本信息你就可以得知它依賴於jdk環境!

image-20200616172931556

2.6 Maven項目模型圖

三、Maven的配置

3.1 配置本地倉庫

本地倉庫簡單來說,就是在本地的maven中存儲管理所需jar包

  1. 首先,打開maven目錄conf文件夾中的settings.xml配置文件

  2. 其次,找到標號1的那一行配置信息,並複製此配置信息放在其下面

  3. 然後,在磁盤中創建一個目錄,作為存儲jar文件的本地倉庫

  4. 最後,將複製的此配置信息路徑替換成自己創建的本地倉庫目錄路徑,參考標號2的操作

image-20200616174928266

3.2 配置jdk

3.2.1 全局配置

由於Maven依賴於jdk環境,所以我們也需要在maven中配置jdk(我使用的jdk是主流的1.8版本)

  1. 打開settings.xml配置文件,找到<profiles>標籤,你會發現標籤內都是註釋的內容,我們需要在標籤內,寫入自己的jdk的配置信息。配置如下:
  2. 在maven中添加好jdk的配置信息后,我們需要在</profiles>結束標籤后添加<activeProfiles>標籤內容,讓配置好的<profiles>標籤中內容生效

注意: profile標籤中的id是此配置信息的名稱,在後面使用activeProfile標籤讓其配置生效的時候,需要保證id與activeProfile的名稱一致!(貼圖供大家參考!)

    <!-- 配置jdk -->
    <profile>
        <id>jdk1.8</id>
        <activation>
            <activeByDefault>true</activeByDefault>    
            <jdk>1.8</jdk>    
        </activation>    
        <properties>    
            <maven.compiler.source>1.8</maven.compiler.source>    
            <maven.compiler.target>1.8</maven.compiler.target>
            <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> 
        </properties>    
    </profile>
<!-- 使配置好的profiles標籤中內容生效 -->
<activeProfiles>
    <activeProfile>jdk1.8</activeProfile>
</activeProfiles>

image-20200616181057029

3.2.2 單個項目修改

後面我們會了解到maven項目是通過pom.xml進行構建信息配置和依賴信息配置。其中就包括配置編譯需要的jdk版本。所以我們直接修改pom文件就可以實現單個項目修改,但是我們並不推薦此種方式,因為這個方式需要每個項目都要修改,不具有可重用性!

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
</build>

四、倉庫

4.1 倉庫概念

  • 存儲依賴的地方,體現形式就是本地的一個目錄。
  • 倉庫中不僅存放依賴,而且管理着每個依賴的唯一標識(坐標),Java項目憑坐標獲取依賴。

4.2 倉庫分類

Maven倉庫可以分為本地倉庫和遠程倉庫,其中遠程倉庫又分為中央倉庫、公共倉庫和私服

  • 本地倉庫: 本地倉庫存放着項目中所需jar文件
  • 中央倉庫: Maven的中央倉庫是由Maven社區提供存儲jar文件的倉庫
  • 公共倉庫: 國內廠家提供的存儲jar文件的倉庫,比如:aliyun倉庫
  • 私服: 由公司創建的存儲jar文件的倉庫,可在公司範圍內共享,不對外開放

當項目中需要jar文件依賴時,會從倉庫中查找獲取,如果我們把所有倉庫都配置好。maven在查找獲取依賴的時候遵循一個依賴查找順序,如下:(如果本地倉庫找不到依賴就去私服下載,以此類推……)

依賴查找順序: 本地倉庫 – > 私服 – > 公共倉庫 – > 中央倉庫

image-20200616184606968

4.3 本地倉庫

本地倉庫: 本地目錄中存放所需jar包,需修改settings配置文件來配置本地倉庫

  • 使用過的依賴都會存儲在本地倉庫中,實現復用

4.4 遠程倉庫

4.4.1 中央倉庫

中央倉庫: Maven中央倉庫是由Maven社區提供的倉庫,不用任何配置,maven中內置了中央倉庫地址。其中就包含絕大多數流行的開源Java構件

  • https://mvnrepository.com/可以搜索需要的依賴的相關信息(倉庫搜索服務)
  • http://repo.maven.apache.org/maven2/為中央倉庫地址
4.4.2 公共倉庫

公共倉庫: 第三方維護的jar文件倉庫,比如阿里雲提供的倉庫。但是jar文件可能不如官方的中央倉庫全,有時候也會找不到,所以如果項目構建不成功,可以更改鏡像為官方的,下載完jar包再去改回來

  • aliyun倉庫地址:http://maven.aliyun.com/nexus/content/groups/public/

  • 因為Maven社區提供的中央倉庫在國外,國內使用下載依賴速度過慢,所以一般我們都配置國內的公共倉庫來代替中央倉庫

  • 使用時,需要在settings.xml配置文件中添加配置信息。打開settings.xml配置文件,找到<mirrors>標籤,你也會發現這是一個空標籤,最後標籤內填寫如下配置就OK!

    <!--setting.xml中添加如下配置-->
    <mirror>
        <id>aliyun</id>  
        <!-- 中心倉庫的 mirror(鏡像) -->
        <mirrorOf>central</mirrorOf>    
        <name>Nexus aliyun</name>
        <!-- aliyun倉庫地址 以後所有要指向中心倉庫的請求,都會指向aliyun倉庫-->
        <url>http://maven.aliyun.com/nexus/content/groups/public</url>  
    </mirror>

image-20200616223651045

4.4.3 私服

私服: 公司創建存儲jar文件的倉庫,只對公司範圍共享,不對外開放。可以通過Nexus來創建、管理一個私服。

4.4.3.1 私服概念
  • 私服是架設在局域網的一種特殊的遠程倉庫,目的是代理遠程倉庫及部署第三方構件。

  • 有了私服之後,當 Maven 需要下載依賴時,直接請求私服,私服上存在則下載到本地倉庫;否則,私服請求外部的遠程倉庫,將構件下載到私服,再提供給本地倉庫下載。

  • 私服可以解決在企業做開發時每次需要的jar包都要在中心倉庫下載,且每次下載完只能被自己使用,不能被其他開發人員使用

  • 所謂私服就是一個服務器,但是不是本地層面的,是公司層面的,公司中所有的開發人員都在使用同一個私服

4.4.3.2 私服架構

我們可以使用專門的 Maven 倉庫管理軟件來搭建私服,比如:Apache Archiva,Artifactory,Sonatype Nexus。這裏我們使用Sonatype Nexus

  • 我們可以在圖中得到在無私服的情況下,我們都需要從遠程倉庫去獲取jar文件並存儲在本地倉庫,由於中央倉庫是國外的,所以下載速度比較慢等等原因,並存在很多缺點,比如我們公司內使用
  • 我們在圖中可清晰的看到,如果有私服的話,我們需要從私服中,查找jar文件,如果私服沒有該jar文件,就需要去中央倉庫或公共倉庫去下載,然後傳到私服中,最後傳入到自己的本地倉庫進行使用。假設公司員工再一次使用到該jar文件,它會先從私服中找有沒有這個jar文件,由於我們之前的員工已經將該jar文件存儲到了私服中,所以就省去了其他員工調用遠程倉庫的步驟。並且私服是公司內部局域網類型的,下載速度會比遠程倉庫快出很多倍
無私服 有私服
私服1 私服2
4.4.3.3 Nexus安裝(了解即可)
  • Nexus官網地址:https://blog.sonatype.com/

  • 下載地址:https://help.sonatype.com/repomanager2/download/download-archives—repository-manager-oss

下載我們需要下載Zip解壓包即可,將解壓包解壓到本地盤符中即可!

解壓后,你會看到nexus目錄為私服目錄,sonatype-work目錄中包含存儲私服下載的依賴。

4.4.3.4 Nexus登錄

訪問私服:http://localhost:8081/nexus/

登錄私服的賬號為admin,密碼為admin123

4.4.3.5 倉庫列表
倉庫類型 描述
group 包含多個倉庫,通過group庫的地址可以從包含的多個倉庫中查找構件
hosted 私服 服務器本地的倉庫,其中存儲諸多構件
proxy 代理倉庫,其會關聯一個遠程倉庫, 比如中央倉庫,aliyun倉庫,向該倉庫查找構件時,如果沒有會從其關聯的倉庫中下載
倉庫名 描述
Releases 存放項目的穩定發布版本,一個模塊做完后如果需要共享給他人,可以上傳到私服的該庫
Snapshots 對應不穩定的發布版本
3rd party 存放中央倉庫沒有的 ,如ojdbc.jar,可以上傳到私服的該庫中
倉庫列表
私服_list
4.4.3.6 倉庫組

而此時就有問題,私服中有很多倉庫,每個倉庫都有自己的url,則項目中配置哪個倉庫呢 ?

私服中有一個倉庫組,組中包含多個倉庫,可以指定倉庫組的url,即可從多個倉庫中獲取構件

關於倉庫的設置: 由於我們在使用私服的時候,本地倉庫沒有的jar文件,需要去私服找,私服沒有的話,就去中央倉庫找。所以我們需要把私服內的中央倉庫換為阿里雲倉庫,這樣可以保證我們國內的下載速度。

倉庫組 注意:proxy的倉庫排序在最後
私服_deploy2
4.4.3.7 手動上傳倉庫
4.4.3.8 Maven關聯私服

配置settings.xml,設置私服地址、認證等信息(關聯私服需要添加配置文件信息如下,找到父標籤,添加子標籤內容即可)

<servers>
    <server> 
        <id>nexus-public</id> <!-- nexus的認證id -->
        <username>admin</username> <!--nexus中的用戶名密碼-->
        <password>admin123</password> 
    </server>
</servers>
<profiles>
    <profile> 
        <id>nexus</id> 
        <repositories> 
            <repository> 
                <id>nexus-public</id> <!--nexus認證id 【此處的repository的id要和 <server>的id保持一致】-->
                <!--name隨便-->
                <name>Nexus Release Snapshot Repository</name> 
                <!--地址是nexus中倉庫組對應的地址-->
                <url>http://localhost:8081/nexus/content/groups/public/</url>
                <releases><enabled>true</enabled></releases> 
                <snapshots><enabled>true</enabled></snapshots> 
            </repository>
        </repositories> 
        <pluginRepositories> <!--插件倉庫地址,各節點的含義和上面是一樣的-->
            <pluginRepository> 
                <id>nexus-public</id> <!--nexus認證id 【此處的repository的id要和 <server>的id保持一致】-->
                <!--地址是nexus中倉庫組對應的地址-->
                <url>http://localhost:8081/nexus/content/groups/public/</url>
                <releases><enabled>true</enabled></releases> 
                <snapshots><enabled>true</enabled></snapshots> 
            </pluginRepository> 
        </pluginRepositories> 
    </profile>
</profiles>
<activeProfiles>
    <activeProfile>yourjdk</activeProfile>
    <!-- 使私服配置生效 -->
    <activeProfile>nexus</activeProfile>
</activeProfiles>
4.4.3.9 Meven項目部署到私服
  • 執行mvn deploy指令即可將項目部署到私服對應的倉庫中,此時項目中的打包方式多為jar
  • 但需要提前在項目的pom.xml中配置部署私服倉庫位置,如下:

注意: 如上的 repository的 id 依然是要和settings.xml中配置的server中的id 一致,才能通過私服的認證

<project>
        ...
    <dependencies>
        .....
    </dependencies>

    <!-- 在項目的pom.xml中 配置私服的倉庫地址,可以將項目打jar包部署到私服 -->
    <distributionManagement>
        <repository>
            <id>nexus-public</id> <!-- nexus認證id -->
            <url>http://localhost:8081/nexus/content/repositories/releases</url>
        </repository>
        <snapshotRepository>
            <id>nexus-public</id> <!-- nexus認證id -->
            <url>http://localhost:8081/nexus/content/repositories/snapshots</url>
        </snapshotRepository>
    </distributionManagement>
</project>

五、IDEA中的Maven操作

5.1 創建Maven項目

創建Maven項目
image-20200616215847109
指定項目名稱和項目位置
image-20200616220455702
5.1.1 節點配置解析
節點 詳細描述
groupId 這是項目組的編號,這在組織或項目中通常是獨一無二的。 例如,一家銀行集團 com.company.bank擁有所有銀行相關項目。
artifactId 這是項目的 ID。這通常是項目的名稱。 例如,consumer-banking。 除了 groupId 之外,artifactId 還定義了 artifact 在存儲庫中的位置。
version 這是項目的版本。與 groupId 一起使用,artifact 在存儲庫中用於將版本彼此分離。 例如:com.company.bank:consumer-banking:1.0com.company.bank:consumer-banking:1.1
5.1.2 語義化版本號
  • 規則:正式穩定版本從v0.1.0開始,配套軟件公共API
  • 注意:正式版發布后不可修改,只能在下一個版本中發布新內容
版本類型 詳細描述
主要版本 當你做了不兼容的API 修改(正式版發布、架構升級)
次要版本 當你做了向下兼容的功能性新增(功能增減)
修訂版本 當你做了向下兼容的問題修正(BUG修復、查缺補漏)
5.1.3 擴展(SNAPSHOT)

在使用maven過程中,我們在開發階段經常性的會有很多公共庫處於不穩定狀態,隨時需要修改併發布,可能一天就要發布一次,遇到bug時,甚至一天要發布N次。我們知道,maven的依賴管理是基於版本管理的,對於發布狀態的artifact,如果版本號相同,即使我們內部的鏡像服務器上的組件比本地新,maven也不會主動下載的。如果我們在開發階段都是基於正式發布版本來做依賴管理,那麼遇到這個問題,就需要升級組件的版本號,可這樣就明顯不符合要求和實際情況了。但是,如果是基於快照版本,那麼問題就自熱而然的解決了,而maven已經為我們準備好了這一切。

maven中的倉庫分為兩種,snapshot快照倉庫和release發布倉庫。snapshot快照倉庫用於保存開發過程中的不穩定版本,release正式倉庫則是用來保存穩定的發行版本。定義一個組件/模塊為快照版本,只需要在pom文件中在該模塊的版本號后加上-SNAPSHOT即可(注意這裏必須是大寫),如下:

<groupId>cc.mzone</groupId>
<artifactId>m1</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>

maven會根據模塊的版本號(pom文件中的version)中是否帶有-SNAPSHOT來判斷是快照版本還是正式版本。如果是快照版本,那麼在mvn deploy時會自動發布到快照版本庫中,會覆蓋老的快照版本,而在使用快照版本的模塊,在不更改版本號的情況下,直接編譯打包時,maven會自動從鏡像服務器上下載最新的快照版本。如果是正式發布版本,那麼在mvn deploy時會自動發布到正式版本庫中,而使用正式版本的模塊,在不更改版本號的情況下,編譯打包時如果本地已經存在該版本的模塊則不會主動去鏡像服務器上下載

在maven的約定中,依賴的版本分為兩類——SNAPSHOT和RELEASE。SNAPSHOT依賴泛指以-SNAPSHOT為結尾的版本號,例如1.0.1-SNAPSHOT。除此之外,所有非-SNAPSHOT結尾的版本號則都被認定為RELEASE版本,即正式版,雖然會有beta、rc之類說法,但是這些只是軟件工程角度的測試版,對於maven而言,這些都是RELEASE版本。所以一般我們需要上傳到發布倉庫的時候可以在<version>標籤內直接寫版本即可,不需要再添加任何標籤!

5.2 IDEA關聯Maven

在IDEA中關聯本地安裝的maven,後續就可以通過idea來使用maven管理項目(我使用的aliyun倉庫)

在全局設置中關聯Maven
image-20200616222756521
Maven項目展示 (缺少test包下resources文件夾)
image-20200616224937892

5.3 IDEA創建測試包下resources文件夾

我們在使用IDEA創建Maven項目時,IDEA是沒有幫我們創建test包下的resources文件夾。但是Maven規範中是包含這個文件夾的,所以我們需要手動創建並聲明該文件夾

創建存放測試配置的文件夾
image-20200616225355373
指定文件夾名稱 (下拉框選擇resources文件夾創建即可)
image-20200616225859663
文件目錄結構展示 (完整Maven規範目錄結構)
image-20200616230042781

5.4 Maven項目目錄結構解析

注意: 項目中的創建包、創建類、執行,都與普通項目無異

目錄名稱 描述
src/main/java 用於創建包,存放編寫的源代碼(.java文件)
src/main/resources 存放項目中所需配置文件,比如:c3p0.properties
src/test/java 用於創建包,存放編寫的測試代碼(.java文件)
src/test/resources 存放項目中測試代碼所需配置文件
根目錄/pom.xml 項目對象模型(project object model),maven項目核心文件,其中定義項目構建方式,聲明依賴等

5.5 Maven項目類型

根據項目類型,在pom.xml文件中添加相應配置。

  • 項目類型分為Java項目和JavaWeb項目

  • 如果項目為Java項目需要在<project>標籤內添加 jar

  • 如果項目為JavaWeb項目需要在<project>標籤內添加 war

注意: Maven可以根據項目類型來確定打包方式,比如Java項目打包成jar包JavaWeb項目打包成war包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mylifes1110</groupId>
    <artifactId>firstmaven</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--
        設置項目類型,打包方式:
        如果為Java項目則使用jar
        如果為JavaWeb項目使用war
    -->
    <packaging>jar</packaging>
<!--    <packaging>war</packaging>-->
</project>

5.6 IDEA中導入依賴

5.6.1 導入依賴須知

建好項目后,需要導入需要的jar包,要通過坐標來在倉庫中查找導入

  • 每個構件都有自己的坐標,其坐標分為groupId、artifactId、version 三種,翻譯後為項目標識、項目名稱、版本號
  • 在maven項目中只需要配置坐標,maven便會自動加載對應依賴。刪除坐標則會移除依賴
節點 描述
project 工程的根標籤。
modelVersion 模型版本需要設置為 4.0。
groupId 這是工程組的標識。它在一個組織或者項目中通常是唯一的。例如,一個銀行組織 com.companyname.project-group 擁有所有的和銀行相關的項目。
artifactId 這是工程的標識。它通常是工程的名稱。例如,消費者銀行。groupId 和 artifactId 一起定義了 artifact 在倉庫中的位置。
version 這是工程的版本號。在 artifact 的倉庫中,它用來區分不同的版本。例如:com.company.bank:consumer-banking:1.0 com.company.bank:consumer-banking:1.1
5.6.2 查找依賴

依賴查找服務需要在如下網址中查找依賴,獲取依賴坐標后,在maven項目中的pom.xml文件中導入

  • 依賴查找服務地址: https://mvnrepository.com/
查找jar文件
image-20200616111418839
jar文件的選擇
image-20200616112444879
Copy依賴坐標
image-20200617003002005
5.6.3 導入依賴

在項目的pom.xml文件中添加依賴

  • 首先添加<dependencies>標籤
  • 最後添加複製好的依賴坐標

注意: 導入依賴可以導入多個所需jar文件的依賴,依次在<dependencies>標籤內添加依賴坐標即可

導入依賴
image-20200617003853705
5.6.4 同步依賴

導入依賴后,你會發現pom.xml文件中的依賴坐標是紅色報錯的,而在IDEA的右下角有那麼一個框。這時候需要點擊框內提示信息在maven倉庫中同步下載依賴到項目中

同步下載依賴到項目中
image-20200617004515463
5.6.5 查看依賴

導入並同步下載好的依賴,可以通過項目和IDEA中Maven控制面板查看

查看依賴
image-20200617005635304

5.7 基於Maven創建Web項目

5.7.1 修改web項目打包方式

pom.xml文件中設置 <packaging>war/packaging>修改打包方式為web項目

5.7.2 導入web依賴

基於Maven項目,我們導入了maven所需依賴,如果創建web項目的話,還需要導入JSPServletJSTL依賴,使Maven項目具有web編譯環境。(在pom.xml文件中添加以下依賴)

        <dependency>
            <!-- jstl 支持 -->
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <!-- servlet編譯環境 -->
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <!-- jsp編譯環境 -->
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>

image-20200617123210482

5.7.3 創建web項目目錄結構

web項目在IDEA中有web規範目錄文件夾的,比如webapp、WEB-INF、web.xml、index.jsp。而由於Maven項目中沒有該規範目錄,這就需要我們自己創建目錄結構了!

創建web項目目錄結構有以下注意點:

  • webapp文件夾必須是基於main目錄下創建的,與java文件夾同級
  • webapp文件夾IDEA自動識別此文件夾,給與了特殊藍色圓點標識
  • 在WEB-INF目錄下創建的的web.xml是一個空的xml文件,我們需要在該文件中鍵入如下web.xml空白模板信息,只需要將下面的模板複製到項目中的web.xml文件中即可!
  • 創建的文件夾以及文件名稱千萬不要寫錯!

注意: 在創建項目的時候,其實我們是可以指定使用IDEA來創建來創建web項目的目錄結構的,創建項目時需要勾選Create from archetype。只是IDEA為我們構建的項目架構是有版本差異的,而且還附加了很多對我們無用的註解等等,所以我們一般都手動創建,IDEA自動構建作為了解就好!

xml空白模板

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
</web-app>
IDEA自動構建項目結構
image-20200617164915737
手動構建web項目結構
基於main目錄下創建webapp文件夾
image-20200617124249878
image-20200617124319926
基於webapp目錄創建WEB-INF文件夾
image-20200617124624648
基於WEB-INF目錄創建web.xml文件
image-20200617125038803
image-20200617125055216
xml文件內容展示
image-20200617125729115
基於webapp目錄創建index.jsp文件
image-20200617125613861
目錄展示 (完整的web項目目錄結構)
image-20200617125808227
5.7.4 tomact的引入

關於Tomact服務的引入,需要我們手動添加tomact服務

添加tomact服務后,如果對tomact服務器在IDEA中的開發流程不熟悉的小夥伴,不要灰心。請參考tomact服務器基礎和開發步驟即可,此文章中詳細講到了關於tomact的各種知識點!

添加tomact服務
image-20200617131801103

5.8 pom文件的其他標籤操作

5.8.1 build標籤修改默認打包名

默認的打包名稱:artifactid+verson.打包方式

我們可以通過build中finalName修改,如下操作:

<build>
  <finalName>maven_name</finalName>
</build>  
5.8.2 引入插件

dependencies引入開發需要的jar包,有時候我們還需要引入一些常用的插件,比如:jdk、tomact、分頁插件等等

插件配置位置也是在<build>標籤中,如下:

<build>
<plugins>
    <!-- java編譯插件,配jdk的編譯版本 -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>
    <!-- tomcat插件 -->
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <configuration>
        <port>8080</port>
        <path>/</path>
        <uriEncoding>UTF-8</uriEncoding>
        <server>tomcat7</server>
      </configuration>
    </plugin>
  </plugins>
</build>
5.8.3 控制打包資源

如果在java文件夾中添加java類,會自動打包編譯到classes文件夾下。但是xml文件默認不會被打包,需要我們手動指定打包。可以使用<resources>標籤來指定要打包資源的文件夾要把哪些靜態資源打包到classes根目錄下

<!--打包指定靜態資源-->
<build>
<resources>
    <resource>
      <!-- 指定要打包資源的文件夾 要把哪些靜態資源打包到 classes根目錄下-->
      <directory>src/main/resources</directory>
      <includes>
        <include>**/*.xml</include>
        <include>**/
*.properties</include>
      </includes>
    </resource>
    <resource>
      <directory>src/main/resources</directory>
      <excludes>
        <exclude>spring/*</exclude>
      </excludes>
      <includes>
        <include>*.xml</include>
        <!--<include>*/
*.properties</include>-->
      </includes>
    </resource>
  </resources>
</build>

六、依賴的生命周期

6.1 什麼是依賴的生命周期

jar文件的生效時間段可以成為依賴的生命周期

6.2 依賴的生命周期分類與詳解

前三個是常用的依賴生命周期設置,而後兩個製作了解即可,幾乎用不到!

標識 周期
compile 缺省值(默認依賴生命周期),適用於所有階段(測試運行,編譯,運行,打包)
provided 類似compile,期望JDK、容器或使用者會提供這個依賴。如servlet-api.jar;適用於(測試運行,編譯)階段
test 只在測試時使用,適用於(編譯,測試運行)階段,如 junit.jar
runtime 依賴在編譯器不使用,只在運行時使用,如 mysql的驅動jar,適用於(運行,測試運行)階段
system Maven不會在倉庫中查找對應依賴,在本地磁盤目錄中查找;適用於(編譯,測試運行,運行)階段

6.3 依賴生命周期的使用

關於依賴生命周期的使用,需要在期望指定生命周期的依賴內添加<scope>標籤,在此標籤內添加所需依賴標識。

complie

jstl依賴默認沒有compile標識的生命周期。因為在依賴中不指定生命周期就是默認指定適用於所有階段的生命周期,其默認標識為compile。

image-20200617135847510

provided

servlet和jsp依賴默認指定provided標識的生命周期。因為我們在servlet或jsp代碼時是需要這兩個依賴的,但是我們將項目部署到tomact中,本地tomact目錄的lib文件夾下也會有一些jar文件,所以這造成了一種依賴衝突。為了避免這種依賴衝突我們需要指定依賴的生命周期為編譯和測試運行階段。這樣我們在書寫代碼時,編譯期也有有依賴可以使用,不會飄紅,而在過了編譯期後項目部署到了tomact中,該依賴聲生命就會被結束掉了,不會影響tomact服務器內置依賴的使用!

image-20200617135818392

test

在maven項目中,項目結構是區分main主文件和test測試文件的。如果我們在使用Junit單元測試時,指定依賴的生命周期為test,那該依賴只適用於test測試文件內,在其他文件的階段默認沒有單元測試的依賴。

簡單來說,在test文件夾內創建的測試類,使用@Test註解不會有任何問題。如果換做在main文件夾和其他文件夾中創建測試類,使用@Test註解就會因沒有依賴注入而報錯。

image-20200617140515452

runtime

我們在添加mysql驅動的依賴時,你會發現它並沒有指定生命周期為runtime。這是因為我們在書寫jdbc工具類的操作時,如果在編譯期沒有mysql驅動的依賴,它並不會飄紅報錯。如果沒有依賴只有在我們運行的時候才會發生報錯,並告知mysql驅動依賴未找到。所以,這就顯得runtime這個依賴生命周期十分的雞肋。因此,可以不指定該生命周期。

image-20200617141145812

system

當依賴的生命周期設置為system時,表示該依賴項是我們自己提供的,不需要Maven到倉庫裏面去找。
指定scope為system需要與另一個屬性元素systemPath一起使用,它表示該依賴項在當前系統的位置,使用的是絕對路徑。由於此類依賴不是通過 Maven 倉庫解析的,而且往往與本機系統綁定,可能造成構建的不可移植,因此應該慎用。systemPath 元素可以引用環境變量。

image-20200617142301138

七、maven的構建命令

7.1 項目的構建過程

7.2 常用構建命令

一般命令的鍵入在IDEA中的框中鍵入命令就可以!

image-20200617170717904

命令 描述
mvn compile(常用) 編譯項目,生成target文件
mvn package(常用) 打包項目,生成war或jar文件
mvn clean(常用) 清理編譯或打包后的項目結構
mvn install(常用) 打包後上傳到Maven本地倉庫
mvn source:jar 打包項目,生成jar包
mvn deploy 只打包,不測試
mvn site 生成站點
mvn test 執行測試源碼

7.3 maven項目的生命周期

maven項目的生命周期分為了三個階段,而這三個階段相互獨立、互不影響

  1. 清理生命周期(Clean Lifecycle): 該生命周期負責清理項目中多餘信息,保持資源和代碼的整潔性。一般用來清空目錄下的文件
  2. 默認構建生命周期(Default Lifeclyle): 該生命周期表示項目的構建過程,其中定義了一個項目構建要經歷的不同階段
  3. 站點管理生命周期(Site Lifecycle): site生命周期的目的是建立和發布項目站點,Maven能夠基於POM所包含的信息,自動生成一個友好的站點

clean

該生命周期主要是對項目編譯生成的文件進行清理,清理的話主要是清理編譯後項目中的target文件夾,清理后還可以通過編譯指令重新生成。

  • 命令: mvn clean

default

該生命周期的主要目的是項目的編譯 -> 測試 -> 打包 -> 發布

  • 命令: mvn deploy
  • 其中default生命周期分為23個階段,如下列舉比較重要的幾個階段
  • validate:驗證工程是否正確,所有需要的資源是否可用。
  • compile:編譯項目的源代碼。
  • test:使用合適的單元測試框架來測試已編譯的源代碼。這些測試不需要已打包和布署。
  • Package:把已編譯的代碼打包成可發布的格式,比如jar。
  • integration-test:如有需要,將包處理和發布到一個能夠進行集成測試的環境。
  • verify:運行所有檢查,驗證包是否有效且達到質量標準。
  • install:把包安裝到maven本地倉庫,可以被其他工程作為依賴來使用。
  • Deploy:在集成或者發布環境下執行,將最終版本的包拷貝到遠程的repository,使得其他的開發者或者工程可以共享。

site

該生命周期的主要是建立和發布項目站點,Maven能夠基於POM所包含的信息,自動生成一個友好的站點

  • 命令: mvn site

注意: 低版本的site插件可能引發失敗現象!升級高版本site插件即可!

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-site-plugin</artifactId>
  <version>3.7.1</version>
</plugin>

7.4 maven命令與插件的關係

maven命令是操作maven項目的重要方式,但是我們要知道maven命令只是支配maven插件工作的一個方式,其工作核心主要還是maven插件。maven內嵌了項目操作的插件,我們只需要通過執行命令來調用插件來完成項目的編譯、測試、發布等工作

注意: 執行一次命令可能會觸發多個插件工作

八、傳遞依賴和衝突解決

8.1 什麼是傳遞依賴

假如有Maven項目A,項目B依賴A,項目C依賴B。那麼我們可以說 C依賴A。也就是說,依賴的關係為:C – > B – > A, 那麼我們執行項目C時,會自動把B、A都下載導入到C項目的jar包文件夾中,這就是依賴的傳遞性。

8.2 什麼是依賴衝突

依賴衝突我在上文中也提到過,依賴衝突是當直接引用或者間接引用出現了相同的jar包擁有不同版本的時候。

舉個例子:A依賴於B,B依賴於C,此時C的版本為V1.0;如果此時引入的C依賴還有一個V2.0,那麼我們的A傳遞依賴於C,此時C的版本為V2.0。這時候就是一個衝突,直接或間接的都引用了C,而C版本有兩個!

8.3 手動解決依賴衝突

如果我不想在C依賴中出現B,那麼就可以主動的使用依賴排除技術,排除B依賴的引用,如下操作需要添加排除依賴的配置信息(<exclusions>標籤和其中信息,其中兩個id標籤是需要排除依賴的id):

     <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.12.RELEASE</version>

            <!--手動排除依賴-->
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>

        </dependency>
     </dependencies>

8.4 自動解決依賴衝突

8.4.1 自動解決依賴衝突的途徑

自動解決依賴通途問題的兩個途徑分為:短路優先原則先聲明優先原則

8.4.2 短路優先原則

首先,先看如下依賴關係

  1. A – > B – > C – > D – > E(V1.0)
  2. A – > F – > E(V2.0)

解釋: 1中,A依賴於B,B依賴於C,C依賴於D,D依賴於E,此時E的版本為V1.0版本(2中解釋基本相似)

所謂短路優先,就是路徑段的依賴有限被加載使用,這裏我們可以看到2中的路徑是最短的路徑,所以maven在加載依賴的時候,是使用2中的這一條依賴和版本

8.4.3 先聲明優先原則

針對依賴路徑長度相同的情況,則使用先聲明優先原則,看如下依賴關係

  1. A – > B – > C(V1.0)
  2. A – > D – > C(V2.0)

此時路徑優先原則不能解決問題時,maven需要判斷在A項目的<depencies>標籤內,B和D的哪個依賴聲明在前面,如果B依賴聲明早於D依賴,那麼就使用1中的這一條依賴和版本

微信公眾號 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司“嚨底家”!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

循序漸進VUE+Element 前端應用開發(11)— 圖標的維護和使用,循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理,循序漸進VUE+Element 前端應用開發(9)— 界面語言國際化的處理

在VUE+Element 前端應用中,圖標是必不可少點綴界面的元素,因此整合一些常用的圖標是非常必要的,還好Element界面組件裏面提供了很多常見的圖標,不過數量不是很多,應該是300個左右吧,因此考慮擴展更多圖標,我引入了vue-awesome組件,它利用了Font Awesome的內置圖標,實現了更多圖標的整合,可以在項目中使用更多的圖標元素了,另外在本隨筆的圖標管理中,提供了對圖標名稱進行搜索,並根據圖標顏色樣式生成對應圖標的代碼,非常方便使用。

Vue-Awesome 是基於 Vue.js 的 SVG 圖標組件,內置圖標來自  Font Awesome。

本篇隨筆先來上一個圖標管理的界面效果,然後在逐一進行介紹Element內置圖標和Vue-Awesome的圖標吧。

Element圖標管理界面如下:

基於Vue-Awesome的圖標管理如下所示。

其中查詢提供了名稱進行圖標查詢,以及限制一次性展示多少個,另外提供一個自定義顏色選項,從而可以改變圖標的展示顏色。

  

1、Vue-Awesome的使用介紹

  Vue-Awesome 的 npm的安裝命令如下所示:

npm install vue-awesome

安裝方式如下所示

import Vue from 'vue'

/* 在下面兩種方式中任選一種 */

// 僅引入用到的圖標以減小打包體積
import 'vue-awesome/icons/flag'

// 或者在不關心打包體積時一次引入全部圖標
import 'vue-awesome/icons'

/* 任選一種註冊組件的方式 */

import Icon from 'vue-awesome/components/Icon'

// 全局註冊(在 `main .js` 文件中)
Vue.component('v-icon', Icon)

// 或局部註冊(在組件文件中)
export default {
  components: {
    'v-icon': Icon
  }
}

界面使用代碼如下所示,詳細Demo可以參考https://justineo.github.io/vue-awesome/demo/ 了解。

<!-- 基礎用法 -->
<v-icon name="beer"/>

<!-- 添加選項 -->
<v-icon name="sync" scale="2" spin/>
<v-icon name="comment" flip="horizontal"/>
<v-icon name="code-branch" label="Forked Repository"/>

<!-- 堆疊圖標 -->
<v-icon label="No Photos">
  <v-icon name="camera"/>
  <v-icon name="ban" scale="2" class="alert"/>
</v-icon>

Vue-Awesome圖標提供了一些prop的屬性設置,參考下面列表所示。

  • name: string

    圖標名稱。如果本組件沒有用作圖標堆棧的容器,那麼這個字段是必須的。所有合法的值都對應於圖標文件相對於 icons 目錄的路徑。請注意當你在 FontAwesome 官網查找到圖標名詞后,需要確認一下圖標集的名稱。比如,在 500px 這個圖標的詳情頁會有一個 URL 參數 style=brands,故圖標名稱就是 brands/500px

    默認情況下,只能使用 FontAwesome 的免費圖標,另外由於 solid 樣式中的圖標最多,我們將其設為了默認圖標集,所以路徑前綴可以省略。

    當傳入 null 時,整個組件將不會渲染。

  • scale: number|string

    用來調整圖標尺寸,默認值為 1

  • spin: boolean

    用來指定圖標是否需要旋轉。默認值為 false。(不能與 pulse 一同使用。)

  • pulse: boolean

    用來指定圖標是否有脈衝旋轉的效果。默認值為 false。(不能與 spin 一同使用。)

  • inverse: boolean

    為 true 時圖標顏色將被設置為 #fff。默認值為 false

  • flip: 'vertical'|'horizontal'|'both'

    用來指定圖標是否需要翻轉。

  • label: string

    當指定時會設置圖標的 aria-label

  • title: string

    為圖標設置標題。

另外,我們也可以註冊自定義圖標,如下所示。

import Icon from 'vue-awesome/components/Icon'

Icon.register({
  baidu: {
    width: 23.868,
    height: 26,
    d: 'M3.613 13.701c2.827-.608 2.442-3.986 2.357-4.725-.138-1.139-1.477-3.128-3.296-2.971C.386 6.21.052 9.515.052 9.515c-.309 1.528.74 4.793 3.561 4.186zm3.002 5.875c-.083.238-.268.846-.107 1.375.315 1.187 1.346 1.24 1.346 1.24h1.48v-3.619H7.749c-.713.213-1.057.767-1.134 1.004zM8.86 8.035c1.562 0 2.823-1.797 2.823-4.019C11.683 1.796 10.421 0 8.86 0 7.301 0 6.036 1.796 6.036 4.016c0 2.222 1.265 4.019 2.824 4.019zm6.724.265c2.087.271 3.429-1.956 3.695-3.644.272-1.686-1.074-3.644-2.552-3.98-1.48-.339-3.329 2.032-3.497 3.578-.2 1.89.271 3.778 2.354 4.046zm5.114 9.923s-3.229-2.498-5.113-5.198c-2.555-3.981-6.185-2.361-7.399-.337-1.209 2.024-3.093 3.305-3.36 3.644-.271.334-3.9 2.293-3.095 5.871.806 3.576 3.635 3.508 3.635 3.508s2.085.205 4.504-.336c2.42-.537 4.503.134 4.503.134s5.652 1.893 7.199-1.751c1.545-3.645-.874-5.535-.874-5.535zm-9.671 5.423H7.352c-1.587-.316-2.219-1.4-2.299-1.584-.078-.188-.528-1.059-.29-2.539.686-2.219 2.642-2.379 2.642-2.379h1.956V14.74l1.666.025v8.881zm6.844-.025h-4.229c-1.639-.423-1.716-1.587-1.716-1.587v-4.677l1.716-.027v4.203c.104.447.661.529.661.529h1.742v-4.705h1.825v6.264zm5.986-12.486c0-.808-.671-3.239-3.159-3.239-2.492 0-2.825 2.295-2.825 3.917 0 1.548.131 3.71 3.227 3.641 3.096-.068 2.757-3.507 2.757-4.319z'
  }
})

如果你的 SVG 文件有多個路徑或多邊形,以及/或者你想預定義一些樣式,可以用如下方式進行註冊:

路徑方式:

import Icon from 'vue-awesome/components/Icon'

Icon.register({
  webpack: {
    width: 1200,
    height: 1200,
    paths: [
      {
        style: 'fill:#8ED6FB',
        d: 'M1035.6 879.3l-418.1 236.5V931.6L878 788.3l157.6 91zm28.6-25.9V358.8l-153 88.3V765l153 88.4zm-901.5 25.9l418.1 236.5V931.6L320.3 788.3l-157.6 91zm-28.6-25.9V358.8l153 88.3V765l-153 88.4zM152 326.8L580.8 84.2v178.1L306.1 413.4l-2.1 1.2-152-87.8zm894.3 0L617.5 84.2v178.1l274.7 151.1 2.1 1.2 152-87.8z'
      },
      {
        style: 'fill:#1C78C0',
        d: 'M580.8 889.7l-257-141.3v-280l257 148.4v272.9zm36.7 0l257-141.3v-280l-257 148.4v272.9zm-18.3-283.6zM341.2 436l258-141.9 258 141.9-258 149-258-149z'
      }
    ]
  }
})

多邊形方式:

import Icon from 'vue-awesome/components/Icon'
Icon.register({
  vue: {
    width: 256,
    height: 221,
    polygons: [
      {
        style: 'fill:#41B883',
        points: '0,0 128,220.8 256,0 204.8,0 128,132.48 50.56,0 0,0'
      },
      {
        style: 'fill:#35495E',
        points: '50.56,0 128,133.12 204.8,0 157.44,0 128,51.2 97.92,0 50.56,0'
      }
    ]
  }
})

對於自定義的這些圖標,我們可以把它們一起放在一個獨立的JS文件裏面進行定義,然後全局統一加入即可。

 

 使用上和其他的圖標沒有差異,只是名稱不同而已。

    <div style="height:100px;padding:10px">
      <span>vue-Awesome 自定義圖標:(在utils/awesome-icon-customed.js中引入)</span>
      <v-icon name="baidu" scale="2" :style="iconStyle" />
      <v-icon name="vue" scale="2" />
      <v-icon name="webpack" scale="2" spin />
      <v-icon name="html5-c" scale="2" spin />
    </div>

 

2、導入Element 圖標和Vue-Awesome圖標

在我們進行頁面管理的時候,我們需要提取Element 圖標和Vue-Awesome圖標,以便能夠進行界面的展示處理。

Element圖標,我們只需要在一個JS文件裏面,包含它的名稱進去一個列表裡面即可,如下定義所示。

const elementIcons = [
  'platform-eleme', 'eleme', 'delete-solid', 'delete', ..........
]

export default elementIcons

在其中錄入對應Element的圖表名稱,移除el-icon-的前綴即可構成所需列表的每項內容。

而對於Vue-Awesome圖標,我們安裝對應的組件后,它的圖標資源肯定也一定下載下來了,我們找到對應的node_modules 目錄下的文件就可以看到了,如下截圖所示。

 可以看到它的每個圖標對應一個js文件,而且不同樣式還有不同的目錄的,我們只需要自動加入對應的文件名稱即可。

定義一個js文件,如vue-awesome-icon.js,用來獲取對應Awesome圖標名稱,如下邏輯所示 

// Vue-Awesome圖標自動加入
const req = require.context('vue-awesome/icons', true, /\.js$/)
const requireAll = requireContext => requireContext.keys()

const re = /\.\/(.*)\.js/

const vueAwesomeIcons = requireAll(req).filter((key) => {
  return key.indexOf('index.js') < 0
}).map(i => {
  return i.match(re)[1]
})

export default vueAwesomeIcons

通過 require.context 的操作以及僅需filter的數組過濾,我們就可以獲得對應的Awesome圖標名稱了。

在管理頁面裏面,我們需要引入Element和Vue-Awesome的圖標管理文件,如下所示。

import elementIcons from './element-icons' // 引入Element圖標
import vueAwesomeIcons from './vue-awesome-icons' // 引入vue-awesome圖標

然後構造頁面的data數據,如下所示,其中searchForm 是用來承載查詢條件的。

export default {
  name: 'Icons',
  data() {
    return {
      // 查詢表單
      searchForm: {
        label: '',
        pagesize: 50,
        color: '#409EFF'
      },
      // 自定義svg圖標集合
      svgIcons,
      // element圖標集合
      elementIcons,
      // vueAwesome圖標集合
      vueAwesomeIcons
    }
  },

然後增加幾個Computed函數,用來處理數據的過濾查詢等操作。

 computed: {
    iconStyle: function() {
      return { color: this.searchForm.color }
    },
    elementIconsFiltered: function() {
      const that = this
      var list = that.elementIcons.filter(item => {
        return item.indexOf(that.searchForm.label) >= 0
      })
      if (that.searchForm.pagesize > 0) {
        return list.slice(0, that.searchForm.pagesize)
      } else {
        return list
      }
    },
    vueAwesomeIconsFiltered: function() {
      const that = this
      var list = that.vueAwesomeIcons.filter(item => {
        return item.indexOf(that.searchForm.label) >= 0
      })
      if (that.searchForm.pagesize > 0) {
        return list.slice(0, that.searchForm.pagesize)
      } else {
        return list
      }
    }
  },

然後在method裏面,對常規的圖標進行生成處理即可。

  methods: {
    generateElementIconCode(symbol) {
      return `<i class="el-icon-${symbol}" style="color:${this.searchForm.color};"/>`
    },
    generateAwesomeIconCode(symbol) {
      return `<v-icon name="${symbol}" style="color:${this.searchForm.color};"/>`
    },
    handleClipboard(text, event) {
      if (text) {
        clipboard(text, event)
      }
    }
  }

對於界面的展示,我們以Vue-awesome圖標展示為例介紹,如下所示。

      <el-tab-pane label="Vue-Awesome 圖標">
        <div
          v-for="item of vueAwesomeIconsFiltered"
          :key="item"
          @click="handleClipboard(generateAwesomeIconCode(item),$event)"
        >
          <el-tooltip placement="top">
            <div slot="content">{{ generateAwesomeIconCode(item) }}</div>
            <div class="icon-item">
              <v-icon :name="item" scale="2" :style="iconStyle" />
              <span>{{ item }}</span>
            </div>
          </el-tooltip>
        </div>
      </el-tab-pane>

這樣就可以實現對應圖標的動態過濾和展示了。

 

 

列出一下前面幾篇隨筆的連接,供參考:

循序漸進VUE+Element 前端應用開發(1)— 開發環境的準備工作

循序漸進VUE+Element 前端應用開發(2)— Vuex中的API、Store和View的使用

循序漸進VUE+Element 前端應用開發(3)— 動態菜單和路由的關聯處理

循序漸進VUE+Element 前端應用開發(4)— 獲取後端數據及產品信息頁面的處理

循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理

循序漸進VUE+Element 前端應用開發(6)— 常規Element 界面組件的使用

循序漸進VUE+Element 前端應用開發(7)— 介紹一些常規的JS處理函數

循序漸進VUE+Element 前端應用開發(8)— 樹列表組件的使用

循序漸進VUE+Element 前端應用開發(9)— 界面語言國際化的處理

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

聚甘新

Spring系列.事務管理

Spring提供了一致的事務管理抽象。這個抽象是Spring最重要的抽象之一, 它有如下的優點:

  • 為不同的事務API提供一致的編程模型,如JTA、JDBC、Hibernate和MyBatis數據庫層 等;
  • 提供比大多數事務API更簡單的,易於使用的編程式事務管理API;
  • 完美整合Spring數據訪問抽象;
  • 支持Spring聲明式事務管理;

這篇博客就來介紹Spring事務管理相關的內容。

事務簡介

什麼是事務

事務(Transaction)一般是指對數據庫的一個或一組操作單元。

事務的作用

1、為數據庫操作提供了一個從失敗中恢復到正常狀態的方法,同時提供了數據庫即使在異常狀態下仍能保持一致性的方法。
2、當多個應用程序在併發訪問數據庫時,可以在這些應用程序之間提供一個隔離方法,以防止彼此的操作互相干擾。

當一個事務被提交給了DBMS(數據庫管理系統),則DBMS需要確保該事務中的所有操作都成功完成且其結果被永久保存在數據庫中,如果事務中有的操作沒有成功完成,則事務中的所有操作都需要被回滾,回到事務執行前的狀態(要麼全執行,要麼全都不執行);同時,該事務對數據庫或者其他事務的執行無影響,所有的事務都好像在獨立的運行。

事務的特點

事務具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。

原子性(Atomicity):事務作為一個整體被執行,包含在其中的對數據庫的操作要麼全部被執行,要麼都不執行。
一致性(Consistency):事務應確保數據庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是數據庫中的數據應滿足完整性約束。
隔離性(Isolation):多個事務併發執行時,一個事務的執行不應影響其他事務的執行。
持久性(Durability):一個事務一旦提交,他對數據庫的修改應該永久保存在數據庫中。

事務的隔離級別

在多個事務併發操作的過程中,如果控制不好隔離級別,就有可能產生臟讀、不可重複讀或者幻讀等讀現象。數據操作過程中利用數據庫的鎖機制或者多版本併發控制機制獲取更高的隔離等級。但是,隨着數據庫隔離級別的提高,數據的併發能力也會有所下降。所以,如何在併發性和隔離性之間做一個很好的權衡就成了一個至關重要的問題。

ANSI/ISO SQL定義的標準隔離級別有四種,從高到底依次為:可序列化(Serializable)、可重複讀(Repeatable reads)、提交讀(Read committed)、未提交讀(Read uncommitted)。

  1. 讀未提交
    未提交讀(READ UNCOMMITTED)是最低的隔離級別。通過名字我們就可以知道,在這種事務隔離級別下,一個事務可以讀到另外一個事務未提交的數據。未提交讀會導致臟讀

事務在讀數據的時候並未對數據加鎖。

務在修改數據的時候只對數據增加行級共享鎖。

  1. 讀已提交
    提交讀(READ COMMITTED)也可以翻譯成讀已提交,通過名字也可以分析出,在一個事務修改數據過程中,如果事務還沒提交,其他事務不能讀該數據。讀已提交會導致不可重複讀

事務對當前被讀取的數據加 行級共享鎖(當讀到時才加鎖),一旦讀完該行,立即釋放該行級共享鎖;
事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

  1. 可重複讀
    可重複讀能保障一個事務在事務內讀到的某條數據是一致的。但是可重複讀不能解決幻讀的問題。就是在事務還沒結束時,其他事務又插入了一條新的數據。

事務在讀取某數據的瞬間(就是開始讀取的瞬間),必須先對其加 行級共享鎖,直到事務結束才釋放;
事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放

  1. 序列化
    可序列化(Serializable)是最高的隔離級別,前面提到的所有的隔離級別都無法解決的幻讀,在可序列化的隔離級別中可以解決。

事務在讀取數據時,必須先對其加 表級共享鎖 ,直到事務結束才釋放;
事務在更新數據時,必須先對其加 表級排他鎖 ,直到事務結束才釋放。

下面是臟讀、不可重複讀和幻讀的解釋。

臟讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交(commit)到數據庫中,這時,另外一個事務也訪問這個數據,然後使用了這個數據。因為這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是臟數據,依據臟數據所做的操作可能是不正確的。
不可重複讀:在一個事務內,多次讀同一個數據。在這個事務還沒有結束時,另一個事務也訪問該同一數據。那麼,在第一個事務的兩次讀數據之間。由於第二個事務的修改,那麼第一個事務讀到的數據可能不一樣,這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為不可重複讀,即原始讀取不可重複。
幻讀指的是一個事務在前後兩次查詢同一個範圍的時候,后一次查詢看到了前一次查詢沒有看到的數據行。幻讀專指“新插入的行”是不可重複讀(Non-repeatable reads)的一種特殊場景

Spring事務

Spring事務模型的優勢

事務可以分為本地事務和全局事務,這兩種事務都有一定程度的局限性,Spring框架的事務管理支持解決全局和本地事務模型的局限性。

1. 全局事務
全局事務可以讓你跨多個事務進行工作,比如你的事務同事包含多個關係型數據庫,也可以包含關係型數據庫和JMS事務。一般情況下都是通過JTA來實現全局事務,但是JTA一般需要具體的應用容器來支持,這就導致代碼的通用性較低。

下面舉個全局事務的列子,方便理解。

在電商網站上,在消費者點擊購買按鈕后,交易後台會進行庫存檢查、下單、減庫存、更新訂單狀態等一連串的服務調用,每一個操作對應一個獨立的服務,服務一般會有獨立的數據庫,因此會產生分佈式事務問題。分佈式事務就是一種比較常見的全局事務。

2. 本地事務

本地事務和具體的某個事務關聯,比如說JDBC事務。本地事務比較簡單,但是不能實現分佈式事務的功能。

Spring提供了統一方便的事務編程模型,可以解決上面本地事務和全局事務的局限。使用Spring的事務API進行事務管理,底層可以適應各種事務資源。

Spring事務抽象

Spring為提供統一的事務編程模型,提供相關的接口。主要接口如下:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

上面接口的getTransaction方法接收一個TransactionDefinition參數,返回一個TransactionStatus 值。其中TransactionStatus 可能代表一個新的事務,或者返回一個已經存在本次調用棧中的事務。(TransactionStatus 和具體的線程綁定。可以自己寫代碼測試下)

TransactionStatus接口定義如下。

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();
}

聲明式事務管理

使用Spring的事務管理,推薦使用聲明式事務管理。Spring的聲明式事務管理是通過Spring的AOP功能實現的。

因為平時在開發過程中都是使用註解的方式使用聲明式事務。下面就介紹註解的方式。

step1:添加@EnableTransactionManagement註解

@Configuration
@EnableTransactionManagement
@MapperScan("com.csx.demo.spring.boot.dao")
public class MyBatisConfig {

}

step2:添加@Transactional註解到接口的實現。

@Service
@Transactional(readOnly = true,rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public int saveSysUser(SysUser user) {
        int i = sysUserMapper.insert(user);
        return i;
    }
}

使用Spring的聲明式事務就這麼簡單。

當你使用Spring的AOP方式來使用事務的話,你添加@Transactional註解的方法一定要是public的,不然事務不會生效。

假如你需要讓非public的方法生效,你需要使用AspectJ 的AOP實現。(說明:Spring的AOP功能有兩種實現方式,一種是Spring自己實現的AOP功能,主要是通過JDK動態代理或者CGLIB動態代理實現的。還有一種方式是整合AspectJ 這個第三方AOP框架實現的)

另外,@Transactional註解可以添加到接口、接口中的方法定義、類和類裏面的方法。Spring團隊建議將註解加到具體的類和方法實現上,而不是加到接口定義上(原因見下面英文描述)。當然,您可以將@Transactional註釋放在接口(或接口方法)上,但是只有在使用基於接口的代理時,才會像您期望的那樣工作。

The fact that Java annotations are not inherited from interfaces means that, if you use class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), the transaction settings are not recognized by the proxying and weaving infrastructure, and the object is not wrapped in a transactional proxy.

@Transactional註解的配置

@Transactional註解可以進行以下配置。

Property Type Description
value String 一個項目中可以存在多個事務管理器,這個值用於指定具體使用哪個事務管理器。
propagation enum: Propagation 設置傳播機制
isolation enum: Isolation 設置隔離級別(只有當傳播機制設置成 REQUIRED or REQUIRES_NEW時這個配置才生效)
timeout int (in seconds of granularity) 設置超時時間(以秒為單位,只有當傳播機制設置成 REQUIRED or REQUIRES_NEW時這個配置才生效)
readOnly boolean 只讀事務配置(只有當傳播機制設置成 REQUIRED or REQUIRES_NEW時這個配置才生效)
rollbackFor Array of Class objects, which must be derived from Throwable. 回滾的異常
rollbackForClassName Array of class names. The classes must be derived from Throwable.
noRollbackFor Array of Class objects, which must be derived from Throwable. 不回滾的異常
noRollbackForClassName Array of String class names, which must be derived from Throwable.

假如我們沒有配置上面的屬性,這些屬性也都是有默認值的

  • The propagation setting is PROPAGATION_REQUIRED.
  • The isolation level is ISOLATION_DEFAULT.
  • The transaction is read-write.
  • The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
  • Any RuntimeException triggers rollback, and any checked Exception does not.(默認回滾RuntimeException )

多事務管理器

有時候項目中可能會存在多個事務管理器,比如JDBC事務,比如JMS事務。這時候我們可以通過transactionManager屬性指定。

public class TransactionalService {

    @Transactional("jdbc")
    public void setSomething(String name) { ... }

    @Transactional("jms")
    public void doSomething() { ... }
}

上面的jdbc和jms是指兩個事務管理器在Spring容器中Bean的名字。

事務的傳播機制

在TransactionDefinition這個類中定義了6中傳播機制的類型。

1. PROPAGATION_REQUIRED

2. PROPAGATION_REQUIRES_NEW

3. PROPAGATION_NESTED

只支持JDBC事務。

編程式事務管理

Spring框架提供兩種方式來進行編程式事務管理:

  • The TransactionTemplate.
  • PlatformTransactionManager 的實現。

Spring團隊推薦使用第一種方式進行編程式事務管理。

1. 使用TransactionTemplate進行事務管理

下面是使用TransactionTemplate進行事務管理的一個例子。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TxApp.class)
public class TxTest {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Test
    public void selectUserTest() {

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        //同一個sqlSession創建的Mapper
        SysUserMapper mapper = sqlSession1.getMapper(SysUserMapper.class);
        SysUser sysUser = new SysUser();
        sysUser.setUsername("zyzl");
        sysUser.setPassword("11");

        //有返回值的操作
        transactionTemplate.execute(new TransactionCallback() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                try {
                    return mapper.insert(sysUser);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });

        //沒返回值的操作
        transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
            @Override
            public void accept(TransactionStatus transactionStatus) {
                try {
                    mapper.insert(sysUser);
                } catch (Exception e) {
                    transactionStatus.setRollbackOnly();
                    throw e;
                }
            }
        });
    }

}

我們也可以通過TransactionTemplate來設定事務的隔離級別等屬性。

//設置隔離級別
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//設置超時時間
transactionTemplate.setTimeout(30);
//設置傳播機制
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

對於不同的事務操作,如果需要不同的隔離級別和傳播機制的話,請使用不同的transactionTemplate。也就是說,你要創建不同的transactionTemplate對象來進行操作。

2. 使用PlatformTransactionManager進行事務管理

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
// 設置傳播機制
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//開啟事務
TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
//提交事務
txManager.commit(status);

事務綁定事件

使用@TransactionalEventListener可以在事務提交前後,回滾后等階段觸發某些操作。但是這個功能暫時還沒想到很好的使用場景。後續有需要再來用。

@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        // ...
    }
}

重要類和接口

  • PlatformTransactionManager:事務管理器,用於獲取事務,提交回滾事務;
  • TransactionDefinition:
  • TransactionStatus:代表一個事務

進一步閱讀

Distributed transactions in Spring, with and without XA is a JavaWorld presentation in which Spring’s David Syer guides you through seven patterns for distributed transactions in Spring applications, three of them with XA and four without.(Spring實現分佈式事務的介紹)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

聚甘新