Maven:約定優於配置。
標準的重要性已不用過多強調,想象一下,如果不是所有程式設計師都基於HTTP協議開發Web應用,網際網路會亂成怎樣。各個版本的IE、Firefox等瀏覽器之間的差別已經讓很多開發者頭痛不已。而Java成功的重要原因之一就是他能遮蔽大部分作業系統的差異,XML流行的原因之一是所有語言都接受它。Maven當然還不能和這些即成功又成熟的技術相比,但Maven的使用者都應該清楚,Maven提倡“約定優於配置”,這是Maven最核心的核心理念之一。
那麼為什麼要使用約定而不是自己更靈活的配置呢?原因之一是,使用約定可以大量減少配置。先看一個簡單地Ant配置檔案,如下所示。
<project name="my-project" default="dist" basedir="."> <description> simple example build file </description> <!- 設定構建的全域性屬性 --> <property name="src" location="src/main/java" /> <property name="build" location="target/classes" /> <property name="dist" location="target"/> <target name="init"> <!- 建立時間戳 --> <tstamp /> <!- 建立編譯使用的構建目錄 --> <mkdir dir="${build}" /> </target> <target name="compile" depends="init" description="compile the source"> <!- 將java程式碼從目錄${src}編譯至${build} --> <javac srcdir="${src}" destdir="${build}" /> </target> <target name="dist" depends="compile" description="generate the distribution"> <!-建立分發目錄--> <mkdir dir="${dist}/lib" /> <!- 將${build}目錄的所有內容打包至MyProject-${DSTAMP}.jar file--> <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/> </target> <target name="clean" description="clean up"> <!- 刪除${build}和${dist}目錄樹 --> <delete dir="${build}" /> <delete dir="${dist}"/> </target> </project>
這段程式碼做的事情就是清除構建目錄、建立目錄、編譯程式碼、複製依賴至目標目錄,最後打包。這時一個專案構建要完成的最基本的事情,不過為此還是需要寫很多的XML配置:原始碼目錄是什麼、編譯目標目錄是什麼、分發目錄是什麼,等等。使用者還需要記住各種Ant任務命令,如delete、mkdir、javac和jar。
做同樣的事情,Maven需要什麼配置呢?Maven只需要一個最簡單的POM,見下面。
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId> <artifactId>my-project</artifactId> <version>1.0</version> </project>
這段配置簡單的令人驚奇,但為了獲得這樣簡潔的配置,使用者是需要付出一定的代價的,那就是遵循Maven的約定。Maven會假設使用者的專案是這樣的:
- 原始碼目錄為src/main/java
- 編譯輸出目錄為target/classes/
- 打包方式為jar
- 包輸出目錄為target/
遵循約定雖然損失了一定的靈活性,使用者不能隨意安排目錄結構,但是卻能減少配置。更重要的是,遵循約定能夠幫助使用者遵守構建標準。
如果沒有約定,10個專案可能使用10種不同的專案目錄結構,這意味著交流學習成本的增加,當新成員加入專案的時候,他就不得不花時間去學習這種構建配置。而有了Maven的約定,大家都知道什麼目錄放什麼內容。此外,與Ant的自定義目標名稱不同,Maven在命令列暴露的使用者介面是統一的,像mvn clean install 這樣的命令可以用來構建幾乎任何的Maven專案。 也許這時候會問,如果我不想遵守約定該怎麼辦?這時,請首先問自己三遍,你真的需要這麼做嗎?如果僅僅是因為喜好,就不要耍個性,個性往往意味著犧牲通用性,意味著增加無謂的複雜度,例如,Maven允許你自定義原始碼目錄,如下所示。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
<build>
<sourceDirectory>src/java</sourceDirectory>
</build>
</project>
該例中原始碼目錄就成了src/java而不是預設的src/main/java。但這往往會造成交流問題,習慣Maven的人會奇怪,原始碼去哪裡了?當這種自定義大量存在的時候,交流成本就會大大提高。只有在一些特殊的情況下,這種自定義配置的方式才應該被正確使用以解決實際問題。例如你在處理遺留程式碼,並且沒有辦法更改原來的目錄結構,這個時候就只能讓Maven妥協。
任何一個Maven專案都隱式的繼承自超級POM,這有點類似於任何一個Java類都隱式的繼承於Object類。因此,大量超級POM的配置都會被所有Maven專案繼承,這些配置也就成為了Maven所提倡的約定。
對於Maven 3,超級POM在檔案 $ MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路徑下。對於Maven2,超級POM在檔案$ MAVEN_HOME/lib/maven-x.x.x-uber.jar中的org/apache/maven/project/pom-4.0.0.xml目錄下。這裡的x.x.x表示Maven的具體版本。
Maven設定核心外掛的原因是防止由於外掛版本的變化而造成構建不穩定。
超級POM實際上很簡單,但從POM我們就能知曉Maven約定的由來,不僅理解了什麼是約定,為什麼要遵循約定,還能明白約定是如何實現的。