奔馳下一代汽車將棄用特斯拉電動傳動系統 採用自家技術

戴姆勒電動汽車主管哈拉爾德•克勒格爾(Harald Kroeger)在接受德國媒體採訪時說,為了開發自有傳動系統和電池技術,奔馳已經投資了5.5億美元,下一代B級Electric Drive電動汽車不會使用特斯拉傳動系統。

奔馳這樣做合情合理。2014年年末時,奔馳基本上已經清空了特斯拉持股。在2014年的年報中特斯拉也說B級車研發合作已經基本完成。到了2015年年底,特斯拉已經銷售10萬輛電動汽車。以前特斯拉是矽谷新崛起的明星企業,一些大型汽車公司有興趣與它合作,現在汽車公司已經將特斯拉當成了競爭對手。比如豐田,在特斯拉成立之初,豐田也曾參與投資,特斯拉還為豐田電動版RAV4提供傳動系統。

那時的特斯拉還很年輕,它還沒有推出Model S轎車,如果能與大型汽車商合作對公司的發展無疑是有利的。特斯拉需要增加營收,只有有了營收,公司才有可能轉變為主流汽車製造商。另外,在公司沒有上市之前,它需要吸引早期投資者來支援自己。今天的特斯拉已經完全不同了,Model S大獲成功,Model X剛剛開始向用戶交貨。本月晚些時候Model 3也將上市,這是一款低價電動汽車。可以說現在的特斯拉已經是一家規模雖小但是卻很成熟的電動汽車製造商了。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

柯文哲訪美西 特斯拉初體驗

台北市長柯文哲近日參訪美國舊金山,並於10日拜訪全球知名的電動車大廠特斯拉汽車公司(Tesla Motors, Inc.),與特斯拉交換電動車友善城市的意見。

由Elon Musk與史丹佛碩士JB Straul聯手打造的特斯拉,是目前知名度最高的電動車製造商。特斯拉將自己定位為「科技產業」,除生產純電動汽車外,也加入自動駕駛等功能。同時,特斯拉搭載的儲能裝置採用鋰電池,其鋰電池技術也透過品牌「Tesla Energy」開始提供Powerwall、Powerpack家用與商用儲能系統。

柯文哲在美西時間10日下午參訪特斯拉,並試乘價值8 萬美金的特斯拉Model X 經典車款。台北市政府表示,柯文哲安排本次參訪的目的,是為了向特斯拉請益如何打造電動車友善城市,並與對方交流意見。

在台北市,目前能見度最高的電動車為電動機車Gogoro。Gogoro致力於打造智慧城市,透過電池交換系統來提供車輛行駛時所需的電力,同時在北、北、基、桃、竹都設有電池交換站,提高民眾採購Gogoro的意願。

(照片來源:台北市政府)

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

南投搬家前需注意的眉眉角角,別等搬了再說!

寶馬未來百年發力電動車 將在中國實現“國產”

德國慕尼克報導寶馬集團於日前舉辦百年慶典之際,不僅宣佈企業正式進入下一個百年,同時寶馬集團董事長科魯格先生還表示:“我們在多種技術上進行投資,並持續在電動化領域的投入。”根據寶馬集團的規劃,未來每一款產品都會推出插電式混合動力車型。

對於寶馬在華新能源佈局,寶馬集團大中華區總裁兼首席執行官康思遠先生于近日在慕尼克的寶馬總部與車王雜誌溝通時透露:“未來我們將推出更多插電式混合動力車型,寶馬在華的瀋陽工廠正在建設高壓電池中心,是我們為長期發展所做出的投資。”這也將成為國內首個在華建設動力電池的豪華車製造商,並為後續的寶馬插電式混合動力車型提供電池。目前,寶馬已在華投產首款插電式混合動力車型530Le,後續有望再基於2系旅行車、新3系以及全新X1打造的3款國產插電混合動力車。

根據寶馬的新車規劃,全新一代X1將在2016年中實現投產,這款SUV將推出標準軸距版和長軸距版,其中長軸距版將推出三排七座的車型。此外,全新X1還將推出一款插電混動版車型,網通社從國家工商行政管理總局商標局獲悉,寶馬股份公司已在華註冊了“xDrive25Le”商標。其中xDrive25Le中“Le”是指加長版插電式混合動力車,與5系插電混動版530Le的含義相同。新車將搭載由1.5升三缸渦輪增壓汽油發動機和一台後置電動機組成混動系統,總功率輸出可達142千瓦,預計綜合油耗將保持2L左右的水準。

今年3月21日,創新寶馬2系旅行車將實現在華投產上市,基於新車型所打造的插電混動版本也有望投產。現階段,寶馬2系旅行車的插電混動版(225xe)已在海外推出,並搭載的混動系統由1.5T發動機和一款動力輸出可達65千瓦和165牛米電動機組成,當發動機和電動機同時做功時,這套插電式混合動力系統的綜合最大功率為165千瓦,峰值扭矩達到385牛米,百公里綜合油耗僅在2L左右。

除2系旅行車和全新X1之外,目前在華投產的3系轎車也有望推出插電混合動力版車型。此前寶馬已在國際車展上發佈了3系的插電式混合動力車型330e。新車在動力上採用的2.0T+電動機的插電混動系統,最大綜合功率可達到185千瓦,峰值扭矩則為420牛米。百公里綜合油耗僅為2L,與現款3系同搭載2.0T發動機的320i車型6.7L/100km相比,油耗下降了約70%。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

英國2017年將在高速公路上測試無人汽車

據路透社報導,英國政府宣佈,2017年將首次在高速公路上測試無人駕駛汽車,其目標是到2020年允許無人駕駛汽車行駛在城市街道上。

英國財政部稱,下週三,英國財政大臣喬治•奧斯本(George Osborne)公佈年度預算報告時,將宣佈在高速公路測試無人駕駛汽車的相關計畫,並表示英國政府將提出議案,清除影響無人駕駛汽車技術部署的法規壁壘。

根據英國政府的資料,在全球範圍內,無人駕駛汽車市場的規模近期內將達到9000億英鎊(約合1.29萬億美元),但需要克服一系列法律障礙,包括明確誰為無人駕駛汽車發生的車禍負責等。

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

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

※評比南投搬家公司費用收費行情懶人包大公開

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

系統分析師-軟件水平考試(高級)-理論-系統規劃

系統分析師-軟件水平考試(高級)-理論-系統規劃

前言

系統規劃是什麼東東呢?

通俗點說,就是為了實現企業的戰略目標,分析企業現狀(技術外部)與系統狀況(技術內部),從而提出新的技術要求,對現有系統進行規劃設計等變化。

  1. 目標是實現企業戰略目標;
  2. 分析企業現狀(了解情況了,才能下手唄。站在技術角度,企業現狀就分技術和非技術);
  3. 根據掌握的資料,對接下來的系統開發做出規劃(技術規劃,那是CTO的工作,這裏就直接系統規劃);
  4. 落實規劃,開發或改造系統,從而完成對企業戰略目標的支持。

期間會涉及對一些項目或系統(一個公司會擁有多個項目與多個系統,用於支撐公司產業線)的評估,如機會選擇,可行性分析,效益分析(分析師比架構師多了這些思量與考察,而高項則更為具體)。

XMIND

(圖片絕對清楚。如果看不清楚,請下載,或者在新頁面中打開圖片)

總結

XMIND重難點的解讀

步驟

為了幫助大家更好地理解這部分內容,我們舉個栗子。

這樣說吧,阿里巴巴定下了淘寶雙十一戰略目標。你作為CTO,就要站在公司高層,開始規劃阿里的系統,讓它為雙十一這一戰略目標服務。

具體的實施過程應該是這樣的:

  1. 首先,根據雙十一這一戰略目標,你得分析阿里現在技術環境(可以做出怎樣的改變,業務上是否可以進行一定的妥協等等),並了解系統當前的狀況(是否可以承受住雙十一壓力。承受不了的話,瓶頸在哪裡等等)。無論做什麼,收集信息,了解情況都是首要的。
  2. 其次,站在技術的角度,你需要確定公司信息系統該達到怎樣的目標(更大一些說,應該是技術的目標標準)。比如雙十一這樣的戰略目標,我確定公司的系統應該達到十萬的訂單處理速度(2019年,阿里的訂單創建峰值已經達到了五十萬左右),以及一系列類似的系統要求。
  3. 然後,根據前面制定的一系列系統要求。開始規劃系統的拆分,將目標系統進行邏輯的拆分。但是如果已經有了系統基礎,那麼就需要在原有的系統上,進行調整。如阿里雲平台必須保證在雙十一時提供足夠強大的基礎設施支撐(可以在必要時,支撐大當量的橫向擴展)。又如支付寶需要確保在雙十一零點(流量衝擊的峰值)保證可以支撐百萬計的支付請求,並且妥善處理超出的部分。這樣就完成了目標的落地方案生成。
  4. 接下來,需要確定工作的優先級別,以及開發順序。由於總的開發資源是有限的,並且不同業務之間存在各種錯綜複雜的關係,所以,需要CTO站在公司高層角度,進行這個層次的方案安排。如由於阿里雲是其它業務的支撐基礎,是實現目標的必要因素(甚至都沒有太多業務妥協的空間),那麼我們可以將它作為優先級第一的工作,並最大程度地傾斜企業資源等。其它工作以此類推。從複雜的角度,可以了解項目組合管理,以及企業戰略管理等(但是分析師考試不會這麼複雜的,這裏只是舉個栗子)。
  5. 接着,我需要對這些方案進行可行性分析。畢竟這些方案可能是我根據自己的經驗,以及周邊個別的意見,花了一天的時間整合出來的。其中可能存在一些不合理,不科學的部分,而我並不了解(個人的力量與認知畢竟有限。也許我對軟件十分清楚,但是在硬件上提出了超脫物理規律的不可能方案,而我想當然地認為可以)。所以,需要進行可行性分析。甚至通過論證會議,進行具體的論證,從而確定方案是否可行,是否需要調整。
  6. 最後,根據前面這些簡要的方案與意見,通過自己與下屬不斷溝通,從而形成一份具體的系統設計的方案-系統設計任務書。作為接下來實施系統建設工作的具體依據。

可行性分析

這裏着重提一下這個部分,這個部分充分體現了系統的業務特性。

從考試角度說,這個部分雖然不是一個章節,但是每次考試都會考幾分。

從實際角度來說,可能一些感興趣的比較清楚,阿里的p7-p8,騰訊的t3-t4,晉陞都是有一定困難的。晉陞條件中有一條,需要對業務有足夠的了解。就如同國外對程序員的分級看法,也對程序員的產品思維有着非常重的要求。大廠要求程序員對業務有一定的了解,一方面是確定經歷真實性,另一方面就是觀察面試者的業務思維,產品思維。可能p7,只是要求根據業務場景,實現業務方案,並適當根據技術,提出對業務的看法(如調整業務,提出新的業務特性等)。到了p8-p9就是希望你能在一個大的目標下,提出自己的產品理念。在項目經理的產品模型或產品想法的基礎上,站在技術角度,給出新的產品想法,去完善產品模型,提高產品的層次。

以上看法,不保證完全正確,畢竟不同人的解讀是不同的。但是還是值得參考一下的(日後我晉陞p8,p9,考慮回來更新一下)。

那麼可行性分析可以帶來產品思維嘛?

當然,,,不能。

