Maven 是跨平臺的項目管理工具,主要服務於基於Java平臺的項目構建、依賴管理和項目信息管理。Maven 的主要思想是約定優於配置。經過將約定項目的目錄結構,抽象項目的生命週期的方式,將程序員從繁瑣的項目構建中解放出來。java
注:本文用的是 maven-3.5.0 版本。程序員
平常工做除了編寫源代碼,天天有至關一部分時間花在了項目構建中,他們包括項目的編譯、運行單元測試、生成文檔、打包和部署等煩瑣且不起眼的工做。spring
項目自動化構建。Maven 提供一套規範以及一系列腳本,從清理、編譯、測試到生成報告,再到打包和部署實現自動化構建。還提供了插件擴展的方式,進一步簡化構建的過程。Maven 還能對項目進行編譯、測試、打包,而且將項目生成的構建部署到倉庫中。apache
Maven 是跨平臺的。對外提供了一致的操做接口,不管在 windows 平臺、Linux 平臺仍是 Mac 上都能用相同的命令進行操做。同時,Maven 項目目錄結構、測試用例命名方式等內容都有既定的規則,只要遵循了這些成熟的規則,用戶在項目間切換的時候就免去了額外的學習成本。windows
Maven 是依賴管理工具。Java 項目須要依賴許多的 jar 包,隨着依賴的增多,版本不一致、版本衝突、依賴臃腫等問題都會接踵而來。Maven 經過倉庫
統一存儲這些 jar 包,並經過 pom 文件
來管理這些依賴。api
Maven 是項目配置工具。Maven能幫助咱們管理本來分散在項目中各個角落的項目信息,包括項目描述、開發者列表、版本控制系統地址、許可證、缺陷管理系統地址等。緩存
以上是 Maven 的概念模型,前面說過 Maven 能管理衆多的 jar 包,而且梳理他們之間的依賴關係。Maven 經過 pom 文件
和倉庫
進行實現。tomcat
若是沒有 maven 咱們要使用一個 jar 包要從項目的官網尋找下載 jar 到本地,而後再將 jar 包導入到項目中。這樣存在幾個問題:服務器
...框架
最好的解決方式就是將這些 jar 包統一管理,每次只要去一個地方找就能夠了。
Maven 就幫咱們作了這樣一件事情,他提供一個免費的中央倉庫http://repo1.maven.org/maven2
,該中央倉庫包含了世界上大部分流行的開源項目。
咱們能夠從中央倉庫下載須要的 jar 包,從中央倉庫
下載的 jar 包會統一保存在 maven 的本地倉庫
中。本地倉庫
在本機的.m2
文件夾中。
本地倉庫更多相關信息能夠去搜索 maven 的安裝教程。
遠程倉庫除了中央倉庫還有私服和其餘公共倉庫。
私服是一種特殊的遠程倉庫,它是架設在局域網內的倉庫服務,私服代理廣域網上的遠程倉庫,供局域網內的Maven用戶使用。
上圖是搭建私服的示意圖。私服會將其餘公共倉庫的 jar 緩存到搭建的服務器上,局域網內的用戶還能夠將構建的項目直接放到私服上供其餘開發人員使用。
架設私服是 Maven 推薦的作法,私服減小中央服務器的壓力。若是沒有私服,每次請求都須要向中央服務器請求,有了私服以後經過私服的服務器向中央倉庫請求,局域網內的用戶只須要向私服請求便可。
好比 Maven 的中央倉庫部署在國外,國內訪問外網速度不夠,咱們能夠在國內架設 Maven 的公共倉庫。
若是倉庫 X 能夠提供倉庫 Y 存儲的全部內容,那麼就能夠認爲 X 是 Y 的一個鏡像,顯然在國內咱們須要一箇中央倉庫的鏡像。http://maven.net.cn/content/groups/public/
是中央倉庫,http://repo1.maven.org/maven2/
在中國的鏡像。
Maven 默認是從中央倉庫下載文件的,想要讓其從其餘地方下載文件就要進行配置,這裏就須要操做 maven 的 setting.xml
文件了。
在安裝好 maven 的基礎上,進入 maven 的安裝目錄,能夠看到以下的目錄結構:
setting.xml
文件就在conf
目錄中。這裏的setting.xml
是 maven 全局的配置文件,不建議修改。修改以後會影響 maven 的升級等操做。經常使用的作法是拷貝一份setting.xml
到 maven 本地倉庫的同一目錄下,而本地倉庫配置在用戶目錄的.m2
文件夾中,此時的setting.xml
就是用戶級別的配置文件。
強烈建議遵循以上規範,避免沒必要要的麻煩。
接下來就來看setting.xml
的一些配置了。
首先localRepository
定義本地倉庫位置,默認在用戶目錄下的.m2/repository
中。
Default: ${user.home}/.m2/repository <localRepository> /path/to/local/repo </localRepository>
前面講過有中央倉庫和其餘遠程倉庫,配置遠程倉庫就在repositories
中配置。
<repositories> <repository> <id>jboss</id> <name>JBoss Repository</name> <url>http://repository.jboss.com/maven2/</url> <releases> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> </releases> <snapshots> <enabled>false</enabled> <checksumPolicy>warn</checksumPolicy> </snapshots> <layout>default</layout> </repository> </repositories>
在repositories元素下,可使用repository子 元素聲明一個或者多個遠程倉庫。
<settings> …… <mirrors> <mirror> <id>maven.net.cn</id> <name>one of the central mirrors in China </name> <url> http://maven.net.cn/content/groups/public/ </url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> …… </settings>
<mirrors> <mirror> <id>maven.oschina.net</id> <name>maven mirror in China</name> <url>http://maven.oschina.net/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>
如下網站提供 Maven 倉庫搜索功能。
通常我就用最後一個搜索。
如今有了倉庫統一保管這些 jar 包,剩下的問題就是怎麼取了。
不知道你有沒有取快遞的經驗。咱們能夠這些 jar 包想象成是快遞,倉庫中保管着這些快遞。咱們去認領快遞須要依靠快遞單來肯定,一張快遞單上會有單號、咱們的姓名、手機號等信息。依靠這些信息就不會領錯快遞了。
這裏的快遞單就像 Maven 中的 pom 文件
,單子上的信息就像是 pom 文件中的座標系
。
Maven 項目規定的項目結構是這樣的:
每一個 maven 項目都有 pom.xml
文件。Maven座標爲各類構件引入了秩序,任何一個構件都必須明肯定義本身的座標。
一組 Maven座標是經過一些元素定義的,它們是 groupId、artifactId、version、packaging、 classifier:
經過座標系咱們來保證項目在 Maven 倉庫中的惟一性,每次取也不會取錯了。
咱們本身項目須要用別人的 jar 包,好比 spring。這就是咱們的項目依賴於 spring,所以咱們經過 pom 來配置這樣的依賴關係,這樣就能讓項目有清晰的結構。
依賴的關係用用<dependecy>
標籤來表示依賴:
上圖說明該項目依賴了 hibernate 等
如今來考慮一種狀況,咱們在項目開發的過程當中用到了 junit 進行測試,也就是說咱們的項目依賴於 junit。在項目構建的過程當中咱們會把 junit 也打包在項目中。可是在生產環境中徹底沒有必要用到 junit,咱們並不想將它發佈到生產環境中。
咱們能夠每次在發佈項目以前把他刪除了對麼?那若是依賴 servlet-api,咱們只有在編譯和測試項目的時候須要該依賴,但在運行項目的時候,因爲容器已經提供,也不須要 Maven 重複地引入一遍。
因此最好是在編譯、測試、運行的過程當中須要用到什麼 jar 包,就讓 Maven 去打包什麼。
maven 爲此提供了scope
標籤表示依賴範圍
,表示該 jar 包在何時須要被使用。
依賴範圍除了控制classpath,還會對依賴傳遞產生影響。若是A依賴B,B依賴C,則A對於B是第一直接依賴。B對於C是第二直接依賴。A對於C是傳遞性依賴。結論是:第一直接依賴的範圍和第二直接依賴的範圍決定了傳遞性依賴的範圍。
第一列是第一直接依賴,第一行是第二直接依賴,中間表示傳遞性依賴範圍。
此外 maven 還提供了option
和exclusions
來進一步管理依賴,分別稱爲可選依賴
和排除依賴
。
在依賴中添加 <optional> true/false <optional> 表示是否向下傳遞。
如上圖所示,B 依賴於 X,Y 而 A 依賴於 B,若是 B 不但願將依賴傳遞給 A 則能夠配置 B 中的 X,Y 依賴的optional
爲 true 來阻止依賴的傳遞。
再來看一種狀況,A 依賴於 B,且 B 將他的依賴 C 傳遞給了 A。可是 A 依賴了 C 的另外一個版本。這個時候 A 能夠主動排除 B 給的 C 依賴,轉而使用本身須要的版本,這就用到了exclusions
標籤。
用exclusions元素聲明排除依賴,exclusions能夠包 含一個或者多個exclusion子元素,所以能夠排除一個或者多個傳遞性依賴。
因此我用主動和被動的方式來區分他們。
接上面的問題,若是 A 和 B 依賴 C 的不一樣版本,並且既沒有配置可選依賴
也沒有配置排除依賴
。兩個版本都被解析顯然是不對的,由於那會形成依賴重複,所以必須選擇一個。
路徑最近者優先。若是直接與間接依賴中包含有同一個座標不一樣版本的資源依賴,以直接依賴的版本爲準。
第一聲明者優先。在依賴路徑長度相等的前 提下,在POM中依賴聲明的順序決定了誰會被解析使用,順序最靠前的那個依賴優勝。
上面例子中,A -> C(1.10) 和 A -> B -> C(?),C(1.10)的路徑短因此用它。
Maven的生命週期就是爲了對全部的構建過程進行抽象和統一。這個生命週期包含了項目的 清理、初始化、編譯、測試、打包、集成測試、 驗證、部署和站點生成等幾乎全部構建步驟。
初學者每每會覺得Maven的生命週期是一個總體,其實否則。Maven擁有三套相互獨立的生命週期,它們分別爲clean、default和site。clean生命週期的目的是清理項目,default生命週期的目的是構建項目,而site生命週期的目的是創建項目站點。
clean生命週期的目的是清理項目,它包含三個階段:
mvn clean 中的clean就是上面的clean,在一個生命週期中,運行某個階段的時候,它以前的全部階段都會被運行,也就是說,mvn clean 等同於 mvn pre-clean clean ,若是咱們運行 mvn post-clean ,那麼 pre-clean,clean 都會被運行。這是Maven很重要的一個規則,能夠大大簡化命令行的輸入。
default生命週期定義了真正構建時所須要執 行的全部步驟,它是全部生命週期中最核心的部分,其包含的階段以下:
運行任何一個階段的時候,它前面的全部階段都會被運行,這也就是爲何咱們運行mvn install 的時候,代碼會被編譯,測試,打包。此外,Maven的插件機制是徹底依賴Maven的生命週期的,所以理解生命週期相當重要。
site生命週期的目的是創建和發佈項目站點,Maven可以基於POM所包含的信息,自動生成一個友好的站點,方便團隊交流和發佈項目信息。
site 生成項目的站點文檔
這裏常常用到的是site階段和site-deploy階段,用以生成和發佈Maven站點,這但是Maven至關強大的功能,Manager比較喜歡,文檔及統計數據自動生成,很好看。
mvn clean:該命令調用clean生命週期的clean階段。實際執行的階段爲clean生命週期的 pre-clean和clean階段。
mvn test:該命令調用default生命週期的test階段。實際執行的階段爲default生命週期的 validate、initialize等,直到test的全部階段。這也解釋了爲何在執行測試的時候,項目的代碼可以自動得以編譯。
mvn clean install:該命令調用clean生命週期 的clean階段和default生命週期的install階段。
mvn clean deploy site-deploy:該命令調用 clean生命週期的clean階段、default生命週期的 deploy階段,以及site生命週期的site-deploy階段。
軟件設計人員每每會採用各類方式對軟件劃分模塊,以獲得更清晰的設計及更高的重用性。當把Maven應用到實際項目中的時候,也須要將項目分紅不一樣的模塊。
簡單的說就是有 A,B 兩個模塊,如今想要將他們統一管理。Maven 的聚合特性可以把項目的各個模塊聚合在一塊兒構建,而Maven的繼承特性則能幫助抽取各模塊相同的依賴和插件等配置,在簡化POM的同時,還能促進各個模塊配置的一致性。
兩個子模塊但願同時構建。這時,一個簡單的需求就會天然而然地顯現出來:咱們會想要一次構建兩個項目,而不是到兩個模塊的目錄下分別執行mvn命令。Maven聚合(或者稱爲多模塊)這一特性就是爲該需求服務的。
上圖所示api
是一個模塊,cmd
是一個模塊他們都有各自的 pom 文件,其實每個包都是一個子模塊,而最底下的 pom 文件則是統一管理這些子模塊。
他們的配置很簡單,咱們最好遵循規範。
api 的 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"> <parent> <groupId>com.shuiyujie.fu</groupId> <artifactId>sop</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>api</artifactId> ...
cmd 的 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"> <parent> <groupId>com.shuiyujie.fu</groupId> <artifactId>sop</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cmd</artifactId>
聚合 pom.xml
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.shuiyujie.fu</groupId> <artifactId>pom</artifactId> <packaging>sop</packaging> <version>1.0-SNAPSHOT</version> <name>sop</name> <url>http://maven.apache.org</url> ... <modules> <module>base</module> <module>core</module> <module>server</module> <module>persist</module> <module>api</module> <module>impl</module> <module>cmd</module> </modules> ...
觀察上面的三個代碼清單能夠聚合 pom 文件中定義了 <modules>
標籤,標籤中包含的就是各個子模塊,而且用子模塊的artifactId
來標記他們。
注意:聚合 pom 文件的打包方式,即 packaging
必須爲 pom。
這樣只須要構建聚合 pom 文件便可同時構建在其管理下的多個子模塊。
消除重複。在面向對象世界中,程序員可使用類繼承在必定程度上消除重複,在Maven的世界 中,也有相似的機制能讓咱們抽取出重複的配 置,這就是POM的繼承。
任然看上面的三個 pom.xml 代碼清單,子模塊都有一個parent
標籤,這就代表他們繼承了一個 pom 文件,而parent
標籤下的其餘標籤就是一個座標系
,經過一個座標系就能定位一個惟一的項目。
好比上面的子模塊繼承自聚合 pom 文件
,因此此時聚合 pom 文件
也是父類 pom 文件
。
在繼承的過程當中咱們考慮一種情形,咱們但願在父類中統一控制 spring 的版本,而後子類繼承自父類就可使用統一版本的 spring 依賴了。可是有些子模塊不須要依賴 spring,並不須要從父類繼承 spring 的依賴。
咱們可使用dependencyManagement
標籤。
父類 pom.xml
<dependencyManagement> <dependencies> <!-- 模塊間依賴 start --> <dependency> <groupId>${project.groupId}</groupId> <artifactId>core</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>persist</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>impl</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>server</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>cmd</artifactId> <version>${project.version}</version> </dependency> <!-- 模塊間依賴 end --> </dependencies> </dependencyManagement>
父類dependencyManagement
中聲明瞭各個子模塊,子模塊之間有的會須要相互引用,有的卻並不須要。因此在父類中統一配置各個子模塊的groupId
,artifactId
,version
等基本信息。
在dependencyManagement
中聲明的依賴不會在當前pom中引入依賴,也不會再繼承他的pom中引入依賴,他的做用只是聲明瞭可能要引入依賴的一些通用信息。
若是要使用一個子模塊要使用其餘子模塊就能夠另外聲明,可是不須要指定版本等通用信息,這樣就能夠減小依賴衝突的發生,代碼以下:
<?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"> <parent> <groupId>com.shuiyujie.fu</groupId> <artifactId>sop</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>api</artifactId> <dependencies> <dependency> <groupId>${project.parent.groupId}</groupId> <artifactId>fu-persist</artifactId> </dependency> </dependencies> </project>
Maven 的核心思想是約定優於配置。
首先,Maven 約定了項目的結構,咱們不須要配置 Maven 編譯、打包等操做時文件的位置。統一的項目結構下降了學習的成本,讓我能將精力集中到了項目自己。
其次,Maven 抽象了項目構建的過程,將其分紅一個個生命週期進行管理。經過命令和插件的形式進一步簡化操做,又讓咱們從繁瑣的操做解放出來。
本文大部份內容來自於《Maven 實戰》一書,想要了解一手信息強烈建議閱讀。網上的其餘文章基本上都是摘抄《Maven 實戰》的部份內容。
因此還想說一遍:發現一本好書就像發現了一座寶藏。