【精選】項目管理工具——Maven詳解
Maven簡介

Maven是一個項目管理工具。它可以幫助程序員構建工程,管理jar包,編譯代碼,完成測試,項目打包等等。
- Maven工具是基于POM(Project Object Model,項目對象模型)實現的。在Maven的管理下每個項目都相當于是一個對象。
- Maven標準化了項目的構建。即對項目結構,構建命令等進行了標準化定義。
- Maven提供了一個免費的中央倉庫,在其中幾乎可以找到任何的流行開源類庫。
- Maven是跨平臺的,在Windows、Linux、Mac上,都可以使用同樣的命令。
Maven的作用

一鍵構建
我們的項目往往都要經歷編譯、測試、運行、打包、安裝 ,部署等一系列過程,這些過程稱之為構建。通過Maven工具,可以使用簡單的命令輕松完成構建工作。
依賴管理
傳統的Web項目中,我們必須將工程所依賴的jar包復制到工程中,導致了工程的變得很大。如果一個公司具有相同架構的項目有十個,那么就需要將這一份jar包復制到十個不同的工程中,非常浪費資源。
同樣的項目內容,傳統Web項目大小如下:
而使用Maven構建的項目大小如下:
這說明了傳統Web項目中大部分磁盤空間是被jar包占據,而Maven項目中一定沒有jar包,那沒有jar包的項目是怎么運行的呢?
maven工程中不直接將jar包導入到工程中,而是有一個專門存放jar包的倉庫,倉庫中的每個jar包都有自己的坐標。maven工程中只要配置jar包坐標即可,運行項目需要使用jar包時,根據坐標即可從maven倉庫中拿到jar包即可運行。
Maven工程的類型和結構
Maven工程類型
-
POM工程
POM工程是邏輯工程,Maven并不會對該類型工程做打包處理,這些工程往往不包含具體的業務,而是用來整合其他工程的。
-
JAR工程
普通Java工程,在打包時會將項目打成jar包。
-
WAR工程
JAVA Web工程,在打包時會將項目打成war包。
Maven工程結構
接下來我們通過一個WAR工程學習Maven工程的結構
文件目錄結構:
- src:源代碼
- target:編譯生成的文件
- pom.xml:Maven工程配置文件,如坐標信息等。
項目結構:
- src/main/java:存放項目的java 文件
- src/main/resources:存放項目資源文件,如配置文件
- src/test/java:存放項目的測試文件
- src/test/resources:存放測試時的資源文件
一鍵構建
項目的生命周期
使用maven完成項目的構建的過程中,包括:驗證、編譯、測試、打包、部署等過程,maven將這些過程規范為項目構建的生命周期。