但是,可行性分析,絕對是一個絕佳的入門機會。可以幫助開發人員,從一個新的角度去看待自己開發的系統。並且這個入門的門檻很低,開發人員可以很好的理解,入門。後續的道路,就需要各位自己走了。畢竟完事開頭難,開始解決了,後面走下去就行了。當然後續有更多積累了,我會分享我對產品,業務的看法。

至於其它部分,都比較簡單,就不在此深入談論了。

學習必要性

考試的必要性,我只說一句,分值可觀。

現實的意義,我總結了三點比較重要的:

  • 提升高度。通過這部分的學習,可以為你晉陞公司技術高層埋下一顆種子。起碼你和Boss扯皮的時候,不再是只有技術名詞了。
  • 改善眼界。通過這部分的學習,可以改變你對信息系統的純技術看法。有時候,人的改變,只是需要一個開始。
  • 心中有數。通過這部分的學習,可以令你明白現有項目的一些基本商業特性。起碼你知道你的項目能走多遠,是不是該跑路了。囧

這個章節就這些內容,如果有什麼不清楚的,可以@我。如果有正在準備考試或已經通過考試的,或有交流需求的,可以@我,加群。

希望這篇博客對大家有所幫助。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※試算大陸海運運費!

文件上傳

文件上傳是 Web 開發常見需求,上傳文件需要用到文件輸入框,如果給文件輸入框添加一個 multiple 屬性則可以一次選擇多個文件(不支持的瀏覽器會自動忽略這個屬性)

<input multiple type="file">

點擊這個輸入框就可以打開瀏覽文件對話框選擇文件了,一般一個輸入框上傳一個文件就行,要上傳多個文件也可以用多個輸入框來處理,這樣做是為了兼容那些不支持 multiple 屬性的瀏覽器,同時用戶一般也不會選擇多個文件

基本上傳方式

當把文件輸入框放入表單中,提交表單的時候即可將選中的文件一起提交上傳到服務器,需要注意的是由於提交的表單中包含文件,因此要修改一下錶單元素的 enctype 屬性為 multipart/form-data

<form action="#" enctype="multipart/form-data" method="post">
  <input name="file" type="file">
  <button type="submit">Upload</button>
</form>

這樣上傳方式是傳統的同步上傳,上傳的文件如果很大,往往需要等待很久,上傳完成后頁面還會重新加載,並且必須等待上傳完成后才能繼續操作

早期的瀏覽器並不支持異步上傳,不過可以使用 iframe 來模擬,在頁面中隱藏一個 <iframe> 元素,指定一個 name 值,同時將 <form> 元素的 target 屬性值指定為 <iframe> 元素的 name 屬性的值,將兩者關聯起來

<form action="#" enctype="multipart/form-data" method="post" target="upload-frame">
  <input name="file" type="file">
  <button type="submit">Upload</button>
</form>
<iframe id="upload-frame" name="upload-frame" src="about:blank" style="display: none;"></iframe>

這樣在提交表單上傳的時候,頁面就不會重新加載了,取而代之的是 iframe 重新加載了,不過 iframe 原本就是隱藏的,即使重新加載也不會感知到

訪問文件

File API 提供了訪問文件的能力,通過輸入框的 files 屬性訪問,這會得到一個 FileList,這是一個集合,如果只選擇了一個文件,那麼集合中的第一個元素就是這個文件

var input = document.querySelector('input[type="file"]')
var file = input.files[0]

console.log(file.name) // 文件名稱
console.log(file.size) // 文件大小
console.log(file.type) // 文件類型

支持 File API 的瀏覽器可以參考

Ajax 上傳

由於可以通過 File API 直接訪問文件內容,再結合 XMLHttpRequest 對象直接將文件上傳,將其作為參數傳給 XMLHttpRequest 對象的 send 方法即可

var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(file)

不過一些原因不建議直接這樣傳遞文件,而是使用 FormData 對象來包裝需要上傳的文件,FormData 是一個構造函數,使用的時候先 new 一個實例,然後通過實例的 append 方法向其中添加數據,直接把需要上傳的文件添加進去

var formData = new FormData()
formData.append('file', file, file.name) // 第 3 個參數是文件名稱
formData.append('username', 'Mary') // 還可以添加額外的參數

甚至也可以直接把表單元素作為實例化參數,這樣整個表單中的數據就全部包含進去了

var formData = new FormData(document.querySelector('form'))

數據準備好后,就是上傳了,同樣是作為參數傳給 XMLHttpRequest 對象的 send 方法

var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(formData)

監測上傳進度

XMLHttpRequest 對象還提供了一個 progress 事件,基於這個事件可以知道上傳進度如何

var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.upload.onprogress = progressHandler // 這個函數接下來定義

上傳的 progress 事件由 xhr.upload 對象觸發,在事件處理程序中使用這個事件對象的 loaded(已上傳字節數) 和 total(總數) 屬性來計算上傳的進度

function progressHandler(e) {
  var percent = Math.round((e.loaded / e.total) * 100)
}

上面的計算會得到一個表示完成百分比的数字,不過這兩個值也不一定總會有,保險一點先判斷一下事件對象的 lengthComputable 屬性

function progressHandler(e) {
  if (e.lengthComputable) {
    var percent = Math.round((e.loaded / e.total) * 100)
  }
}

支持 Ajax 上傳的瀏覽器可以參考

分割上傳

使用文件對象的 slice 方法可以分割文件,給該方法傳遞兩個參數,一個起始位置和一個結束位置,這會返回一個新的 Blob 對象,包含原文件從起始位置到結束位置的那一部分(文件 File 對象其實也是 Blob 對象,這可以通過 file instanceof Blob 確定,Blob 是 File 的父類)

var blob = file.slice(0, 1024) // 文件從字節位置 0 到字節位置 1024 那 1KB

將文件分割成幾個 Blob 對象分別上傳就能實現將大文件分割上傳

function upload(file) {
  let formData = new FormData()
  formData.append('file', file)
  let xhr = new XMLHttpRequest()
  xhr.open('POST', '/upload/url', true)
  xhr.send(formData)
}

var blob = file.slice(0, 1024)
upload(blob) // 上傳第一部分

var blob2 = file.slice(1024, 2048)
upload(blob2) // 上傳第二部分

// 上傳剩餘部分

通常用一個循環來處理更方便

var pos = 0 // 起始位置
var size = 1024 // 塊的大小

while (pos < file.size) {
  let blob = file.slice(pos, pos + size) // 結束位置 = 起始位置 + 塊大小

  upload(blob)
  pos += size // 下次從結束位置開始繼續分割
}

服務器接收到分塊文件進行重新組裝的代碼就不在這裏展示了

使用這種方式上傳文件會一次性發送多個 HTTP 請求,那麼如何處理這種多個請求同時發送的情況呢?方法有很多,可以用 Promise 來處理,讓每次上傳都返回一個 promise 對象,然後用 Promise.all 方法來合併處理,Promise.all 方法接受一個數組作為參數,因此將每次上傳返回的 promise 對象放在一個數組中

var promises = []

while (pos < file.size) {
  let blob = file.slice(pos, pos + size)

  promises.push(upload(blob)) // upload 應該返回一個 promise
  pos += size
}

同時改造一下 upload 函數使其返回一個 promise

function upload(file) {
  return new Promise((resolve, reject) => {
    let formData = new FormData()
    formData.append('file', file)
    let xhr = new XMLHttpRequest()
    xhr.open('POST', '/upload/url', true)
    xhr.onload = () => resolve(xhr.responseText)
    xhr.onerror = () => reject(xhr.statusText)
    xhr.send(formData)
  })
}

當一切完成后

Promise.all(promises).then((response) => {
  console.log('Upload success!')
}).catch((err) => {
  console.log(err)
})

支持文件分割的瀏覽器可以參考

判斷一下文件對象是否有該方法就能知道瀏覽器是否支持該方法,對於早期的部分版本瀏覽器需要加上對應的瀏覽器廠商前綴

var slice = file.slice || file.webkitSlice || file.mozSlice

if (slice) {
  let blob = slice.call(file, 0, 1024) // call
  upload(blob)
} else {
  upload(file) // 不支持分割就只能直接上傳整個文件了,或者提示文件過大
}

拖拽上傳

通過拖拽 API 可以實現拖拽文件上傳,默認情況下,拖拽一個文件到瀏覽器中,瀏覽器會嘗試打開這個文件,要使用拖拽功能需要阻止這個默認行為

document.addEventListener('dragover', function(e) {
  e.preventDefault()
  e.stopPropagation()
})

任意指定一個元素來作為釋放拖拽的區域,給一個元素綁定 drop 事件

var element = document.querySelector('label')
element.addEventListener('drop', function(e) {
  e.preventDefault()
  e.stopPropagation()

  // ...
})

通過該事件對象的 dataTransfer 屬性獲取文件,然後上傳即可

var file = e.dataTransfer.files[0]
upload(file) // upload 函數前面已經定義

選擇類型

給文件輸入框添加 accept 屬性即可指定選擇文件的類型,比如要選擇 png 格式的圖片,則指定其值為 image/png,如果要允許選擇所有類型的圖片,就是 image/*

<input accept="image/*" type="file">

添加 capture 屬性可以調用設備機能,比如 capture="camera" 可以調用相機拍照,不過這並不是一個標準屬性,不同設備實現方式也不一樣,需要注意

<input accept="image/*" capture="camera" type="file">

經測 iOS 設備添加該屬性后只能拍照而不能從相冊選擇文件了,所以判斷一下

if (iOS) { // iOS 用 navigator.userAgent 判斷
  input.removeAttribute('capture')
}

不支持的瀏覽器會自動忽略這些屬性

自定義樣式

文件輸入框在各個瀏覽器中呈現的樣子都不大相同,而且給 input 定義樣式也不是那麼方便,如果有需要應用自定義樣式,有一個技巧,可以用一個 label 關聯到這個文件輸入框,當點擊這個 label 元素的時候就會觸發文件輸入框的點擊,打開瀏覽文件的對話框,相當於點擊了文件輸入框一樣的效果

<label for="file-input"></label>
<input id="file-input" style="clip: rect(0,0,0,0); position: absolute;" type="file">

這時就可以將原本的文件輸入框隱藏了,然後給 label 元素任意地應用樣式,畢竟要給 label 元素應用樣式比 input 方便得多

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

【集合系列】- 深入淺出分析LinkedHashMap

一、摘要

在集合系列的第一章,咱們了解到,Map的實現類有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

本文主要從數據結構和算法層面,探討LinkedHashMap的實現。

二、簡介

LinkedHashMap可以認為是HashMap+LinkedList,它既使用HashMap操作數據結構,又使用LinkedList維護插入元素的先後順序,內部採用雙向鏈表(doubly-linked list)的形式將所有元素( entry )連接起來。

LinkedHashMap繼承了HashMap,允許放入key為null的元素,也允許插入value為null的元素。從名字上可以看出該容器是LinkedList和HashMap的混合體,也就是說它同時滿足HashMap和LinkedList的某些特性,可將LinkedHashMap看作採用Linked list增強的HashMap。

打開 LinkedHashMap 源碼,可以看到主要三個核心屬性:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{

    /**雙向鏈表的頭節點*/
    transient LinkedHashMap.Entry<K,V> head;

    /**雙向鏈表的尾節點*/
    transient LinkedHashMap.Entry<K,V> tail;

    /**
      * 1、如果accessOrder為true的話,則會把訪問過的元素放在鏈表後面,放置順序是訪問的順序
      * 2、如果accessOrder為false的話,則按插入順序來遍歷
      */
      final boolean accessOrder;
}

