Maven 劃分模塊最佳實(shí)踐
所有用Maven管理的真實(shí)的項(xiàng)目都應(yīng)該是分模塊的,每個(gè)模塊都對(duì)應(yīng)著一個(gè)pom.xml。它們之間通過(guò)繼承和聚合(也稱(chēng)作多模塊,multi-module)相互關(guān)聯(lián)。那么,為什么要這么做呢?我們明明在開(kāi)發(fā)一個(gè)項(xiàng)目,劃分模塊后,導(dǎo)入Eclipse變成了N個(gè)項(xiàng)目,這會(huì)帶來(lái)復(fù)雜度,給開(kāi)發(fā)帶來(lái)不便。
為了解釋原因,假設(shè)有這樣一個(gè)項(xiàng)目,很常見(jiàn)的Java Web應(yīng)用。在這個(gè)應(yīng)用中,我們分了幾層:
Dao層負(fù)責(zé)數(shù)據(jù)庫(kù)交互,封裝了Hibernate交互的類(lèi)。
Service層處理業(yè)務(wù)邏輯,放一些Service接口和實(shí)現(xiàn)相關(guān)的Bean。
Web層負(fù)責(zé)與客戶端交互,主要有一些Structs的Action類(lèi)。
對(duì)應(yīng)的,在一個(gè)項(xiàng)目中,我們會(huì)看到一些包名:
org.myorg.app.daoorg.myorg.app.serviceorg.myorg.app.weborg.myorg.app.util
這樣整個(gè)項(xiàng)目的框架就清晰了,但隨著項(xiàng)目的進(jìn)行,你可能會(huì)遇到如下問(wèn)題:
這個(gè)應(yīng)用可能需要有一個(gè)前臺(tái)和一個(gè)后臺(tái)管理端(web或者swing),你發(fā)現(xiàn)大部分dao,一些service,和大部分util是在兩個(gè)應(yīng)用中可用的。這樣的問(wèn)題,你一周內(nèi)遇到了好幾次。pom.xml中的依賴(lài)列表越來(lái)越長(zhǎng)以重用的,但是,由于目前只有一個(gè)項(xiàng)目(WAR),你不得不新建一個(gè)項(xiàng)目依賴(lài)這個(gè)WAR,這變得非常的惡心,因?yàn)樵贛aven中配置對(duì)WAR的依賴(lài)遠(yuǎn)不如依賴(lài)JAR那樣簡(jiǎn)單明了,而且你根本不需要org.myorg.app.web。有人修改了dao,提交到svn并且不小心導(dǎo)致build失敗了,你在編寫(xiě)service的代碼,發(fā)現(xiàn)編譯不過(guò),只能等那人把dao修復(fù)了,你才能繼續(xù)進(jìn)行,很多人都在修改,到后來(lái)你根本就不清楚哪個(gè)依賴(lài)是誰(shuí)需要的,漸漸的,很多不必要的依賴(lài)被引入。甚至出現(xiàn)了一個(gè)依賴(lài)有多個(gè)版本存在。
build整個(gè)項(xiàng)目的時(shí)間越來(lái)越長(zhǎng),盡管你只是一直在web層工作,但你不得不build整個(gè)項(xiàng)目。
某個(gè)模塊,比如util,你只想讓一些經(jīng)驗(yàn)豐富的人來(lái)維護(hù),可是,現(xiàn)在這種情況,每個(gè)開(kāi)發(fā)者都能修改,這導(dǎo)致關(guān)鍵模塊的代碼質(zhì)量不能達(dá)到你的要求。
我們會(huì)發(fā)現(xiàn),其實(shí)這里實(shí)際上沒(méi)有遵守一個(gè)設(shè)計(jì)模式原則:“高內(nèi)聚,低耦合”。雖然我們通過(guò)包名劃分了層次,并且你還會(huì)說(shuō),這些包的依賴(lài)都是單向的,沒(méi)有包的環(huán)依賴(lài)。這很好,但還不夠,因?yàn)榫蜆?gòu)建層次來(lái)說(shuō),所有東西都被耦合在一起了。因此我們需要使用Maven劃分模塊。
一個(gè)簡(jiǎn)單的Maven模塊結(jié)構(gòu)是這樣的:
1----?app-parent
2|--?pom.xml?(pom)
3|
4|--?app-util
5|????????|--?pom.xml?(jar)
6|
7|--?app-dao
8|????????|--?pom.xml?(jar)
9|
10|--?app-service
11|????????|--?pom.xml?(jar)
12|
13|--?app-web
14|--?pom.xml?(war)
上述簡(jiǎn)單示意圖中,有一個(gè)父項(xiàng)目(app-parent)聚合很多子項(xiàng)目(app-util, app-dao, app-service, app-web)。每個(gè)項(xiàng)目,不管是父子,都含有一個(gè)pom.xml文件。而且要注意的是,小括號(hào)中標(biāo)出了每個(gè)項(xiàng)目的打包類(lèi)型。父項(xiàng)目是pom,也只能是pom。子項(xiàng)目有jar,或者war。根據(jù)它包含的內(nèi)容具體考慮。
這些模塊的依賴(lài)關(guān)系如下:
1app-dao??????-->?app-util
2app-service??-->?app-dao
3app-web??????-->?app-service注意依賴(lài)的傳遞性(大部分情況是傳遞的,除非你配置了特殊的依賴(lài)scope),app-dao依賴(lài)于app-util,app-service依賴(lài)于app-dao,于是app-service也依賴(lài)于app-util。同理,app-web依賴(lài)于app-dao,app-util。
用項(xiàng)目層次的劃分替代包層次的劃分能給我們帶來(lái)如下好處:
方便重用,如果你有一個(gè)新的swing項(xiàng)目需要用到app-dao和app-service,添加對(duì)它們的依賴(lài)即可,你不再需要去依賴(lài)一個(gè)WAR。而有些模塊,如app-util,完全可以漸漸進(jìn)化成公司的一份基礎(chǔ)工具類(lèi)庫(kù),供所有項(xiàng)目使用。這是模塊化最重要的一個(gè)目的。
由于你現(xiàn)在劃分了模塊,每個(gè)模塊的配置都在各自的
pom.xml里,不用再到一個(gè)混亂的紛繁復(fù)雜的總的POM中尋找自己的配置。如果你只是在app-dao上工作,你不再需要build整個(gè)項(xiàng)目,只要在app-dao目錄運(yùn)行
mvn命令進(jìn)行build即可,這樣可以節(jié)省時(shí)間,尤其是當(dāng)項(xiàng)目越來(lái)越復(fù)雜,build越來(lái)越耗時(shí)后。某些模塊,如app-util被所有人依賴(lài),但你不想給所有人修改,現(xiàn)在你完全可以從這個(gè)項(xiàng)目結(jié)構(gòu)出來(lái),做成另外一個(gè)項(xiàng)目,svn只給特定的人訪問(wèn),但仍提供jar給別人使用。
多模塊的Maven項(xiàng)目結(jié)構(gòu)支持一些Maven的更有趣的特性(如
DepencencyManagement),這留作以后討論。接下來(lái)討論一下POM配置細(xì)節(jié),實(shí)際上非常簡(jiǎn)單,先看app-parent的
pom.xml:
1<project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/maven-v4_0_0.xsd">
3<modelVersion>4.0.0modelVersion>
4<groupId>org.myorg.myappgroupId>
5<artifactId>app-parentartifactId>
6<packaging>pompackaging>
7<version>1.0-SNAPSHOTversion>
8<modules>
9????<module>app-utilmodule>
10????<module>app-daomodule>
11????<module>app-servicemodule>
12????<module>app-webmodule>
13modules>
14project>Maven的坐標(biāo)GAV(groupId,?artifactId,?version)在這里進(jìn)行配置,這些都是必須的。特殊的地方在于,這里的packaging為pom。所有帶有子模塊的項(xiàng)目的packaging都為pom。packaging如果不進(jìn)行配置,它的默認(rèn)值是jar,代表Maven會(huì)將項(xiàng)目打成一個(gè)jar包。
該配置重要的地方在于modules,例子中包含的子模塊有app-util, app-dao, app-service, app-war。在Maven build app-parent的時(shí)候,它會(huì)根據(jù)子模塊的相互依賴(lài)關(guān)系整理一個(gè)build順序,然后依次build。
這就是一個(gè)父模塊大概需要的配置,接下來(lái)看一下子模塊符合配置繼承父模塊。
1<project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/maven-v4_0_0.xsd">
3<parent>
4<artifactId>app-parentartifactId>
5<groupId>org.myorg.myappgroupId>
6<version>1.0-SNAPSHOTversion>
7parent>
8<modelVersion>4.0.0modelVersion>
9<artifactId>app-utilartifactId>
10<dependencies>
11????<dependency>
12????????<groupId>commons-langgroupId>
13????????<artifactId>commons-langartifactId>
14????????<version>2.4version>
15???dependency>
16dependencies>
17project>
app-util模塊繼承了app-parent父模塊,因此這個(gè)POM的一開(kāi)始就聲明了對(duì)app-parent的引用,該引用是通過(guò)Maven坐標(biāo)GAV實(shí)現(xiàn)的。而關(guān)于項(xiàng)目app-util本身,它卻沒(méi)有聲明完整GAV,這里我們只看到了artifactId。這個(gè)POM并沒(méi)有錯(cuò),groupId和version默認(rèn)從父模塊繼承了。實(shí)際上子模塊從父模塊繼承一切東西,包括依賴(lài),插件配置等等。
此外app-util配置了一個(gè)對(duì)于commons-lang的簡(jiǎn)單依賴(lài),這是最簡(jiǎn)單的依賴(lài)配置形式。大部分情況,也是通過(guò)GAV引用的。
再看一下app-dao,它也是繼承于app-parent,同時(shí)依賴(lài)于app-util:
1<project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/maven-v4_0_0.xsd">
3<parent>
4<artifactId>app-parentartifactId>
5<groupId>org.myorg.myappgroupId>
6<version>1.0-SNAPSHOTversion>
7parent>
8<modelVersion>4.0.0modelVersion>
9<artifactId>app-daoartifactId>
10<dependencies>
11???????<dependency>
12?????????<groupId>org.myorg.myappgroupId>
13?????????<artifactId>app-utilartifactId>
14?????????<version>${project.version}version>
15????dependency>
16dependencies>
17project>
該配置和app-util的配置幾乎沒(méi)什么差別,不同的地方在于,依賴(lài)變化了,app-dao依賴(lài)于app-util。這里要注意的是version的值為${project.version},這個(gè)值是一個(gè)屬性引用,指向了POM的project/version的值,也就是這個(gè)POM對(duì)應(yīng)的version。由于app-dao的version繼承于app-parent,因此它的值就是1.0-SNAPSHOT。而app-util也繼承了這個(gè)值,因此在所有這些項(xiàng)目中,我們做到了保持版本一致。
這里還需要注意的是,app-dao依賴(lài)于app-util,而app-util又依賴(lài)于commons-lang,根據(jù)傳遞性,app-dao也擁有了對(duì)于commons-lang的依賴(lài)。
app-service我們跳過(guò)不談,它依賴(lài)于app-dao。我們最后看一下app-web:
1<project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/maven-v4_0_0.xsd">
3<parent>
4<artifactId>app-parentartifactId>
5<groupId>org.myorg.myappgroupId>
6<version>1.0-SNAPSHOTversion>
7parent>
8<modelVersion>4.0.0modelVersion>
9<artifactId>app-webartifactId>
10<packaging>warpackaging>
11<dependencies>
12????<dependency>
13????????<groupId>org.myorg.myappgroupId>
14????????<artifactId>app-serviceartifactId>
15????????<version>${project.version}version>
16????dependency>
17dependencies>
18project>
app-web依賴(lài)于app-service,因此配置了對(duì)其的依賴(lài)。
由于app-web是我們最終要部署的應(yīng)用,因此它的packaging是war。為此,你需要有一個(gè)目錄src/main/webapp。并在這個(gè)目錄下?lián)碛衱eb應(yīng)用需要的文件,如/WEB-INF/web.xml。沒(méi)有web.xml,Maven會(huì)報(bào)告build失敗,此外你可能還會(huì)有這樣一些子目錄:/js, /img, /css … 。
看看Maven是如何build整個(gè)項(xiàng)目的,我們?cè)?app-parent 根目錄中運(yùn)行?mvn clean install?,輸出的末尾會(huì)有大致這樣的內(nèi)容:
1...
2...
3[INFO]?[war:war]
4[INFO]?Packaging?webapp
5[INFO]?Assembling?webapp[app-web]?in?[/home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT]
6[INFO]?Processing?war?project
7[INFO]?Webapp?assembled?in[50?msecs]
8[INFO]?Building?war:?/home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war
9[INFO]?[install:install]
10[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
11[INFO]
12[INFO]
13[INFO]?------------------------------------------------------------------------
14[INFO]?Reactor?Summary:
15[INFO]?------------------------------------------------------------------------
16[INFO]?app-parent?............................................?SUCCESS?[1.191s]
17[INFO]?app-util?..............................................?SUCCESS?[1.274s]
18[INFO]?app-dao?...............................................?SUCCESS?[0.583s]
19[INFO]?app-service?...........................................?SUCCESS?[0.593s]
20[INFO]?app-web?...............................................?SUCCESS?[0.976s]
21[INFO]?------------------------------------------------------------------------
22[INFO]?------------------------------------------------------------------------
23[INFO]?BUILD?SUCCESSFUL
24[INFO]?------------------------------------------------------------------------
25[INFO]?Total?time:?4?seconds
26[INFO]?Finished?at:?Sat?Dec?27?08:20:18?PST?2008
27[INFO]?Final?Memory:?3M/17M
28[INFO]?------------------------------------------------------------------------
注意Reactor Summary,整個(gè)項(xiàng)目根據(jù)我們希望的順序進(jìn)行build。Maven根據(jù)我們的依賴(lài)配置,智能的安排了順序,app-util, app-dao, app-service, app-web。
最后,你可以在?app-web/target?目錄下找到文件?app-web-1.0-SNAPSHOT.war?,打開(kāi)這個(gè)war包,在?/WEB-INF/lib?目錄看到了 commons-lang-2.4.jar,以及對(duì)應(yīng)的app-util, app-dao, app-service 的jar包。Maven自動(dòng)幫你處理了打包的事情,并且根據(jù)你的依賴(lài)配置幫你引入了相應(yīng)的jar文件。
使用多模塊的Maven配置,可以幫助項(xiàng)目劃分模塊,鼓勵(lì)重用,防止POM變得過(guò)于龐大,方便某個(gè)模塊的構(gòu)建,而不用每次都構(gòu)建整個(gè)項(xiàng)目,并且使得針對(duì)某個(gè)模塊的特殊控制更為方便。本文同時(shí)給出了一個(gè)實(shí)際的配置樣例,展示了如何使用Maven配置多模塊項(xiàng)目。

喜歡,在看