| 生命周期 | 所做工作 |
|---|---|
| 驗證 validate | 驗證項目是否正確 |
| 編譯 compile | 源代碼編譯 |
| 測試 Test | 使用適當的單元測試框架(例如junit)運行測試。 |
| 打包 package | 創建JAR/WAR包 |
| 檢查 verify | 對集成測試的結果進行檢查,以保證質量達標。 |
| 安裝 install | 安裝打包的項目到本地倉庫,以供其他項目使用。 |
| 部署 deploy | 拷貝最終的工程包到遠程倉庫中,以共享給其他開發人員和工程。 |
maven有三套相互獨立的生命周期。分為是構建生命周期,clean生命周期(清理構建后的文件)、site生命周期(生成項目報告)。作為開發人員我們一般重點學習構建生命周期即可。
Maven常用命令
在Maven構建項目的每一步都可以使用一句簡單的命令完成,接下來我們學習這些命令:
| 命令 | 作用 |
|---|---|
| mvn clean | 清除編譯的class文件,即刪除target目錄。 |
| mvn validate | 驗證項目是否正確 |
| mvn compile | 編譯maven項目 |
| mvn test | 編譯maven項目及運行測試文件 |
| mvn package | 編譯maven項目及運行測試文件,并打包 |
| mvn install | 編譯maven項目及運行測試文件并打包,并發布到本地倉庫 |
| mvn deploy | 部署項目到遠程倉庫 |
| mvn tomcat7:run | 使用tomcat運行項目 |
Maven依賴插件來執行命令,比如clean、validate等命令是maven自帶的,tomcat7命令是引入的第三方插件。
依賴管理
Maven倉庫類型
本地倉庫
本地倉庫指用戶計算機中的文件夾。用來存儲從遠程倉庫或中央倉庫下載的jar包,只有下載到本地倉庫的jar包才能使用,項目使用jar包時優先從本地倉庫查找。
遠程倉庫
遠程倉庫一般指私服,它是架設在局域網的倉庫服務,可以從中央倉庫下載資源,供局域網使用,從而減少每個程序員都從中央倉庫下載浪費的帶寬。
如果項目需要的jar包本地倉庫沒有,則會去遠程倉庫下載,下載到本地倉庫即可使用。
遠程倉庫不是必須配置的,如果本地倉庫沒有jar包,也沒有配置遠程倉庫,則會直接從中央倉庫下載。
中央倉庫
中央倉庫是互聯網上的服務器,是Maven提供的最大的倉庫,里面擁有最全的jar包資源。
如果項目需要的jar包,本地倉庫和遠程倉庫都沒有,則會去中央倉庫下載,下載到本地倉庫使用。
Maven中央倉庫訪問頁面:https://mvnrepository.com/
中央倉庫訪問速度較慢,我們一般都會配置鏡像代理中央倉庫的下載請求,如阿里鏡像、華為鏡像等。
Maven配置文件
本地倉庫的默認位置是${user.dir}/.m2/repository,${user.dir}表示 windows用戶目錄,我們可以通過修改${MAVEN_HOME}\conf\settings.xml,修改本地倉庫的位置。
配置本地倉庫
在<settings>中添加如下標簽:
<!-- 本地倉庫路徑 -->
<localRepository>F://repository</localRepository>
配置鏡像
由于中央倉庫訪問速度較慢,可以配置鏡像代理中央倉庫的下載請求。在<settings>下的<mirrors>中添加如下標簽即可配置鏡像:
<mirror>
<!-- 指定鏡像ID -->
<id>nexus-aliyun</id>
<!-- 匹配中央倉庫。-->
<mirrorOf>central</mirrorOf>
<!-- 指定鏡像名稱 -->
<name>Nexus aliyun</name>
<!-- 指定鏡像路徑 -->
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
配置JDK版本
創建maven項目的時候,默認使用的JDK是1.5版本,驗證語法、編譯、運行時都會按照JDK1.5操作,這樣就有很多語法無法使用。我們本機安裝的JDK是JDK11,可以配置maven按照JDK11創建項目。
在<settings>下的<profiles>中添加如下標簽即可配置JDK版本:
<profile>
<id>jdk11</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>11</jdk>
</activation>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
</properties>
</profile>
pom文件配置
pom文件最上方是項目基本信息:
groupId
groupId一般定義項目組名,命名規則使用反向域名。例如com.java
artifactId
artifactId一般定義項目名,命名使用小寫字母。項目發布后,它的坐標是groupId+artifactId。
version
version定義版本號。版本號一般有三段,第一段:革命性的產品升級。第二段:新功能版本。第三段:修正一些bug。
packaging
packaging定義打包方式。
<properties>中定義一些配置信息:
<dependencies>中定義依賴的jar包坐標:
由于項目是web項目,需要寫Servlet和JSP,所以需要引入Servlet和JSP的依賴。查找依賴坐標的網站:https://mvnrepository.com/
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- jsp -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
為什么之前的web項目中沒有引入jsp和servlet的jar包?
因為之前項目中使用的是tomcat中的jsp和servlet中的jar包,在項目中沒有引入。
<plugins>中定義第三方插件:
web項目依賴tomcat運行,所以添加tomcat7插件
<plugins>
<!-- tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat7</server>
</configuration>
</plugin>
</plugins>
編寫代碼
接下來編寫Servlet和Jsp代碼:
@WebServlet("/demo1")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("hello.jsp").forward(req,resp);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>測試</title>
</head>
<body>
<h1>你好,程序員!</h1>
</body>
</html>
運行項目,按照如圖步驟,配置使用maven中的tomcat7插件運行項目:
點擊OK后配置完成,點擊Idea的運行符號即可使用maven中的tomcat7插件運行項目。
依賴范圍
訪問servlet后,發現報500異常,這是為什么呢?
這是由于項目中引入了Servlet和Jsp的jar包,而在項目運行時,Tomcat要使用它自帶的Servlet和Jsp的jar包,這樣就造成了jar包沖突。但如果項目不引入Servlet和Jsp的jar包,則根本無法通過編譯。
此時可以設置依賴的作用范圍解決該問題,設置Servlet和Jsp的jar包只在編譯期起作用,運行時不起作用,這樣不僅可以通過編譯,還能在運行時避免jar包沖突。
通過在<dependency>中添加<scope>,可以設置依賴的作用范圍,有以下取值:
compile默認范圍。表示該依賴在編譯和運行時生效,項目打包時也會將該依賴打包進去。
provided使用此依賴范圍的Maven依賴,編譯和測試時有效,但在運行時無效。典型的例子是servlet-api,在運行時Web容器已經提供依賴,就不需要Maven重復地引入一遍。
runtimeruntime范圍表明編譯時不需要生效,而只在運行時生效。典型的例子是JDBC驅動包,編譯時只需要JDK的JDBC接口即可,只有運行項目時才需要具體的JDBC驅動。
testtest范圍表明使用此依賴范圍的依賴,只在編譯和運行測試代碼的時生效,程序的正常運行不需要此類依賴。典型的例子就是JUnit,它只有在編譯測試代碼及運行測試的時候才需要。
system如果有些你依賴的jar包沒有Maven坐標的,它完全不在Maven體系中,這時候你可以把它下載到本地硬盤,然后通過system來引用。(不推薦使用)
所以對于Servlet和Jsp依賴,我們添加依賴范圍為provided即可。
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
重啟項目,即可正常訪問Servlet和Jsp
Maven工程測試
測試概述
測試即在交付前對寫好的代碼進行評測,分為黑盒測試和白盒測試:
- **黑盒測試:**不需要寫代碼,給輸入值,看程序是否能夠輸出期望的值。
- **白盒測試:**需要寫代碼的。關注程序具體的執行流程。
單元測試是指對軟件中的最小可測試單元進行檢查和驗證,Java里單元測試指一個類的功能。單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。
Junit是Java編程語言的單元測試框架,用于編寫和運行可重復的自動化測試。從分類中屬于白盒測試。
Junit使用步驟
在Maven項目中引入依賴
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
定義被測試的類
我們定義一個計算器工具類。
public class Calculator {
// 加法
public int add(int a,int b){
return a+b;
}
// 除法
public int div(int a,int b){
return a/b;
}
}
對定義的類進行測試
-
創建src/test/java包,并將改包設置為測試包。
-
在src/test/java中創建測試類的包,包名一般與被測試包名一致。
-
定義測試類,類名一般為被測試類+Test
-
測試類中編寫測試方法。
public class CalculatorTest {
/**
* 測試方法是可以獨立運行的,寫法如下:
* 1.方法名一般為test+被測試方法名
* 2.方法上方添加@Test
* 3.測試方法沒有參數和返回值
*/
@Test
public void testAdd(){
Calculator calculator = new Calculator();
int add = calculator.add(1, 2);
System.out.println(add);
}
@Test
public void testDiv(){
Calculator calculator = new Calculator();
int div = calculator.div(2,0);
System.out.println(div);
}
}
Junit結果判定
點擊測試方法邊的三角運行測試方法,如果出現綠色對鉤,證明方法能正常運行;如果出現紅色感嘆號,證明方法拋出異常,需要修改方法。
當然,不是能正常運行就代表方法一定沒有問題,也有可能方法的結果和預期結果不一致,這時就需要使用斷言操作。
斷言操作:Assert.assertEquals(參數1,參數2);
參數1:預期結果,參數2:實際結果
@Test
public void testAdd(){
Calculator calculator = new Calculator();
int add = calculator.add(1, 2);
/**
* 斷言
* 參數1:預期結果,參數2:實際結果
*/
Assert.assertEquals(2,add);
}
如果真實結果和預期結果不一致,則會拋出以下異常:
@Before、@After注解
在測試類中,@Before修飾的方法會在測試方法之前自動執行,@After修飾的方法會在測試方法執行之后自動執行。之后的學習中,我們可以設置前置方法為獲取資源,后置方法為釋放資源。
@Before
public void before(){
System.out.println("開始測試");
}
@After
public void after(){
System.out.println("結束測試");
}
依賴沖突
依賴沖突產生的原因:依賴傳遞
假設你的項目依賴jar包A,jar包A又依賴jar包B。當添加jar包A時,Maven會把jar包B也自動加入到項目中。比如剛剛我們添加了junit依賴,junit又依賴hamcrest,所以Maven會將junit和hamcrest都加入項目中。
這時就可能會產生依賴沖突問題,比如依賴A會引入依賴C,依賴B也會引入依賴C。如果不進行調解則會引入兩個依賴C,那么Maven是如何解決依賴沖突問題的呢?
依賴沖突調解
我們以Spring依賴為例,spring-webmvc依賴spring-aop,spring-context也依賴spring-aop,如果兩個同時引入,會引入哪個版本的spring-aop呢?
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
最先聲明原則
最短路徑優先原則不能解決所有問題,比如這樣的依賴關系:A–>B–>C(1.0)和D–>E–>C(2.0),同時引入A和D之后,C(1.0)和C(2.0)的依賴路徑長度都為2。此時第一原則將不能解決問題
Maven調解依賴沖突的第二原則是最先聲明原則:
在依賴路徑長度相等的前提下,在POM中依賴聲明的順序靠前的會被解析使用。比如:以上案例中,spring-webmvc和spring-context到spring-core的路徑都為1。誰聲明在上方,spring-core會按照誰的版本引入。
排除依賴、鎖定版本
如果不想使用Maven默認的沖突調解方式,有兩種方式可以手動進行沖突調解。
排除依賴
比如以上案例中,想使用spring-webmvc的spring-aop包,那么可以讓spring-context引入時排除引入spring-aop包,這樣就可以使用spring-webmvc的spring-aop包了,寫法如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</exclusion>
</exclusions>
</dependency>
鎖定版本
在Maven中為某個jar包配置鎖定版本后,不考慮依賴的聲明順序和依賴路徑,以鎖定的版本的為準添加到工程中,此方法在企業開發中常用。以下可以直接配置spring-aop鎖定的版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement
Maven聚合開發
聚合關系
之前我們在Idea中開發時會將項目的所有包放在同一個工程當中。
- domain:定義實體類
- dao:操作數據庫
- service:具體的業務邏輯,需要調用dao的方法。
- controller:前后端交互,需要調用service的方法。
- webapp:存放前端資源
假如我們現在寫了兩個項目分別是電商賣家端和電商買家端,兩個項目都需要調用serive層查詢訂單的方法。原來的寫法如下:
重復編碼造成開發效率降低。
而使用maven后,我們可以把之前的項目按需拆分成一個個小項目,之后將小項目發布到倉庫中,小項目之間也可以互相引用,并且在我們的項目中引入需要的小項目即可。
Maven將一個大項目分成一個個小項目開發,最后打包時會將這些小的項目打成一個完整的war包獨立運行。
繼承關系
Maven中的繼承是針對于父工程和子工程。父工程定義的依賴和插件子工程可以直接使用。注意父工程類型一定為POM類型工程。
多繼承
在Maven中對于繼承采用的也是單繼承,也就是說一個子項目只能有一個父項目。但我們可以在<dependencyManagement>配置多繼承。寫法如下:
<dependencyManagement>
<dependencies>
<!--父項目a-->
<dependency>
<groupId>com.java</groupId>
<artifactId>parent_a</artifactId>
<version>1.0-SNAPSHOT</version*>
<type>pom</type>
<!-- 引入父項目,scope的值為import -->
<scope>import</scope>
</dependency>
<!--父項目b-->
<dependency>
<groupId>com.java</groupId>
<artifactId>parent_b</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
浙公網安備 33010602011771號