LinkedHashMap 在初始化階段,默認按插入順序來遍歷

public LinkedHashMap() {
        super();
        accessOrder = false;
}

LinkedHashMap 採用的 Hash 算法和 HashMap 相同,不同的是,它重新定義了數組中保存的元素Entry,該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。

源碼如下:

static class Entry<K,V> extends HashMap.Node<K,V> {
        //before指的是鏈表前驅節點,after指的是鏈表后驅節點
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
}

可以直觀的看出,雙向鏈表頭部插入的數據為鏈表的入口,迭代器遍歷方向是從鏈表的頭部開始到鏈表尾部結束。

除了可以保迭代歷順序,這種結構還有一個好處:迭代LinkedHashMap時不需要像HashMap那樣遍歷整個table,而只需要直接遍歷header指向的雙向鏈表即可,也就是說LinkedHashMap的迭代時間就只跟entry的個數相關,而跟table的大小無關。

三、常用方法介紹

3.1、get方法

get方法根據指定的key值返回對應的value。該方法跟HashMap.get()方法的流程幾乎完全一樣,默認按照插入順序遍歷。

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
}

如果accessOrdertrue的話,會把訪問過的元素放在鏈表後面,放置順序是訪問的順序

void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
}

測試用例:

public static void main(String[] args) {
        //accessOrder默認為false
        Map<String, String> accessOrderFalse = new LinkedHashMap<>();
        accessOrderFalse.put("1","1");
        accessOrderFalse.put("2","2");
        accessOrderFalse.put("3","3");
        accessOrderFalse.put("4","4");
        System.out.println("acessOrderFalse:"+accessOrderFalse.toString());
        
        //accessOrder設置為true
        Map<String, String> accessOrderTrue = new LinkedHashMap<>(16, 0.75f, true);
        accessOrderTrue.put("1","1");
        accessOrderTrue.put("2","2");
        accessOrderTrue.put("3","3");
        accessOrderTrue.put("4","4");
        accessOrderTrue.get("2");//獲取鍵2
        accessOrderTrue.get("3");//獲取鍵3
        System.out.println("accessOrderTrue:"+accessOrderTrue.toString());
}

輸出結果:

acessOrderFalse:{1=1, 2=2, 3=3, 4=4}
accessOrderTrue:{1=1, 4=4, 2=2, 3=3}

3.2、put方法

put(K key, V value)方法是將指定的key, value對添加到map里。該方法首先會調用HashMap的插入方法,同樣對map做一次查找,看是否包含該元素,如果已經包含則直接返回,查找過程類似於get()方法;如果沒有找到,將元素插入集合。

/**HashMap 中實現*/
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}

LinkedHashMap 中覆寫的方法

// LinkedHashMap 中覆寫
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // 將 Entry 接在雙向鏈表的尾部
    linkNodeLast(p);
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // last 為 null,表明鏈表還未建立
    if (last == null)
        head = p;
    else {
        // 將新節點 p 接在鏈表尾部
        p.before = last;
        last.after = p;
    }
}

3.3、remove方法

remove(Object key)的作用是刪除key值對應的entry,該方法實現邏輯主要以HashMap為主,首先找到key值對應的entry,然後刪除該entry(修改鏈表的相應引用),查找過程跟get()方法類似,最後會調用 LinkedHashMap 中覆寫的方法,將其刪除!

/**HashMap 中實現*/
public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode) {...}
            else {
                // 遍歷單鏈表,尋找要刪除的節點,並賦值給 node 變量
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode) {...}
            // 將要刪除的節點從單鏈表中移除
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);    // 調用刪除回調方法進行後續操作
            return node;
        }
    }
    return null;
}

LinkedHashMap 中覆寫的 afterNodeRemoval 方法

void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    // 將 p 節點的前驅后後繼引用置空
    p.before = p.after = null;
    // b 為 null,表明 p 是頭節點
    if (b == null)
        head = a;
    else
        b.after = a;
    // a 為 null,表明 p 是尾節點
    if (a == null)
        tail = b;
    else
        a.before = b;
}

四、總結

LinkedHashMap 繼承自 HashMap,所有大部分功能特性基本相同,二者唯一的區別是 LinkedHashMap 在HashMap的基礎上,採用雙向鏈表(doubly-linked list)的形式將所有 entry 連接起來,這樣是為保證元素的迭代順序跟插入順序相同。

主體部分跟HashMap完全一樣,多了header指向雙向鏈表的頭部,tail指向雙向鏈表的尾部,默認雙向鏈表的迭代順序就是entry的插入順序。

五、參考

1、JDK1.7&JDK1.8 源碼
2、

作者:炸雞可樂
出處:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

【從今天開始好好學數據結構03】鏈表

目錄

今天我們來聊聊“鏈表(Linked list)”這個數據結構。

在我們上一章中棧與隊列底層都是採用順序存儲的這種方式的,而今天要聊的鏈表則是採用鏈式存儲,鏈表可以說是繼數組之後第二種使用得最廣泛的通用數據結構了,可見其重要性!

相比,鏈表是一種稍微複雜一點的數據結構。對於初學者來說,掌握起來也要比數組稍難一些。這兩個非常基礎、非常常用的數據結構,我們常常將會放到一塊兒來比較。所以我們先來看,這兩者有什麼區別。數組需要一塊連續的內存空間來存儲,對內存的要求比較高。而鏈表恰恰相反,它並不需要一塊連續的內存空間,它通過指針”將一組零散的內存塊串聯起來使用,鏈表結構五花八門,今天我重點給你介紹三種最常見的鏈表結構,它們分別是:單鏈表、雙向鏈表和循環鏈表。

鏈表通過指針將一組零散的內存塊串聯在一起。其中,我們把內存塊稱為鏈表的“結點”。為了將所有的結點串起來,每個鏈表的結點除了存儲數據之外,還需要記錄鏈上的下一個結點的地址。而尾結點特殊的地方是:指針不是指向下一個結點,而是指向一個空地址NULL,表示這是鏈表上最後一個結點。

@

單鏈表

package demo2;

//一個節點
public class Node {

    //節點內容
    int data;
    //下一個節點
    Node next;
    
    public Node(int data) {
        this.data=data;
    }
    
    //為節點追回節點
    public Node append(Node node) {
        //當前節點
        Node currentNode = this;
        //循環向後找
        while(true) {
            //取出下一個節點
            Node nextNode = currentNode.next;
            //如果下一個節點為null,當前節點已經是最後一個節點
            if(nextNode==null) {
                break;
            }
            //賦給當前節點
            currentNode = nextNode;
        }
        //把需要追回的節點追加為找到的當前節點的下一個節點
        currentNode.next=node;
        return this;
    }
    
    //插入一個節點做為當前節點的下一個節點
    public void after(Node node) {
        //取出下一個節點,作為下下一個節點
        Node nextNext = next;
        //把新節點作為當前節點的下一個節點
        this.next=node;
        //把下下一個節點設置為新節點的下一個節點
        node.next=nextNext;
    }
    
    //显示所有節點信息
    public void show() {
        Node currentNode = this;
        while(true) {
            System.out.print(currentNode.data+" ");
            //取出下一個節點
            currentNode=currentNode.next;
            //如果是最後一個節點
            if(currentNode==null) {
                break;
            }
        }
        System.out.println();
    }
    
    //刪除下一個節點
    public void removeNext() {
        //取出下下一個節點
        Node newNext = next.next;
        //把下下一個節點設置為當前節點的下一個節點。
        this.next=newNext;
    }
    
    //獲取下一個節點
    public Node next() {
        return this.next;
    }
    
    //獲取節點中的數據
    public int getData() {
        return this.data;
    }
    
    //當前節點是否是最後一個節點
    public boolean isLast() {
        return next==null;
    }
    
}

單鏈表測試類

package demo2.test;

import demo2.Node;

public class TestNode {
    
    public static void main(String[] args) {
        //創建節點
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        //追加節點
        n1.append(n2).append(n3).append(new Node(4));
        //取出下一個節點的數據
//      System.out.println(n1.next().next().next().getData());
        //判斷節點是否為最後一個節點
//      System.out.println(n1.isLast());
//      System.out.println(n1.next().next().next().isLast());
        
        //显示所有節點內容
        n1.show();
        //刪除一個節點
//      n1.next().removeNext();
        //显示所有節點內容
//      n1.show();
        //插入一個新節點
        Node node = new Node(5);
        n1.next().after(node);
        n1.show();
    }

}

鏈表要想隨機訪問第k個元素,就沒有數組那麼高效了。因為鏈表中的數據並非連續存儲的,所以無法像數組那樣,根據首地址和下標,通過尋址公式就能直接計算出對應的內存地址,而是需要根據指針一個結點一個結點地依次遍歷,直到找到相應的結點。

你可以把鏈表想象成一個隊伍,隊伍中的每個人都只知道自己後面的人是誰,所以當我們希望知道排在第k位的人是誰的時候,我們就需要從第一個人開始,一個一個地往下數。所以,鏈表隨機訪問的性能沒有數組好,需要O(n)的時間複雜度。

雙向鏈表

接下來我們再來看一個稍微複雜的,在實際的軟件開發中,也更加常用的鏈表結構:雙向鏈表。單向鏈表只有一個方向,結點只有一個後繼指針next指向後面的結點。而雙向鏈表,顧名思義,它支持兩個方向,每個結點不止有一個後繼指針next指向後面的結點,還有一個前驅指針prev指向前面的結點。

public class DoubleNode {
    //上一個節點
    DoubleNode pre=this;
    //下一個節點
    DoubleNode next=this;
    //節點數據
    int data;
    
    public DoubleNode(int data) {
        this.data=data;
    }
    
    //增節點
    public void after(DoubleNode node) {
        //原來的下一個節點
        DoubleNode nextNext = next;
        //把新節點做為當前節點的下一個節點
        this.next=node;
        //把當前節點做新節點的前一個節點
        node.pre=this;
        //讓原來的下一個節點作新節點的下一個節點
        node.next=nextNext;
        //讓原來的下一個節點的上一個節點為新節點
        nextNext.pre=node;
    }
    
    //下一個節點
    public DoubleNode next() {
        return this.next;
    }
    
    //上一個節點
    public DoubleNode pre() {
        return this.pre;
    }
    
    //獲取數據
    public int getData() {
        return this.data;
    }
    
}

雙向鏈表測試

import demo2.DoubleNode;

public class TestDoubleNode {

    public static void main(String[] args) {
        //創建節點
        DoubleNode n1 = new DoubleNode(1);
        DoubleNode n2 = new DoubleNode(2);
        DoubleNode n3 = new DoubleNode(3);
        //追加節點
        n1.after(n2);
        n2.after(n3);
        //查看上一個,自己,下一個節點的內容
        System.out.println(n2.pre().getData());
        System.out.println(n2.getData());
        System.out.println(n2.next().getData());
        System.out.println(n3.next().getData());
        System.out.println(n1.pre().getData());
        
    }
    
}

單鏈表VS雙向鏈表

如果我們希望在鏈表的某個指定結點前面插入一個結點或者刪除操作,雙向鏈表比單鏈表有很大的優勢。雙向鏈表可以在O(1)時間複雜度搞定,而單向鏈表需要O(n)的時間複雜度,除了插入、刪除操作有優勢之外,對於一個有序鏈表,雙向鏈表的按值查詢的效率也要比單鏈表高一些。因為,我們可以記錄上次查找的位置p,每次查詢時,根據要查找的值與p的大小關係,決定是往前還是往後查找,所以平均只需要查找一半的數據。

