Maven最佳实践:版本管理
Maven最佳實踐:版本管理
什么是版本管理
首先,這里說的版本管理(version management)不是指版本控制(version control),但是本文假設你擁有基本的版本控制的知識,了解subversion的基本用法。版本管理中說得版本是指構件(artifact)的版 本,而非源碼的版本(如subversion中常見的rXXX,或者git中一次提交都有個sha1的commit號)。
比如我有一個項目,其artifactId為myapp,隨著項目的進展,我們會生成這樣一些jar:myapp-1.0- SNAPSHOT.jar,myapp-1.0.jar,myapp-1.1-SNAPSHOT.jar,myapp-1.0.1.jar等等。你可能會 說,這很簡單啊,我在POM中改個version,mvn clean install不就完了?但這只是表面,本文我將講述,snapshot和release版本的區別,如何自動化版本發布(如果你的項目有幾十個 module,你就會覺得手工改POM來升級版本是很痛苦的事情),結合自動化發布的過程,這里還會介紹maven-release-plugin。此 外,一些scm概念也會被涉及到,比如tag和branch。
?
前提:版本控制
不管怎樣,我們都需要建立一個項目并提交到SCM中,這里我以subversion為例。你得有一個配置好的subversion repository,這里我建立了一個空的svn倉庫,其地址為:https://192.168.1.100:8443/svn/myapp/ 現在,該目錄下只有三個空的典型的子目錄:/trunk/, branches/, tags/。分別用來存放主干,分支,以及標簽。
接著將項目導入到svn倉庫中,到項目根目錄,運行如下命令:
svn import -m 'project initialization' https://192.168.1.100:8443/svn/myapp/trunk?
(注意,這么做你會將目錄下所有文件導入到svn庫中,但是這其中某些目錄和文件是不應該被導入的,如/target目錄,以及eclipse相關的項目文件)
目前,我們將項目的版本設置為1.0-SNAPSHOT。
?
為什么用SNAPSHOT?
我先說說如果沒有SNAPSHOT會是什么樣子。假設你的項目有2個模塊,A,B,其中A依賴B。這三個模塊分別由甲,乙兩個個人負責開發。在開發 過程中,因為A是依賴于B的,因此乙每次做一個改動都會影響到甲,于是,乙提交了一些更改后,需要讓甲看到。這個時候,怎么做呢?乙對甲說,“你簽出我的 代碼,build一下就OK了”,甲有點不情愿,但還是照做了,簽出代碼,svn clean install,然后,發現build出錯了,有個測試沒有pass。甲郁悶了,對乙說,“你的代碼根本不能用,我不想build,你build好了給 我”,乙看了看確實自己的代碼build不過,于是回去解決了,然后打了個jar包,扔給甲,甲對了對groupId,artifactId,放到了自己 的.m2/repository/目錄下,OK,能用了。
于是乙每次更新都這樣做,打包,復制,然后甲粘貼,使用……漸漸的,大家發現這是個很笨的辦法,這是純手工勞動阿,程序員最BS的就是重復勞動。一 天,甲對乙說,“你知道nexus么?你把你的jar發布到nexus上就可以了,我要用就自動去下載,這多棒!”乙說“哦?有這好東西,我去看看”于是 乙發現了nexus這塊新大陸,并成功的發布了B到nexus上。(見,Nexus入門指南,(圖文)?)。
但是,請注意,我們這里的一切都假設沒有SNAPSHOT,因此如果乙不更改版本,甲下載一次如B-1.0.jar之后,maven認為它已經有了 正確的B的版本,就不會再重新下載。甲發現了這個問題,對乙說“你的更新我看不到,你更新了么?”乙說“不可能!我看看”,于是檢查一下甲下載的C- 1.0.jar,發現那是幾天前的。乙一拍腦袋,說“這簡單,我更新一下我的版本就好了,我發布個B-1.1.jar上去,你更新下依賴版本”,甲照做 了,似乎這么做是可行的。
這里有一個問題,一次提交就更新一個版本,這明顯不是正確的管理辦法,此外,乙得不停的通知甲更新對B的依賴版本,累不累阿?1.0,或者說1.1,2.0,都代表了穩定,這樣隨隨便便的改版本,能穩定么?
所以Maven有SNAPSHOT版本的概念,它與release版本對應,后者是指1.0,1.1,2.0這樣穩定的發布版本。
現在乙可以將B的版本設置成1.0-SNAPSHOT,每次更改后,都mvn deploy到nexus中,每次deploy,maven都會將SNAPSHOT改成一個當前時間的timestamp,比如B-1.0- SNAPSHOT.jar到nexus中后,會成為這個樣子:B-1.0-20081017-020325-13.jar。Maven在處理A中對于B的 SNAPSHOT依賴時,會根據這樣的timestamp下載最新的jar,默認Maven每天?更新一次,如果你想讓Maven強制更新,可以使用-U參數,如:mvn clean install -U?。
現在事情簡化成了這個樣子:乙做更改,然后mvn deploy,甲用最簡單的maven命令就能得到最新的B。
?
從1.0-SNAPSHOT到1.0到1.1-SNAPSHOT
SNAPSHOT是快照的意思,項目到一個階段后,就需要發布一個正式的版本(release版本)。一次正式的發布需要這樣一些工作:
你可以手工一步步的做這些事情,無非就是一些svn操作,一些pom編輯,還有一些mvn操作。但是你應該明白,手工做這些事情,一來繁瑣,而來容易出錯。因此這里我介紹使用maven插件來自動化這一系列動作。
SCM
首先我們需要在POM中加入scm信息,這樣Maven才能夠替你完成svn操作,這里我的配置如下:
Xml代碼 ?需要注意的是,很多windows使用的tortoiseSVN客戶端,而沒有svn命令行客戶端,這會導致Maven所有svn相關的工作失敗,因此,你首先確保svn --version能夠運行。
分發倉庫
想要讓Maven幫我們自動發布,首先我們需要配置好分發倉庫。關于這一點,見Maven最佳實踐:Maven倉庫——分發構件至遠程倉庫。
maven-release-plugin
緊接著,我們需要配置maven-release-plugin,這個插件會幫助我們升級pom版本,提交,打tag,然后再升級版本,再提交,等等。基本配置如下:
Xml代碼 ?GAV我就不多解釋了,這里我們需要注意的是configuration元素下的tagBase元素,它代表了我們svn中的tag目錄,也就是 說,maven-release-plugin幫我們打tag的時候,其基礎目錄是什么。這里,我填寫了svn倉庫中的標準的tags目錄。
提交代碼
接著,確保你的所有代碼都提交了,如果你有未提交代碼,release插件會報錯,既然你要發布版本了,就表示代碼是穩定的,所以要么要么把代碼提交了,要么把本地的更改拋棄了。
開始工作
現在,屏住呼吸,執行:
mvn release:prepare
執行過程中,你會遇到這樣的提示:
What is the release version for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) 1.0: :
——“你想將1.0-SNAPSHOT發布為什么版本?默認是1.0。”我要的就是1.0,直接回車。
What is SCM release tag or label for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) myapp-1.0: :
——“發布的tag標簽名稱是什么?默認為myapp-1.0。”我還是要默認值,直接回車。
What is the new development version for "Unnamed - org.myorg:myapp:jar:1.0-SNAPSHOT"? (org.myorg:myapp) 1.1-SNAPSHOT: :
——“主干上新的版本是什么?默認為1.1-SNAPSHOT。”哈,release插件會自動幫我更新版本到1.1-SNAPSHOT,很好,直接回車。
然后屏幕刷阿刷,maven在build我們的項目,并進行了一些svn操作,你可以仔細查看下日志。
那么結果是什么呢?你可以瀏覽下svn倉庫:
- 我們多了一個tag:https://192.168.1.100:8443/svn/myapp/tags/myapp-1.0/,這就是需要發布的版本1.0。
- 再看看trunk中的POM,其版本自動升級成了1.1-SNAPSHOT。
這不正是我們想要的么?等等,好像缺了點什么,對了,1.0還沒有發布到倉庫中呢。
再一次屏住呼吸,執行:
mvn release:perform
maven-release-plugin會自動幫我們簽出剛才打的tag,然后打包,分發到遠程Maven倉庫中,至此,整個版本的升級,打標簽,發布等工作全部完成。我們可以在遠程Maven倉庫中看到正式發布的1.0版本。
這可是自動化的?,正式的?版本發布!
?
Maven的版本規則
前面我們提到了SNAPSHOT和Release版本的區別,現在看一下,為什么要有1.0,1.1,1.1.1這樣的版本,這里的規則是什么。
Maven主要是這樣定義版本規則的:
<主版本>.<次版本>.<增量版本>
比如說1.2.3,主版本是1,次版本是2,增量版本是3。
主版本一般來說代表了項目的重大的架構變更,比如說Maven 1和Maven 2,在架構上已經兩樣了,將來的Maven 3和Maven 2也會有很大的變化。次版本一般代表了一些功能的增加或變化,但沒有架構的變化,比如說Nexus 1.3較之于Nexus 1.2來說,增加了一系列新的或者改進的功能(倉庫鏡像支持,改進的倉庫管理界面等等),但從大的架構上來說,1.3和1.2沒什么區別。至于增量版本, 一般是一些小的bug fix,不會有重大的功能變化。
一般來說,在我們發布一次重要的版本之后,隨之會開發新的版本,比如說,myapp-1.1發布之后,就著手開發myapp-1.2了。由于 myapp-1.2有新的主要功能的添加和變化,在發布測試前,它會變得不穩定,而myapp-1.1是一個比較穩定的版本,現在的問題是,我們在 myapp-1.1中發現了一些bug(當然在1.2中也存在),為了能夠在段時間內修復bug并仍然發布穩定的版本,我們就會用到分支 (branch),我們基于1.1開啟一個分支1.1.1,在這個分支中修復bug,并快速發布。這既保證了版本的穩定,也能夠使bug得到快速修復,也 不同停止1.2的開發。只是,每次修復分支1.1.1中的bug后,需要merge代碼到1.2(主干)中。
上面講的就是我們為什么要用增量版本。
?
實戰分支
目前我們trunk的版本是1.1-SNAPSHOT,其實按照前面解釋的版本規則,應該是1.1.0-SNAPSHOT。
現在我們想要發布1.1.0,然后將主干升級為1.2.0-SNAPSHOT,同時開啟一個1.1.x的分支,用來修復1.1.0中的bug。
首先,在發布1.1.0之前,我們創建1.1.x分支,運行如下命令:
mvn release:branch -DbranchName=1.1.x -DupdateBranchVersions=true -DupdateWorkingCopyVersions=false
這是maven-release-plugin的branch目標,我們指定branch的名稱為1.1.x,表示這里會有版本1.1.1, 1.1.2等等。updateBranchVersions=true的意思是在分支中更新版本,而 updateWorkingCopyVersions=false是指不更改當前工作目錄(這里是trunk)的版本。
在運行該命令后,我們會遇到這樣的提示:
What is the branch version for "Unnamed - org.myorg:myapp:jar:1.1-SNAPSHOT"? (org.myorg:myapp) 1.1-SNAPSHOT: :
——"分支中的版本號是多少?默認為1.1-SNAPSHOT" 這時我們想要的版本是1.1.1-SNAPSHOT,因此輸入1.1.1-SNAPSHOT,回車,maven繼續執行直至結束。
接著,我們瀏覽svn倉庫,會看到這樣的目錄:https://192.168.1.100:8443/svn/myapp/branches/1.1.x/,打開其中的POM文件,其版本已經是1.1.1-SNAPSHOT。
分支創建好了,就可以使用release:prepare和release:perform為1.1.0打標簽,升級trunk至1.2.0-SNAPSHOT,然后分發1.1.0。
至此,一切OK。
?
小結
本文講述了如何使用Maven結合svn進行版本管理。解釋了Maven中SNAPSHOT版本的來由,以及Maven管理版本的規則。并結合 SCM的tag和branch概念展示了如何使用maven-release-plugin發布版本,以及創建分支。本文涉及的內容比較多,且略顯復雜, 不過掌握版本管理的技巧對于項目的正規化管理來說十分重要。Maven為我們提供了一些一套比較成熟的機制,值得掌握。
?
所有用Maven管理的真實的項目都應該是分模塊的,每個模塊都對應著一個pom.xml。它們之間通過繼承和聚合(也稱作多模塊,multi- module)相互關聯。那么,為什么要這么做呢?我們明明在開發一個項目,劃分模塊后,導入Eclipse變成了N個項目,這會帶來復雜度,給開發帶來 不便。
?
為了解釋原因,假設有這樣一個項目,很常見的Java Web應用。在這個應用中,我們分了幾層:
- Dao層負責數據庫交互,封裝了Hibernate交互的類。
- Service層處理業務邏輯,放一些Service接口和實現相關的Bean。
- Web層負責與客戶端交互,主要有一些Structs的Action類。
對應的,在一個項目中,我們會看到一些包名:
- org.myorg.app.dao
- org.myorg.app.service
- org.myorg.app.web
- org.myorg.app.util
這樣整個項目的框架就清晰了,但隨著項目的進行,你可能會遇到如下問題:
我們會發現,其實這里實際上沒有遵守一個設計模式原則:“高內聚,低耦合”。雖然我們通過包名劃分了層次,并且你還會說,這些包的依賴都是單向的,沒有包的環依賴。這很好,但還不夠,因為就構建層次來說,所有東西都被耦合在一起了。因此我們需要使用Maven劃分模塊。
?
一個簡單的Maven模塊結構是這樣的:
?
---- app-parent
???????????? |-- pom.xml (pom)
???????????? |
???????????? |-- app-util
???????????? |??????? |-- pom.xml (jar)
???????????? |
???????????? |-- app-dao
???????????? |??????? |-- pom.xml (jar)
???????????? |
???????????? |-- app-service
???????????? |??????? |-- pom.xml (jar)
???????????? |
???????????? |-- app-web
???????????? ? ? ? ?? |-- pom.xml (war) ??
?
上述簡單示意圖中,有一個父項目(app-parent)聚合很多子項目(app-util, app-dao, app-service, app-web)。每個項目,不管是父子,都含有一個pom.xml文件。而且要注意的是,小括號中標出了每個項目的打包類型。父項目是pom,也只能是 pom。子項目有jar,或者war。根據它包含的內容具體考慮。
?
這些模塊的依賴關系如下:
?
app-dao????? --> app-util
app-service --> app-dao
app-web???? --> app-service
?
注意依賴的傳遞性(大部分情況是傳遞的,除非你配置了特殊的依賴scope),app-dao依賴于app-util,app-service依賴 于app-dao,于是app-service也依賴于app-util。同理,app-web依賴于app-dao,app-util。
?
用項目層次的劃分替代包層次的劃分能給我們帶來如下好處:
接下來討論一下POM配置細節,實際上非常簡單,先看app-parent的pom.xml:
Xml代碼 ?Maven的坐標GAV(groupId, artifactId, version)在這里進行配置,這些都是必須的。特殊的地方在于,這里的packaging為pom。所有帶有子模塊的項目的packaging都為 pom。packaging如果不進行配置,它的默認值是jar,代表Maven會將項目打成一個jar包。
該配置重要的地方在于modules,例子中包含的子模塊有app-util, app-dao, app-service, app-war。在Maven build app-parent的時候,它會根據子模塊的相互依賴關系整理一個build順序,然后依次build。
這就是一個父模塊大概需要的配置,接下來看一下子模塊符合配置繼承父模塊。、
Xml代碼 ?app-util模塊繼承了app-parent父模塊,因此這個POM的一開始就聲明了對app-parent的引用,該引用是通過Maven坐 標GAV實現的。而關于項目app-util本身,它卻沒有聲明完整GAV,這里我們只看到了artifactId。這個POM并沒有錯,groupId 和version默認從父模塊繼承了。實際上子模塊從父模塊繼承一切東西,包括依賴,插件配置等等。
此外app-util配置了一個對于commons-lang的簡單依賴,這是最簡單的依賴配置形式。大部分情況,也是通過GAV引用的。
再看一下app-dao,它也是繼承于app-parent,同時依賴于app-util:
Xml代碼 ?該配置和app-util的配置幾乎沒什么差別,不同的地方在于,依賴變化了,app-dao依賴于app-util。這里要注意的是 version的值為${project.version},這個值是一個屬性引用,指向了POM的project/version的值,也就是這個 POM對應的version。由于app-dao的version繼承于app-parent,因此它的值就是1.0-SNAPSHOT。而app- util也繼承了這個值,因此在所有這些項目中,我們做到了保持版本一致。
這里還需要注意的是,app-dao依賴于app-util,而app-util又依賴于commons-lang,根據傳遞性,app-dao也擁有了對于commons-lang的依賴。
app-service我們跳過不談,它依賴于app-dao。我們最后看一下app-web:
Xml代碼 ?app-web依賴于app-service,因此配置了對其的依賴。
由于app-web是我們最終要部署的應用,因此它的packaging是war。為此,你需要有一個目錄src/main/webapp。并在這 個目錄下擁有web應用需要的文件,如/WEB-INF/web.xml。沒有web.xml,Maven會報告build失敗,此外你可能還會有這樣一 些子目錄:/js, /img, /css ... 。
?
看看Maven是如何build整個項目的,我們在 app-parent 根目錄中運行 mvn clean install ,輸出的末尾會有大致這樣的內容:
?
...
...
[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[app-web] in [/home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT]
[INFO] Processing war project
[INFO] Webapp assembled in[50 msecs]
[INFO] Building war: /home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war
[INFO] [install:install]
[INFO] Installing /home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war to /home/juven/.m2/repository/org/myorg/myapp/app-web/1.0-SNAPSHOT/app-web-1.0-SNAPSHOT.war
[INFO]?
[INFO]?
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] app-parent ............................................ SUCCESS [1.191s]
[INFO] app-util .............................................. SUCCESS [1.274s]
[INFO] app-dao ............................................... SUCCESS [0.583s]
[INFO] app-service ........................................... SUCCESS [0.593s]
[INFO] app-web ............................................... SUCCESS [0.976s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Dec 27 08:20:18 PST 2008
[INFO] Final Memory: 3M/17M
[INFO] ------------------------------------------------------------------------
?
注意Reactor Summary,整個項目根據我們希望的順序進行build。Maven根據我們的依賴配置,智能的安排了順序,app-util, app-dao, app-service, app-web。
?
最后,你可以在 app-web/target 目錄下找到文件 app-web-1.0-SNAPSHOT.war ,打開這個war包,在 /WEB-INF/lib 目錄看到了 commons-lang-2.4.jar,以及對應的app-util, app-dao, app-service 的jar包。Maven自動幫你處理了打包的事情,并且根據你的依賴配置幫你引入了相應的jar文件。
?
使用多模塊的Maven配置,可以幫助項目劃分模塊,鼓勵重用,防止POM變得過于龐大,方便某個模塊的構建,而不用每次都構建整個項目,并且使得針對某個模塊的特殊控制更為方便。本文同時給出了一個實際的配置樣例,展示了如何使用Maven配置多模塊項目。
總結
以上是生活随笔為你收集整理的Maven最佳实践:版本管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SSO单点登录三种情况的实现方式详解
- 下一篇: java分页通用类