現在,你有沒有覺得雙向鏈表要比單鏈表更加高效呢?這就是為什麼在實際的軟件開發中,雙向鏈表儘管比較費內存,但還是比單鏈表的應用更加廣泛的原因。如果你熟悉Java語言,你肯定用過LinkedHashMap這個容器。如果你深入研究LinkedHashMap的實現原理,就會發現其中就用到了雙向鏈表這種數據結構。實際上,這裡有一個更加重要的知識點需要你掌握,那就是用空間換時間的設計思想。當內存空間充足的時候,如果我們更加追求代碼的執行速度,我們就可以選擇空間複雜度相對較高、但時間複雜度相對很低的算法或者數據結構。相反,如果內存比較緊缺,比如代碼跑在手機或者單片機上,這個時候,就要反過來用時間換空間的設計思路。

循環鏈表

循環鏈表是一種特殊的單鏈表。實際上,循環鏈表也很簡單。它跟單鏈表唯一的區別就在尾結點。我們知道,單鏈表的尾結點指針指向空地址,表示這就是最後的結點了。而循環鏈表的尾結點指針是指向鏈表的頭結點。和單鏈表相比,循環鏈表的優點是從鏈尾到鏈頭比較方便。當要處理的數據具有環型結構特點時,就特別適合採用循環鏈表。比如著名的約瑟夫問題。儘管用單鏈表也可以實現,但是用循環鏈表實現的話,代碼就會簡潔很多。

package demo2;

//一個節點
public class LoopNode {

    //節點內容
    int data;
    //下一個節點
    LoopNode next=this;
    
    public LoopNode(int data) {
        this.data=data;
    }
    
    //插入一個節點做為當前節點的下一個節點
    public void after(LoopNode node) {
        //取出下一個節點,作為下下一個節點
        LoopNode nextNext = next;
        //把新節點作為當前節點的下一個節點
        this.next=node;
        //把下下一個節點設置為新節點的下一個節點
        node.next=nextNext;
    }
    
    //刪除下一個節點
    public void removeNext() {
        //取出下下一個節點
        LoopNode newNext = next.next;
        //把下下一個節點設置為當前節點的下一個節點。
        this.next=newNext;
    }
    
    //獲取下一個節點
    public LoopNode next() {
        return this.next;
    }
    
    //獲取節點中的數據
    public int getData() {
        return this.data;
    }
    
}

循環鏈表測試

package demo2.test;

import demo2.LoopNode;

public class TestLoopNode {

    public static void main(String[] args) {
        LoopNode n1 = new LoopNode(1);
        LoopNode n2 = new LoopNode(2);
        LoopNode n3 = new LoopNode(3);
        LoopNode n4 = new LoopNode(4);
        //增加節點
        n1.after(n2);
        n2.after(n3);
        n3.after(n4);
        System.out.println(n1.next().getData());
        System.out.println(n2.next().getData());
        System.out.println(n3.next().getData());
        System.out.println(n4.next().getData());
    }

}

最後,我們再對比一下數組,數組的缺點是大小固定,一經聲明就要佔用整塊連續內存空間。如果聲明的數組過大,系統可能沒有足夠的連續內存空間分配給它,導致“內存不足(out of memory)”。如果聲明的數組過小,則可能出現不夠用的情況。這時只能再申請一個更大的內存空間,把原數組拷貝進去,非常費時。鏈表本身沒有大小的限制,天然地支持動態擴容,我覺得這也是它與數組最大的區別。

你可能會說,我們Java中的ArrayList容器,也可以支持動態擴容啊?事實上當我們往支持動態擴容的數組中插入一個數據時,如果數組中沒有空閑空間了,就會申請一個更大的空間,將數據拷貝過去,而數據拷貝的操作是非常耗時的。

我舉一個稍微極端的例子。如果我們用ArrayList存儲了了1GB大小的數據,這個時候已經沒有空閑空間了,當我們再插入數據的時候,ArrayList會申請一個1.5GB大小的存儲空間,並且把原來那1GB的數據拷貝到新申請的空間上。聽起來是不是就很耗時?

除此之外,如果你的代碼對內存的使用非常苛刻,那數組就更適合你。因為鏈表中的每個結點都需要消耗額外的存儲空間去存儲一份指向下一個結點的指針,所以內存消耗會翻倍。而且,對鏈表進行頻繁的插入、刪除操作,還會導致頻繁的內存申請和釋放,容易造成內存碎片,如果是Java語言,就有可能會導致頻繁的GC(Garbage Collection,垃圾回收)。

所以,在我們實際的開發中,針對不同類型的項目,要根據具體情況,權衡究竟是選擇數組還是鏈表!

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

不止面試—jvm類加載面試題詳解

面試題

帶着問題學習是最高效的,本次我們將嘗試回答以下問題:

  1. 什麼是類的加載?
  2. 哪些情況會觸發類的加載?
  3. 講一下JVM加載一個類的過程
  4. 什麼時候會為變量分配內存?
  5. JVM的類加載機制是什麼?
  6. 雙親委派機制可以打破嗎?為什麼

答案放在文章的最後,來不及看原理也可以直接跳到最後直接看答案。

深入原理

類的生命周期

類的生命周期相信大家已經耳熟能詳,就像下面這樣:

不過這東西總是背了就忘,忘了又背,就像馬什麼梅一樣,對吧?

其實理解之後,基本上就不會再忘了。

加載

加載主要做三件事:

  1. 找到類文件(通過類的全限定名來獲取定義此類的二進制字節流)
  2. 放入方法區(將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構)
  3. 開個入口(生成一個代表此類的java.lang.Class對象,作為訪問方法區這些數據結構的入口)

總的來講,這一步就是通過類加載器把類讀入內存。需要注意的是,第三步雖然生成了對象,但並不在堆里,而是在方法區里。

連接

連接分為三步,一般面試都比較喜歡問準備這一步。

校驗

顧名思義,檢查Class文件的字節流中包含的信息是否符合當前虛擬機的要求。

準備

這一步中將為靜態變量和靜態常量分配內存,並賦值。

需要注意的是,靜態變量只會給默認值。比如下面這個:

public static int value = 123;

此時賦給value的值是0,不是123。

靜態常量(static final修飾的)則會直接賦值。比如下面這個:

public static final int value = 123;

此時賦給value的值是123。

解析

解析階段就是jvm將常量池的符號引用替換為直接引用。

恩……啥是常量池?啥是符號引用?啥是直接引用?

常量池我們放在jvm內存結構里說。先來說下什麼是符號引用和直接引用。

符號引用和直接引用

假設有一個Worker類,包含了一個Car類的run()方法,像下面這樣:

class Worker{
    ......
    public void gotoWork(){
        car.run(); //這段代碼在Worker類中的二進製表示為符號引用        
    }
    ......
}

在解析階段之前,Worker類並不知道car.run()這個方法內存的什麼地方,於是只能用一個字符串來表示這個方法。該字符串包含了足夠的信息,比如類的信息,方法名,方法參數等,以供實際使用時可以找到相應的位置。

這個字符串就被稱為符號引用

在解析階段,jvm根據字符串的內容找到內存區域中相應的地址,然後把符號引用替換成直接指向目標的指針、句柄、偏移量等,這之後就可以直接使用了。

這些直接指向目標的指針、句柄、偏移量就被成為直接引用

初始化

類的初始化的主要工作是為靜態變量賦程序設定的初值。

還記得上面的靜態變量嗎:

public static int value = 123;

經過這一步,value的值終於是123了。

總結如下圖:

類初始化的條件

Java虛擬機規範中嚴格規定了有且只有五種情況必須對類進行初始化:

  1. 使用new字節碼指令創建類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。
  2. 通過java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則要首先進行初始化。
  3. 當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。
  4. 當虛擬機啟動時,用戶需要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。
  5. 使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行初始化,則需要先觸發其初始化。

除了以上這五種情況,其他任何情況都不會觸發類的初始化。

比如下面這幾種情況就不會觸發類初始化:

  1. 通過子類調用父類的靜態字段。此時父類符合情況一,而子類不符合任何情況。所以只有父類被初始化。
  2. 通過數組來引用類,不會觸發類的初始化。因為new的是數組,而不是類。
  3. 調用類的靜態常量不會觸發類的初始化,因為靜態常量在編譯階段就會被存入調用類的常量池中,不會引用到定義常量的類。

類加載機制

類加載器

在上面咱們曾經說到,加載階段需要“通過一個類的全限定名來獲取描述此類的二進制字節流”。這件事情就是類加載器在做。

jvm自帶三種類加載器,分別是:

  1. 啟動類加載器。
  2. 擴展類加載器。
  3. 應用程序類加載器

他們的繼承關係如下圖:

雙親委派

雙親委派機制工作過程如下:

  1. 當前ClassLoader首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,等下次加載的時候就可以直接返回了。

  2.  當前classLoader的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到bootstrp ClassLoader.

  3.  當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

為啥要搞這麼複雜?自己處理不好嗎?

雙親委派的優點如下:

  1. 避免重複加載。當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
  2. 為了安全。避免核心類,比如String被替換。

打破雙親委派

“雙親委派”機制只是Java推薦的機制,並不是強制的機制。

比如JDBC就打破了雙親委派機制。它通過Thread.currentThread().getContextClassLoader()得到線程上下文加載器來加載Driver實現類,從而打破了雙親委派機制。

至於為什麼,以後再說吧。

答案

現在,我們可以回答文章開頭提出的問題了。盡量在理解的基礎上回答,不需要死記硬背。

  1. 什麼是類的加載?

    JVM把通過類名獲得類的二進制流之後,把類放入方法區,並創建入口對象的過程被稱為類的加載。經過加載,類就被放到內存里了。

  2. 哪些情況會觸發類的初始化?

    類在5種情況下會被初始化:

    第一,假如這個類是入口類,他會被初始化。

    第二,使用new創建對象,或者調用類的靜態變量,類會被初始化。不過靜態常量不算。

    第三,通過反射獲取類,類會被初始化

    第四,如果子類被初始化,他的父類也會被初始化。

    第五,使用jdk1.7的動態語言支持時,調用到靜態句柄,也會被初始化。

  3. 講一下JVM加載一個類的過程

    同問題1。不過這裏也可以問下面試官是不是想問類的生命周期。如果是問類的生命周期,可以回答有”加載、連接、初始化、使用、卸載“五個階段,連接又可以分為”校驗、準備、解析“三個階段。

  4. 什麼時候會為變量分配內存?

    在準備階段為靜態變量分配內存。

  5. JVM的類加載機制是什麼?

    雙親委派機制,類加載器會先讓自己的父類來加載,父類無法加載的話,才會自己來加載。

  6. 雙親委派機制可以打破嗎?為什麼

    可以打破,比如JDBC使用線程上下文加載器打破了雙親委派機制。原因是JDBC只提供了接口,並沒有提供實現。這個問題可以再看下引用文獻的內容。

引用文獻

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

Maven系列第6篇:生命周期和插件詳解,此篇看過之後在maven的理解上可以超越同級別90%的人!

maven系列目標:從入門開始開始掌握一個高級開發所需要的maven技能。

這是maven系列第6篇。

整個maven系列的內容前後是有依賴的,如果之前沒有接觸過maven,建議從第一篇看起,本文尾部有maven完整系列的連接。

前面我們使用maven過程中,用到了一些命令,如下:

mvn clean
mvn compile
mvn test
mvn package
mvn install
mvn install -Dmaven.test.skip=true
mvn deploy
mvn help:system

上面這些命令,如果你玩過maven,估計大家還是比較眼熟的,只是大家有沒有想過這些命令為什麼這麼寫,為什麼-Dmaven.test.skip=true可以跳過測試,大家需要知道mvn命令背後的一些原理,這就是本文的主要內容,廢話不多說,上乾貨。

本文主要內容

  1. 用戶自定義屬性的使用
  2. maven生命周期詳解
  3. maven插件詳解

用戶屬性Properties的使用

項目pom.xml中,有下面這樣一段依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
</dependencies>

大家看一下上面的配置,有沒有什麼問題?

他們的groupId和version都是一樣的,程序員面對與重複的代碼,需要提取,如果是java代碼中,我們可以將同樣的代碼或者變量值,提取成方法或者變量,做到重用,方便維護。

那麼maven的pom.xml中也支持這麼做:

<properties>
    <spring.group>org.springframework</spring.group>
    <spring.version>5.2.1.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>${spring.group}</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>${spring.group}</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>${spring.group}</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

大家看一下上面的代碼,properties位於pom.xml中的,是project元素的子元素,用戶可以在properties中自定義一些用戶屬性,然後可以在其他地方使用${屬性名稱}這種方式進行引用。

生命周期

我們開發一個項目的時候,通常有這些環節:創建項目、編寫代碼、清理已編譯的代碼、編譯代碼、執行單元測試、打包、集成測試、驗證、部署、生成站點等,這些環節組成了項目的生命周期,這些過程也叫做項目的構建過程,幾乎所有的項目都由這些環節中的其中幾個,創建項目和編寫代碼是我們程序員需要多參与的,其他的都可以做成自動化的方式。

用過ant的朋友回憶一下,在maven出現以前,開發人員每天都在對項目進行清理、編譯、執行單元測試、打包、部署等操作,雖然大家都在做這些工作,但是沒有一個統一的標準,項目和項目之間,公司和公司之間,大多數都是各寫各的,寫法是千奇百怪,能滿足自身需求就可以了,但是換個項目就得從頭再來,這些操作又需要重新編寫腳本。

而maven出來之後,項目生命周期中的這些環節都被簡化了,被規範化了,maven出現之前,項目的結構沒有一個統一的標準,所以生命周期中各個環節對應的自動化腳本也是各種各樣,而maven約定好了項目的結構,源碼的位置、資源文件的位置、測試代碼的位置、測試用到的資源文件的位置、靜態資源的位置、打包之後文件的位置等,這些都是maven約定好的,所以清理代碼用一個命令mvn clean就可以完成,不需要我們去配置清理的目標目錄;用mvn compile命令就可以完成編譯的操作;用mvn test就可以自動運行測試用例;用mvn package就可以將項目打包為jar、war格式的包,能夠如此簡單,主要還是maven中約定大於配置的結果。

maven中生命周期詳解

maven將項目的生命周期抽象成了3套生命周期,每套生命周期又包含多個階段,每套中具體包含哪些階段是maven已經約定好的,但是每個階段具體需要做什麼,是用戶可以自己指定的。

maven中定義的3套生命周期:

  1. clean生命周期
  2. default生命周期
  3. site生命周期

上面這3套生命周期是相互獨立的,沒有依賴關係的,而每套生命周期中有多個階段,每套中的多個階段是有先後順序的,並且後面的階段依賴於前面的階段,而用戶可以直接使用mvn命令來調用這些階段去完成項目生命周期中具體的操作,命令是:

mvn 生命周期階段

通俗點解釋:

maven中的3套生命周期相當於maven定義了3個類來解決項目生命周期中需要的各種操作,每個類中有多個方法,這些方法就是指具體的階段,方法名稱就是階段的名稱,每個類的方法是有順序的,當執行某個方法的時候,這個方法前面的方法也會執行。具體每個方法中需要執行什麼,這個是通過插件的方式讓用戶去配置的,所以非常靈活。

用戶執行mvn 階段名稱就相當於調用了具體的某個方法。

下面我們來看看每個生命周期中有哪些階段(也就是我們說的每個類中有哪些方法,順序是什麼樣的)。

clean生命周期

clean生命周期的目的是清理項目,它包含三個階段:

生命周期階段 描述
pre-clean 執行一些需要在clean之前完成的工作
clean 移除所有上一次構建生成的文件
post-clean 執行一些需要在clean之後立刻完成的工作

用戶可以通過mvn pre-clean來調用clean生命周期中的pre-clean階段需要執行的操作。

調用mvn post-clean會執行上面3個階段所有的操作,上文中有說過,每個生命周期中的後面的階段會依賴於前面的階段,當執行某個階段的時候,會先執行其前面的階段。

default生命周期

這個是maven主要的生命周期,主要被用於構建應用,包含了23個階段。

生命周期階段 描述
validate 校驗:校驗項目是否正確並且所有必要的信息可以完成項目的構建過程。
initialize 初始化:初始化構建狀態,比如設置屬性值。
generate-sources 生成源代碼:生成包含在編譯階段中的任何源代碼。
process-sources 處理源代碼:處理源代碼,比如說,過濾任意值。
generate-resources 生成資源文件:生成將會包含在項目包中的資源文件。
process-resources 編譯:複製和處理資源到目標目錄,為打包階段最好準備。
compile 處理類文件:編譯項目的源代碼。
process-classes 處理類文件:處理編譯生成的文件,比如說對Java class文件做字節碼改善優化。
generate-test-sources 生成測試源代碼:生成包含在編譯階段中的任何測試源代碼。
process-test-sources 處理測試源代碼:處理測試源代碼,比如說,過濾任意值。
generate-test-resources 生成測試源文件:為測試創建資源文件。
process-test-resources 處理測試源文件:複製和處理測試資源到目標目錄。
test-compile 編譯測試源碼:編譯測試源代碼到測試目標目錄.
process-test-classes 處理測試類文件:處理測試源碼編譯生成的文件。
test 測試:使用合適的單元測試框架運行測試(Juint是其中之一)。
prepare-package 準備打包:在實際打包之前,執行任何的必要的操作為打包做準備。
package 打包:將編譯后的代碼打包成可分發格式的文件,比如JAR、WAR或者EAR文件。
pre-integration-test 集成測試前:在執行集成測試前進行必要的動作。比如說,搭建需要的環境。
integration-test 集成測試:處理和部署項目到可以運行集成測試環境中。
post-integration-test 集成測試后:在執行集成測試完成後進行必要的動作。比如說,清理集成測試環境。
verify 驗證:運行任意的檢查來驗證項目包有效且達到質量標準。
install 安裝:安裝項目包到本地倉庫,這樣項目包可以用作其他本地項目的依賴。
deploy 部署:將最終的項目包複製到遠程倉庫中與其他開發者和項目共享。

site生命周期

site生命周期的目的是建立和發布項目站點,Maven能夠基於pom.xml所包含的信息,自動生成一個友好的站點,方便團隊交流和發布項目信息。主要包含以下4個階段:

階段 描述
pre-site 執行一些需要在生成站點文檔之前完成的工作
site 生成項目的站點文檔
post-site 執行一些需要在生成站點文檔之後完成的工作,並且為部署做準備
site-deploy 將生成的站點文檔部署到特定的服務器上

mvn命令和生命周期

從命令行執行maven任務的最主要方式就是調用maven生命周期的階段,需要注意的是,每套生命周期是相互獨立的,但是每套生命周期中階段是有前後依賴關係的,執行某個的時候,會按序先執行其前面所有的。

mvn執行階段的命令格式是:

mvn 階段1 [階段2] [階段n]

多個階段的名稱之間用空格隔開。

下面我們舉一些常見的例子來說明一下:

mvn clean

該命令是調用clean生命周期的clean階段,實際執行的階段為clean生命周期中的pre-clean和clean階段。

mvn test

該命令調用default生命周期的test階段,實際上會從default生命周期的第一個階段(validate)開始執行一直到test階段結束。這裏面包含了代碼的編譯,運行測試用例。

mvn clean install

這個命令中執行了兩個階段:cleaninstall,從上面3個生命周期的階段列表中找一下,可以看出clean位於clean生命周期的表格中,install位於default生命周期的表格中,所以這個命令會先從clean生命周期中的pre-clean階段開始執行一直到clean生命周期的clean階段;然後會繼續從default生命周期的validate階段開始執行一直到default生命周期的install階段。

這裏面包含了清理上次構建的結果,編譯代碼,測試,打包,將打好的包安裝到本地倉庫。

mvn clean deploy

這個命令也比較常用,會先按順序執行clean生命周期的[pre-clean,clean]這個閉區間內所有的階段,然後按序執行default生命周期的[validate,deploy]這個閉區間內的所有階段(也就是default生命周期中的所有階段)。這個命令內部包含了清理上次構建的結果、編譯代碼、運行單元測試、打包、將打好的包安裝到本地倉庫、將打好的包發布到私服倉庫。

上面說了這麼多理論,我們來看一下效果。

案例

創建一個maven項目

打開idea,點擊File->New->Project,選擇Maven,如下:

點擊Next,輸入項目坐標信息,如下:

點擊Next,輸入Project name 為maven-chat06,如下:

點擊Finish,創建成功,如下:

配置一下idea的maven環境,點擊File->Settings,如下圖:

點擊上面的OK完成配置。

還原~/.m2/settings.xml的配置到初始狀態,操作如下:

將M2_HOME/conf/settings.xml複製到~/.m2/settings.xml目錄,如果存在先備份一個,然後進行覆蓋。

maven項目是約定大於配置的,項目結構是按照maven的約定生成好的,關於maven約定項目結構,我們再來回顧一下。

Maven 提倡使用一個共同的標準目錄結構,Maven 使用約定優於配置的原則,大家盡可能的遵守這樣的目錄結構,如下所示:

目錄 目的
${basedir} 存放pom.xml和所有的子目錄
${basedir}/src/main/java 項目的java源代碼
${basedir}/src/main/resources 項目的資源,比如說property文件,springmvc.xml
${basedir}/src/test/java 項目的測試類,比如說Junit代碼
${basedir}/src/test/resources 測試用的資源
${basedir}/src/main/webapp/WEB-INF web應用文件目錄,web項目的信息,比如存放web.xml、本地圖片、jsp視圖頁面
${basedir}/target 打包輸出目錄
${basedir}/target/classes 編譯輸出目錄
${basedir}/target/test-classes 測試編譯輸出目錄
Test.java Maven只會自動運行符合該命名規則的測試類
~/.m2/repository Maven默認的本地倉庫目錄位置

結合剛才項目的結構和這個表格領會一下,下面我們來感受一下執行生命周期中的階段產生的效果。

修改pom.xml,如下:

<?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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 配置maven編譯的時候採用的編譯器版本 -->
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        <!-- 指定源代碼是什麼版本的,如果源碼和這個版本不符將報錯,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-source參數 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <!-- 該命令用於指定生成的class文件將保證和哪個版本的虛擬機進行兼容,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-target參數 -->
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

上面properties這個元素中的配置,可能大家看不懂,先略過,後面會詳解。

創建一個Demo類,源碼是放在src/main/java目錄中,如下:

package com.javacode2018.maven;

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
    public static void main(String[] args) {
        System.out.println("歡迎和【路人甲java】一起學習maven,帶你成為maven高手!");
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(i);
        }
        list.forEach(System.out::print);
    }
}
mvn clean命令效果

在當前項目pom.xml所在目錄中執行下面命令:

mvn clean

效果如下:

D:\code\IdeaProjects\maven-chat06>mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.477 s
[INFO] Finished at: 2019-11-15T18:46:13+08:00
[INFO] ------------------------------------------------------------------------

上面有提到編譯、打包的內容都放在target目錄,看上面輸出中有個Deleting target目錄,說明mvn clean是對這個目錄進行清理,這個目錄中目前是空的。

mvn compile命令效果

先看一下項目的目錄中是沒有target目錄的,如下圖:

cmd中執行:

mvn compile

輸出:

D:\code\IdeaProjects\maven-chat06>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.897 s
[INFO] Finished at: 2019-11-15T18:53:12+08:00
[INFO] ------------------------------------------------------------------------

可以看到上面有Compiling 1 source ....,這個是編譯Demo.java,然後輸出到了target中的classes目錄,再來看一下項目的結構,如下圖:

上圖中匡紅的是新生成的。

mvn clean package效果
D:\code\IdeaProjects\maven-chat06>mvn clean package
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-chat06 ---
[INFO] Building jar: D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.177 s
[INFO] Finished at: 2019-11-15T18:56:59+08:00
[INFO] ------------------------------------------------------------------------

從輸出中看一下,有個Building jar ...,生成了一個jar包,這個項目的pom.xml中的packaging元素沒有指定值,那就取默認值jar,表示這個構件是一個jar包,mvn clean package先清理編譯的代碼,然後執行了default生命周期的compile階段,將項目打成了jar放在了target目錄,如下圖:

大家看到上面還有很多其他的輸出,這個大家可以先忽略,本文看完了,都會明白的。

mvn clean install效果
D:\code\IdeaProjects\maven-chat06>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-chat06 ---
[INFO] Building jar: D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ maven-chat06 ---
[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.jar
[INFO] Installing D:\code\IdeaProjects\maven-chat06\pom.xml to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.977 s
[INFO] Finished at: 2019-11-15T19:05:21+08:00
[INFO] ------------------------------------------------------------------------

mvn clean package的輸出對比一下,多了幾行輸出,主要是多了2個Installing...,將項目打成jar包以及項目的pom文件放到本地倉庫去了,也就是將構件打包安裝到本地倉庫了。

上面幾個mvn命令的案例,都是通過mvn命令去執行了mvn中定義的生命周期中的階段,然後完成了很多看似內部很複雜的操作。比如打包,內部包含很多複雜的操作,maven都幫我們屏蔽了,通過一個簡單的mvn package就完成了。

上面也有說過,每個階段具體做的事情是由maven插件來完成的。

我們在回頭看一下上面一個輸出中,有很多類似於maven-xxxx-plugin:版本:xxx這樣的內容,這個就是表示當前在運行這個插件來完成對應階段的操作,mvn 階段明明執行的是階段,但是實際輸出中確實插件在幹活,那麼階段是如何和插件關聯起來的呢?插件又是什麼呢?

注意以下所有命令都在cmd窗口執行,執行位置位於上面這個項目的pom.xml所在目錄。

Maven插件

maven插件主要是為maven中生命周期中的階段服務的,maven中只是定義了3套生命周期,以及每套生命周期中有哪些階段,具體每個階段中執行什麼操作,完全是交給插件去乾的。

maven中的插件就相當於一些工具,比如編譯代碼的工具,運行測試用例的工具,打包代碼的工具,將代碼上傳到本地倉庫的工具,將代碼部署到遠程倉庫的工具等等,這些都是maven中的插件。

插件可以通過mvn命令的方式調用直接運行,或者將插件和maven生命周期的階段進行綁定,然後通過mvn 階段的方式執行階段的時候,會自動執行和這些階段綁定的插件。

插件目標

maven中的插件以jar的方式存在於倉庫中,和其他構件是一樣的,也是通過坐標進行訪問,每個插件中可能為了代碼可以重用,一個插件可能包含了多個功能,比如編譯代碼的插件,可以編譯源代碼、也可以編譯測試代碼;插件中的每個功能就叫做插件的目標(Plugin Goal),每個插件中可能包含一個或者多個插件目標(Plugin Goal)

目標參數

插件目標是用來執行任務的,那麼執行任務肯定是有參數配的,這些就是目標的參數,每個插件目標對應於java中的一個類,參數就對應於這個類中的屬性。

列出插件所有目標
mvn 插件goupId:插件artifactId[:插件version]:help
mvn 插件前綴:help

上面插件前綴的先略過,我們先看第一種效果。

如:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-clean-plugin:2.5

Maven Clean Plugin
  The Maven Clean Plugin is a plugin that removes files generated at build-time
  in a project's directory.

This plugin has 2 goals:

clean:clean
  Goal which cleans the build.
  This attempts to clean a project's working directory of the files that were
  generated at build-time. By default, it discovers and deletes the directories
  configured in project.build.directory, project.build.outputDirectory,
  project.build.testOutputDirectory, and project.reporting.outputDirectory.

  Files outside the default may also be included in the deletion by configuring
  the filesets tag.

clean:help
  Display help information on maven-clean-plugin.
  Call
    mvn clean:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.

上面列出了maven-clean-plugin這個插件所有的目標,有2個,分別是clean:clean、clean:help,分號後面的部分是目標名稱,分號前面的部分是插件的前綴,每個目標的後面包含對這個目標的詳細解釋說明,關於前綴的後面會有詳細介紹。

查看插件目標參數列表
mvn 插件goupId:插件artifactId[:插件version]:help -Dgoal=目標名稱 -Ddetail
mvn 插件前綴:help -Dgoal=目標名稱 -Ddetail

上面命令中的-Ddetail用戶輸出目標詳細的參數列表信息,如果沒有這個,目標的參數列表不會輸出出來,看效果。

如:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help -Dgoal=help -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-clean-plugin:2.5

Maven Clean Plugin
  The Maven Clean Plugin is a plugin that removes files generated at build-time
  in a project's directory.

clean:help
  Display help information on maven-clean-plugin.
  Call
    mvn clean:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.

  Available parameters:

    detail (Default: false)
      If true, display all settable properties for each goal.
      Expression: ${detail}

    goal
      The name of the goal for which to show help. If unspecified, all goals
      will be displayed.
      Expression: ${goal}

    indentSize (Default: 2)
      The number of spaces per indentation level, should be positive.
      Expression: ${indentSize}

    lineLength (Default: 80)
      The maximum length of a display line, should be positive.
      Expression: ${lineLength}

上面列出了clean插件的help目標的詳細參數信息。

注意上面參數詳細參數說明中有Expression: ${xxx}這樣的部分,這種表示給這個運行的目標傳參,可以通過mvn -Dxxx這種方式傳參,xxx${xxx}中的xxx部分,這個xxx有時候和目標參數的名稱不一致,所以這點需要注意,運行帶參數的目標,看一下效果:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help -Dgoal=help -Ddetail=false
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-clean-plugin:2.5

Maven Clean Plugin
  The Maven Clean Plugin is a plugin that removes files generated at build-time
  in a project's directory.

clean:help
  Display help information on maven-clean-plugin.
  Call
    mvn clean:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.332 s
[INFO] Finished at: 2019-11-18T15:14:56+08:00
[INFO] ------------------------------------------------------------------------

上面傳了一個detail=false,上面未輸出目標的詳細參數信息。

命令行運行插件
mvn 插件goupId:插件artifactId[:插件version]:插件目標 [-D目標參數1] [-D目標參數2] [-D目標參數n]
mvn 插件前綴:插件目標  [-D目標參數1] [-D目標參數2] [-D目標參數n]

案例:

maven中運行測試用例使用到的插件坐標是:

<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12.4</version>
</dependency>

我們看一下這個插件有哪些目標:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:help
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:help (default-cli) @ maven-chat06 ---
[INFO] Maven Surefire Plugin 2.12.4
  Surefire is a test framework project.

This plugin has 2 goals:

surefire:help
  Display help information on maven-surefire-plugin.
  Call mvn surefire:help -Ddetail=true -Dgoal=<goal-name> to display parameter
  details.

surefire:test
  Run tests using Surefire.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.662 s
[INFO] Finished at: 2019-11-18T15:26:26+08:00
[INFO] ------------------------------------------------------------------------

maven-surefire-plugin插件有2個目標helptest,描述中可以看出test目標是用來運行測試用例的。

我們看一下test目標對應的參數列表:

test目標對應的參數太多,我們只列出了部分參數,如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:help -Dgoal=test -Ddetail=true
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:help (default-cli) @ maven-chat06 ---
[INFO] Maven Surefire Plugin 2.12.4
  Surefire is a test framework project.

surefire:test
  Run tests using Surefire.

  Available parameters:

    skip (Default: false)
      Set this to 'true' to bypass unit tests entirely. Its use is NOT
      RECOMMENDED, especially if you enable it using the 'maven.test.skip'
      property, because maven.test.skip disables both running the tests and
      compiling the tests. Consider using the skipTests parameter instead.

大家認真看一下skip這個參數說明,這個參數默認是false,如果設置為true的時候,項目將跳過測試代碼的編譯和測試用例的執行,可以maven.test.skip這個屬性來進行命令行傳參,將其傳遞給test目標的skip屬性,這個通過-D傳遞的參數名稱就和目標參數名稱不一樣了,所以需要注意-D後面並不一定是參數名稱。

我們來運行一下test目標看看效果。

先看一下不加參數的效果:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.640 s
[INFO] Finished at: 2019-11-18T15:33:48+08:00
[INFO] ------------------------------------------------------------------------

maven.skip.test=true的效果如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test -Dmaven.test.skip=true
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.652 s
[INFO] Finished at: 2019-11-18T15:34:45+08:00
[INFO] ------------------------------------------------------------------------

對比一下上面2個輸出,下面的多了一行如下:

[INFO] Tests are skipped.

說明跳過了測試的執行。

插件傳參的2種方式

剛才上面講了一種通過-D後面跟用戶屬性的方式給用戶傳參,還有一種方式,在pom.xml中properties的用戶自定義屬性中進行配置,如下:

修改項目maven-chat06的pom.xml,properties中加入:

<maven.test.skip>true</maven.test.skip>

cmd中運行:

mvn org.apache.maven.plugins:maven-surefire-plugin:test

效果如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.638 s
[INFO] Finished at: 2019-11-18T15:46:04+08:00
[INFO] ------------------------------------------------------------------------

輸出中也有Tests are skipped.,說明也跳過了測試,和-Dmaven.test.skip=true效果一樣。

上面說的都是插件目標的東西,那麼插件目標是如何和生命周期關聯起來的呢?繼續向下看。

獲取插件目標詳細描述信息的另外一種方式
mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目標名稱 -Ddetail
mvn help:describe -Dplugin=插件前綴 -Dgoal=目標名稱 -Ddetail

上面這個命令調用的是help插件的describe這個目標,這個目標可以列出其他指定插件目標的詳細信息,看效果:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=org.apache.maven.plugins:maven-surefire-plugin -Dgoal=test -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 ---
[INFO] Mojo: 'surefire:test'
surefire:test
  Description: Run tests using Surefire.
  Implementation: org.apache.maven.plugin.surefire.SurefirePlugin
  Language: java
  Bound to phase: test

  Available parameters:

    additionalClasspathElements
      Additional elements to be appended to the classpath.

    argLine
      User property: argLine
      Arbitrary JVM options to set on the command line.

    skip (Default: false)
      User property: maven.test.skip
      Set this to 'true' to bypass unit tests entirely. Its use is NOT
      RECOMMENDED, especially if you enable it using the 'maven.test.skip'
      property, because maven.test.skip disables both running the tests and
      compiling the tests. Consider using the skipTests parameter instead.

可以拿這種和上面獲取插件目標參數詳情列表對比一下,上面這個更詳細一些,參數說明中多了一行User property: 屬性名稱,這個屬性名稱可以通過兩種方式傳遞:

  1. mvn命令-D屬性名稱的方式傳遞
  2. pom.xml中properties中定義的方式指定。

現在可以大家估計可以知道我們一直用的-Dmaven.test.skip為什麼可以跳過測試代碼的編譯和單元測試的執行了吧。

插件前綴

運行插件的時候,可以通過指定插件坐標的方式運行,但是插件的坐標信息過於複雜,也不方便寫和記憶,所以maven中給插件定義了一些簡捷的插件前綴,可以通過插件前綴來運行指定的插件。

可以通過下面命令查看到插件的前綴:

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version]

示例效果:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=org.apache.maven.plugins:maven-surefire-plugin
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-surefire-plugin:2.12.4

Name: Maven Surefire Plugin
Description: Surefire is a test framework project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-surefire-plugin
Version: 2.12.4
Goal Prefix: surefire

輸出中的Goal Prefix:部分對應的就是插件的前綴,上面這個插件的前綴是surefire

我們使用前綴來運行一下插件感受一下效果:

D:\code\IdeaProjects\maven-chat06>mvn surefire:test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.934 s
[INFO] Finished at: 2019-11-18T16:18:42+08:00
[INFO] ------------------------------------------------------------------------

上面通過別名來運行插件maven-surefire-plugintest目標,是不是簡潔了很多。

上面用了很多mvn help:這個命令,這個調用的是maven-help-plugin插件的功能,help是插件的前綴,它的坐標是:

<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-help-plugin</artifactId>
    <version>3.2.0</version>
</dependency>

插件和生命周期階段綁定

maven只是定義了生命周期中的階段,而沒有定義每個階段中具體的實現,這些實現是由插件的目標來完成的,所以需要將階段和插件目標進行綁定,來讓插件目標幫助生命周期的階段做具體的工作,生命周期中的每個階段支持綁定多個插件的多個目標。

當我們將生命周期中的階段和插件的目標進行綁定的時候,執行mvn 階段就可以執行和這些階段綁定的插件目標

maven內置插件以及綁定

maven為了讓我們不用做任何配置就可以實現一些項目的構建操作,比如運行mvn clean就可以幫我們清理代碼,運行mvn install就可以將構件安裝到本地倉庫,所以maven幫我們做了一些事情,maven內部已經提供了很多默認的插件,而將一些階段默認和這些插件階段綁定好了,所以我們不用做任何配置就可以執行清理代碼、編譯代碼、測試、打包、安裝到本地倉庫、上傳到遠程倉庫等階段的操作,是因為maven已經默認給這些階段綁定好了插件目標,所以不需要我們再去配置,就直接可以運行,這些都是maven內置綁定幫我們做的事情,我們來看看maven有哪些內置綁定。

maven內置綁定

clean生命周期階段與插件綁定關係
生命周期階段 插件:目標
pre-clean
clean maven-clean-plugin:clean
post-clean

clean周期中只有clean階段默認綁定了maven-clean-plugin插件的clean目標。maven-clean-plugin插件的clean目標作用就是刪除項目的輸出目錄。

default生命周期階段與插件綁定關係

default生命周期中有23個階段,我只列出有默認綁定的,其他的沒有列出的沒有綁定任何插件,因此沒有任何實際的行為。

生命周期階段 插件:目標 執行任務
process-resources maven-resources-plugin:resources 複製主資源文件至主輸出目錄
compile maven-compiler-plugin:compile 編譯主代碼至主輸出目錄
process-test-resources maven-resources-plugin:testResources 複製測試資源文件至測試輸出目錄
test-compile maven-compiler-plugin:testCompile 編譯測試代碼至測試輸出目錄
test maven-surefile-plugin:test 執行測試用例
package maven-jar-plugin:jar 創建項目jar包
install maven-install-plugin:install 將輸出構件安裝到本地倉庫
deploy maven-deploy-plugin:deploy 將輸出的構件部署到遠程倉庫
site生命周期階段與插件綁定關係
生命周期階段 插件:目標
pre-site
site maven-site-plugin:site
post-site
site-deploy maven-site-plugin:deploy

來幾個案例解說一下。

mvn clean

該命令是調用clean生命周期的clean階段,實際執行的階段為clean生命周期中的pre-clean和clean階段,從上面內置綁定表格中找一下,可以看到只有clean階段綁定了maven-clean-plugin插件的clean目標,所以運行mvn clean的時候,實際上會調用maven-clean-plugin插件的clean目標來清理代碼。

運行一下看一下效果:

D:\code\IdeaProjects\maven-chat06>mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.408 s
[INFO] Finished at: 2019-11-18T16:34:14+08:00
[INFO] ------------------------------------------------------------------------

上面有一行輸出如下:

[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---

這個表示調用的插件是:maven-clean-plugin,版本是:2.5,插件的目標是:clean

mvn test

該命令調用default生命周期的test階段,實際上會從default生命周期的第一個階段(validate)開始執行一直到test階段結束。這裏面包含了代碼的編譯,運行測試用例。還是和上面的分析過程一樣,對照上面表格中的綁定關係,可以得到mvn test會調用下面一些插件的目標:

maven-resources-plugin:resources
maven-compiler-plugin:compile
maven-resources-plugin:testResources
maven-compiler-plugin:testCompile
maven-surefile-plugin:test

我們來驗證一下,看看是不是和我們分析的一樣:

D:\code\IdeaProjects\maven-chat06>mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.880 s
[INFO] Finished at: 2019-11-18T16:36:55+08:00
[INFO] ------------------------------------------------------------------------

從上面輸出中可以看到調用了5個插件的目標,和分析的一樣。

再來看一個跳過測試的例子,如下:

D:\code\IdeaProjects\maven-chat06>mvn test -Dmaven.skip.test=true
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.384 s
[INFO] Finished at: 2019-11-18T16:38:52+08:00
[INFO] ------------------------------------------------------------------------

上面這個是不是很熟悉,經常用到的跳過測試,為什麼這麼寫,我想大家都知道了吧。

其他幾個mvn compilemvn installmvn deploy建議大家也自己去玩玩,加深理解。

自定義綁定

除了默認綁定的一些操作,我們自己也可以將一些階段綁定到指定的插件目標上來完成一些操作,這種自定義綁定讓maven項目在構件的過程中可以執行更多更豐富的操作。

常見的一個案例是:創建項目的源碼jar包,將其安裝到倉庫中,內置插件綁定關係中沒有涉及到這一步的任務,所以需要用戶自己配置。

插件maven-source-pluginjar-no-fork可以幫助我們完成該任務,我們將這個目標綁定在default生命周期的verify階段上面,這個階段沒有任何默認綁定,verify是在測試完成之後並將構件安裝到本地倉庫之前執行的階段,在這個階段我們生成源碼,配置如下:

maven-chat06中的pom.xml加入如下配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.2.0</version>
            <executions>
                <!-- 使用插件需要執行的任務 -->
                <execution>
                    <!-- 任務id -->
                    <id>attach-source</id>
                    <!-- 任務中插件的目標,可以指定多個 -->
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                    <!-- 綁定的階段 -->
                    <phase>verify</phase>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意上面配置的attach-source,後面輸出中會有。

id:任務的id,需唯一,如果不指定,默認為default

每個插件的配置在pom.xml的plugins元素中只能寫一次,否則會有警告。

最終pom.xml如下:

<?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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 配置maven編譯的時候採用的編譯器版本 -->
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        <!-- 指定源代碼是什麼版本的,如果源碼和這個版本不符將報錯,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-source參數 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <!-- 該命令用於指定生成的class文件將保證和哪個版本的虛擬機進行兼容,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-target參數 -->
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <!-- 使用插件需要執行的任務 -->
                    <execution>
                        <!-- 任務id -->
                        <id>attach-source</id>
                        <!-- 任務中插件的目標,可以指定多個 -->
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                        <!-- 綁定的階段 -->
                        <phase>verify</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

運行下面命令:

mvn install

效果:

D:\code\IdeaProjects\maven-chat06>mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-chat06 ---
[INFO]
[INFO] --- maven-source-plugin:3.2.0:jar-no-fork (attach-source) @ maven-chat06 ---
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ maven-chat06 ---
[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.jar
[INFO] Installing D:\code\IdeaProjects\maven-chat06\pom.xml to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.pom
[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT-sources.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT-sources.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.268 s
[INFO] Finished at: 2019-11-18T16:59:12+08:00
[INFO] ------------------------------------------------------------------------

上面有個輸出如下:

maven-source-plugin:3.2.0:jar-no-fork (attach-source) @ maven-chat06 ---

可以看出調用了我們配置的插件生成源碼jar,上面的括號中的attach-source就是pom.xml中配置的任務id。

最後有個輸出:

[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT-sources.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT-sources.jar

可以看到將源碼安裝到本地倉庫了。

有些插件的目標默認會綁定到一些生命周期的階段中,那麼如果剛好插件默認綁定的階段和上面配置的一致,那麼上面phase元素可以不寫了,那麼怎麼查看插件的默認綁定呢?

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目標名稱 -Ddetail
mvn help:describe -Dplugin=插件前綴 -Dgoal=目標名稱 -Ddetail

我們看一下插件sourcejar-no-fork目標默認的綁定:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=source -Dgoal=jar-no-fork -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 ---
[INFO] Mojo: 'source:jar-no-fork'
source:jar-no-fork
  Description: This goal bundles all the sources into a jar archive. This
    goal functions the same as the jar goal but does not fork the build and is
    suitable for attaching to the build lifecycle.
  Implementation: org.apache.maven.plugins.source.SourceJarNoForkMojo
  Language: java
  Bound to phase: package

上面輸出中有個Bound to phase: package,表示默認綁定在了package階段上。

我們知道3套生命周期的運行時沒有依賴的,但是每套中的階段是有先後順序的,運行某個階段的時候,會先執行他前面所有的階段。清理代碼使用的是clean周期中的clean階段,編譯代碼用的是default周期中的compile階段,當直接運行mvn compile編譯代碼的時候並不會去清理代碼,編譯代碼的時候若發現文件沒有變動,會跳過沒有變化的文件進行編譯。如果我們想每次編譯之前強制先清理代碼,我們經常這麼寫:

mvn clean compile

上面的寫法是不是很熟悉,運行一下看看效果:

D:\code\IdeaProjects\maven-chat06>mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.666 s
[INFO] Finished at: 2019-11-18T17:16:53+08:00
[INFO] ------------------------------------------------------------------------

還有其他方式么?

我們剛才學了自定義綁定,我們可以在default生命周期的第一個階段validate綁定清理代碼的插件,那我們來通過自定義綁定來實現一下,project->build->plugins元素中加入下面配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.5</version>
    <executions>
        <!-- 使用插件需要執行的任務 -->
        <execution>
            <!-- 任務中插件的目標,可以指定多個 -->
            <id>clean-target</id>
            <goals>
                <goal>clean</goal>
            </goals>
            <!-- 綁定的階段 -->
            <phase>validate</phase>
        </execution>
    </executions>
</plugin>

運行下面命令看效果:

D:\code\IdeaProjects\maven-chat06>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (clean-target) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.630 s
[INFO] Finished at: 2019-11-18T17:22:37+08:00
[INFO] ------------------------------------------------------------------------

輸出中有:

[INFO] --- maven-clean-plugin:2.5:clean (clean-target) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target

這個表示運行了代碼清理的功能,進行了代碼清理,是不是感覺很爽,不用每次都寫clean了。

POM.xml插件配置詳解

插件目標共享參數配置

build->plugins->plugin中配置:

<!-- 插件參數配置,對插件中所有的目標起效 -->
<configuration>
    <目標參數名>參數值</目標參數名>
</configuration>

configuration節點下配置目標參數的值,節點名稱為目標的參數名稱,上面這種配置對當前插件的所有目標起效,也就是說這個插件中所有的目標共享此參數配置。

案例:

將案例中的pom.xml中的build元素修改成下面這樣。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.12.4</version>
            <!-- 插件參數配置,對插件中所有的目標起效 -->
            <configuration>
                <skip>true</skip>
            </configuration>
        </plugin>
    </plugins>
</build>

運行下面命令,看效果:

D:\code\IdeaProjects\maven-chat06>mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.328 s
[INFO] Finished at: 2019-11-18T17:30:58+08:00
[INFO] ------------------------------------------------------------------------

可以看到Test are skipped,說明跳過了測試,到此為止,跳過測試已經講了3種了:

1. mvn -Dmaven.test.skip=tue
2. properties中配置<maven.test.skip>true</maven.test.skip>
3. build中配置插件參數的方式

上面這個配置參數方式對當前插件的所有目標有效,如果想對指定的目標進行配置呢,用下面的方式。

插件目標參數配置

project->build->plugins->plugin->executions->execution元素中進行配置,如下:

<!-- 這個地方配置只對當前任務有效 -->
<configuration>
    <目標參數名>參數值</目標參數名>
</configuration>

上面這種配置常用於自定義插件綁定,只對當前任務有效。

感受一下效果,將pom.xml中的build元素改為下面內容:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.12.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>test</goal>
                        <goal>help</goal>
                    </goals>
                    <phase>pre-clean</phase>
                    <!-- 這個地方配置只對當前任務有效 -->
                    <configuration>
                        <skip>true</skip>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

上面自定義了一個綁定,在clean周期的pre-clean階段綁定了插件maven-surefire-plugin的兩個目標test和helpexecution元素沒有指定id,所以默認id是default

運行下面命令,見效果:

D:\code\IdeaProjects\maven-chat06>mvn pre-clean
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:help (default) @ maven-chat06 ---
[INFO] Maven Surefire Plugin 2.12.4
  Surefire is a test framework project.

This plugin has 2 goals:

surefire:help
  Display help information on maven-surefire-plugin.
  Call mvn surefire:help -Ddetail=true -Dgoal=<goal-name> to display parameter
  details.

surefire:test
  Run tests using Surefire.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.738 s
[INFO] Finished at: 2019-11-18T17:41:08+08:00
[INFO] ------------------------------------------------------------------------

可以看到上面輸出中運行了插件的兩個目標,和預期結果一致。

獲取maven插件信息

上面我們介紹了,可以通過下面命令獲取插件詳細介紹信息

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目標名稱 -Ddetail
mvn help:describe -Dplugin=插件前綴 -Dgoal=目標名稱 -Ddetail

更多maven插件的幫助文檔可以參考maven的官方網站,上面有詳細的介紹,建議大家去看看,地址:

http://maven.apache.org/plugins/

插件解析機制

為了方便用戶使用和配置插件,maven不需要用戶提供完整的插件坐標信息,就可以解析到正確的插件,不過我建議使用插件配置的時候最好還是配置完整的坐標信息,不然不利於新人的理解和問題的排查。

插件倉庫

與其他maven構件一樣,插件構件也是基於坐標存儲在maven倉庫中,有需要的時候,maven會從本地查找插件,如果不存在,則到遠程倉庫查找,找到了以後下載到本地倉庫,然後使用。

大家回憶一下,上一章講過的,pom.xml中可以配置依賴的構件的倉庫地址,如下:

<repositories>
    <repository>
        <id>maven-nexus</id>
        <url>http://localhost:8081/repository/maven-public/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

但是插件倉庫的配置和這個有一點不一樣,插件的是在pluginRepositories->pluginRepository元素中配置的,如下:

<pluginRepositories>
    <pluginRepository>
        <id>myplugin-repository</id>
        <url>http://repo1.maven.org/maven2/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
    </pluginRepository>
</pluginRepositories>

看一下上面2段配置,repository中的配置和pluginRepository中的子元素是一樣的,這個主意下就可以了。

插件的默認groupId

在pom.xml中配置插件的時候,如果是官方的插件,可以省略groupId

案例:

修改本篇示例中的pom.xml,如下:

<?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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <compilerVersion>1.8</compilerVersion>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

上面用到了maven-compiler-plugin,這個插件是編譯代碼的,是maven官方提供的插件,我們省略了groupId

上面這個插件用於編譯代碼的,編譯代碼的時候需要指定編譯器的版本,源碼的版本,目標代碼的版本,都是用的是1.8。

大家回頭去看一下,文章最開始的時候,在properties中有幾個屬性值是1.8的配置,這幾個值默認會被maven-compiler-plugin這個插件的上面3個參數獲取,具體可以去看一下這個插件compile目標的參數說明。

運行下面命令:

D:\code\IdeaProjects\maven-chat06>mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.692 s
[INFO] Finished at: 2019-11-18T18:11:34+08:00
[INFO] ------------------------------------------------------------------------

可以看到可以正常運行。

上面pom.xml省略了插件的groupId配置,如下:

<groupId>org.apache.maven.plugins</groupId>

maven在解析該插件的時候,會自動給這個插件補上默認的官方的groupId,所以可以正常運行,但是不建議大家這麼使用,容易讓新手比較懵逼。

插件前綴的解析

前面說過了使用mvn命令調用插件的時候,可以使用插件的前綴來代替繁瑣的插件坐標的方式,那麼maven是如何根據插件的前綴找到對應的插件的呢?

插件前綴與插件groupId:artifactId是一一對應的關係,這個關係的配置存儲在倉庫的元數據中,元數據位於下面2個xml中:

~/.m2/repository/org/apache/maven/plugins/maven-metadata-central.xml
~/.m2/repository/org/codehaus/mojo/maven-metadata-central.xml

接幾個圖,大家感受一下:

也可以通過在settings.xml中配置,讓maven檢查其他grouId上的插件元數據中前綴和插件關係的配置,如下:

<settings>
  <pluginGroups>
    <pluginGroup>com.your.plugins</pluginGroup>
  </pluginGroups>
</settings>

pluginGroups中有多個pluginGroup,可以配置你自己插件的元數據所在的groupId,然後可以通過前綴來訪問你自己的插件元數據目錄,此處先不細說,這個後面文章中講自定義插件的時候會再次說明。

查看項目最終pom.xml文件

我們的pom.xml默認會繼承maven頂級的一個父類pom.xml,頂級的pom.xml中指定了很多默認的配置,如生命周期中的階段和很多插件的綁定,這些如果我們想看到,到哪裡看呢?

mvn命令在項目中執行的時候,我們的pom.xml和父類的pom.xml最終會進行合併,當我們的pom.xml寫的比較複雜的時候,最終合併之後是什麼效果呢,我們可以通過下面這個命令查看:

mvn help:effective-pom

效果:

D:\code\IdeaProjects\maven-chat06>mvn help:effective-pom > 1.xml

上面我們將命令產生的結果輸出到項目的1.xml文件中了,我們看一下項目的1.xml的內容:

<?xml version="1.0" encoding="GBK"?>
<!-- ====================================================================== -->
<!--                                                                        -->
<!-- Generated by Maven Help Plugin on 2019-11-18T18:41:40+08:00            -->
<!-- See: http://maven.apache.org/plugins/maven-help-plugin/                -->
<!--                                                                        -->
<!-- ====================================================================== -->
<!-- ====================================================================== -->
<!--                                                                        -->
<!-- Effective POM for project                                              -->
<!-- 'com.javacode2018:maven-chat06:jar:1.0-SNAPSHOT'                       -->
<!--                                                                        -->
<!-- ====================================================================== -->
<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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <repositories>
        <repository>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <releases>
                <updatePolicy>never</updatePolicy>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <sourceDirectory>D:\code\IdeaProjects\maven-chat06\src\main\java</sourceDirectory>
        <scriptSourceDirectory>D:\code\IdeaProjects\maven-chat06\src\main\scripts</scriptSourceDirectory>
        <testSourceDirectory>D:\code\IdeaProjects\maven-chat06\src\test\java</testSourceDirectory>
        <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\classes</outputDirectory>
        <testOutputDirectory>D:\code\IdeaProjects\maven-chat06\target\test-classes</testOutputDirectory>
        <resources>
            <resource>
                <directory>D:\code\IdeaProjects\maven-chat06\src\main\resources</directory>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <directory>D:\code\IdeaProjects\maven-chat06\src\test\resources</directory>
            </testResource>
        </testResources>
        <directory>D:\code\IdeaProjects\maven-chat06\target</directory>
        <finalName>maven-chat06-1.0-SNAPSHOT</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.3</version>
                </plugin>
                <plugin>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.2-beta-5</version>
                </plugin>
                <plugin>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.8</version>
                </plugin>
                <plugin>
                    <artifactId>maven-release-plugin</artifactId>
                    <version>2.5.3</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <executions>
                    <execution>
                        <id>default-compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <compilerVersion>1.8</compilerVersion>
                            <source>1.8</source>
                            <target>1.8</target>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-testCompile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <compilerVersion>1.8</compilerVersion>
                            <source>1.8</source>
                            <target>1.8</target>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <compilerVersion>1.8</compilerVersion>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>2.5</version>
                <executions>
                    <execution>
                        <id>default-clean</id>
                        <phase>clean</phase>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <id>default-testResources</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testResources</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-resources</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>resources</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>default-jar</id>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <executions>
                    <execution>
                        <id>default-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-install-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>default-install</id>
                        <phase>install</phase>
                        <goals>
                            <goal>install</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.7</version>
                <executions>
                    <execution>
                        <id>default-deploy</id>
                        <phase>deploy</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.3</version>
                <executions>
                    <execution>
                        <id>default-site</id>
                        <phase>site</phase>
                        <goals>
                            <goal>site</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
                            <reportPlugins>
                                <reportPlugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-project-info-reports-plugin</artifactId>
                                </reportPlugin>
                            </reportPlugins>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-deploy</id>
                        <phase>site-deploy</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
                            <reportPlugins>
                                <reportPlugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-project-info-reports-plugin</artifactId>
                                </reportPlugin>
                            </reportPlugins>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
                    <reportPlugins>
                        <reportPlugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-project-info-reports-plugin</artifactId>
                        </reportPlugin>
                    </reportPlugins>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
    </reporting>
</project>

上面這個文件,大家一定要認真多看幾遍,理解一下,裡面包含太多東西,再重複一下,上面的文件多看幾遍!多看幾遍!多看幾遍!要理解!

課後題

留給大家2個問題

  1. 下面這個配置是干什麼的?給哪個插件使用的?

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
  2. mvn test運行測試用例的時候,測試用例類名的寫法默認是有規則的,這些規則有人知道么?從哪裡可以看到這些規則?如何自定義?

大家可以留言或者在技術群討論。

總結

本文內容比較多,希望大家多讀幾遍,要理解。如果有收穫的幫忙分享一下,你們的支持也是我不斷輸出的一個動力,希望大家都能夠學到東西!

Maven系列目錄

更多好文章

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!