Maven——基礎篇

卡斯特梅的雨傘發表於2021-07-06

Maven——基礎篇

Maven出現前的問題

  • 一個專案就是一個工程,而工程內一般是通過package包來分模組,比較使用者模組,訂單模組等,如果專案過於龐大,通過包模組來劃分就不太合適,而應該拆分為模組,這時候就要藉助於maven來分模組,將一個專案拆分為多個工程。
  • 專案依賴的jar包需要手動去複製甚至自己去尋找jar包,然後新增到專案工程的WEB_INF/lib目錄下,這樣才能使用到jar包上的功能,這種情況下jar包的尋找及其浪費時間且沒有統一的下載渠道,非常地耗時耗力且不安全,而且專案中新增的jar包數量多會非常臃腫且不能複用。通過maven統一從中央倉庫去拉去想要依賴的jar包非常簡便且安全,而且拉取下來的jar包儲存在本地倉庫中,可以多個專案複用,我們需要依賴的jar包只要在maven中配置依賴即可。
  • jar包的層級依賴問題在maven使用前需要自己解決,自己去依照經驗或者是手冊新增必要的依賴,比如spring-web需要依賴spring-core等jar包,需要自己去手動維護新增,增加學習成本。而使用maven依賴則只要我們去依賴了spring-web,則maven自動會幫我們解決層級依賴問題,自動匯入需要依賴的jar包。

Maven是什麼

Maven是一款服務於Java平臺的自動化構建工具。

自動化構建工具發展過程:Make——> Ant——>Maven——>Gradle

構建

構建即將專案Java原始檔、配置、圖片等資源生成一個可執行專案的過程。對於一個web工程來說,就要執行編譯,即把Java檔案編譯成class檔案,再構建成可部署的專案檔案war或者jar。maven對於Java Web工程到可部署war包的作用就像廚師對活的雞處理成可食用的熟雞一個道理。

Web工程編譯構建後變成可執行部署的專案工程的目錄結構圖如下,當我們在程式碼中使用路徑特別是相對路徑時,要用的就是編譯後檔案所在的路徑,因此如果可以最後拿絕對路徑,避免因為專案編譯後的工程目錄路徑變化導致相對路徑失效。

編譯.png

編譯2.png

源專案和編碼後的可執行部署專案對比圖

工程編譯.png

注意:開發過程中所有的路徑或配置檔案中的路徑等都是以編譯結果後的目錄結構為標準的。專案工程只是開發環境,真正執行的是編譯構建後的可部署檔案如war、jar。

構建過程中的各個環節

  1. 清理——將以前編譯的舊的class位元組碼刪除,為下一次編譯做準備
  2. 編譯——將java檔案編譯成class位元組碼檔案
  3. 測試——自動測試,自動呼叫Junit程式測試,目的是通過開發寫的Junit測試程式碼確保核心環節無問題測試通過。
  4. 報告——測試程式執行的結果
  5. 打包——Web工程打War包,Java工程打Jar包
  6. 安裝——Maven的特定概念,將打包後的檔案複製到倉庫中的指定位置。比如我們把user-api安裝到倉庫中,提供給其他模組依賴。
  7. 部署——將Web工程生成的War包複製到Servlet容器(Tomcat)的指定目錄下,使其可執行
#輸出環境變數值
C:\Users\castamere>echo %JAVA_HOME%
D:\techsoft\java8\jdk
#檢視maven環境變數是否配置成功及maven版本
C:\Users\castamere>mvn -v
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: D:\techsoft\maven\apache-maven-3.6.3\bin\..
Java version: 1.8.0_251, vendor: Oracle Corporation, runtime: D:\techsoft\java8\jdk\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

Maven的核心概念

  • 約定的目錄結構
  • POM——Maven工程的核心配置檔案
  • 座標——指定專案的唯一位置
  • 依賴——通過依賴引用第三方jar包等
  • 倉庫——依賴的jar包檔案所在的位置,有本地和遠端倉庫兩種
  • 生命週期/外掛/目標
  • 繼承
  • 聚合

maven目錄結構.png

maven目錄結構說明.png

Maven常用命令

  1. mvn clean——清理
  2. mvn compile——編譯主程式
  3. mvn test-compile——編譯測試程式
  4. mvn test——執行測試
  5. mvn package——打包,生成war包或者jar包
  6. mvn install——安裝
  7. mvn site——生成站點
  8. mvn deploy——將生成的jar包複製到遠端倉庫中

注意:執行與專案構建過程相關的Maven命令,必須要進入pom.xml所在的目錄,即專案名目錄下。

注意:maven執行命令都是從頭開始執行的,也就是說第一個執行的操作是mvn clean操作。

deploy.png

POM——Project Object Mode——專案物件模型

類似於DOM——文件物件模型,也就是說,在程式設計中,我們把文件或者物件抽象成一個模型來進行表示研究,就像我們把地球抽象成地球儀一樣進行研究一樣。

pom.xml是Maven工程的核心配置檔案,與構建過程相關的一切設定都在這個檔案中進行配置,其重要程度相當於web.xml對於動態Web工程。

座標——GAV

相當於三維空間中的xyz軸上唯一定位的座標點。

Maven的座標使用groupId artifactId version 來唯一定位一個Maven工程。

  • groupId:公司或組織域名倒序+專案名。(加專案名是因為一般一個公司不止一個專案,就像滴滴除了有叫車專案,還有滴滴金融等)
  • artifactId:模組名,一個專案有多個模組。
  • version:版本號,每個模組都會進行版本迭代。

如何通過座標到Maven參考中尋找依賴的jar包檔案的位置

GAV示例:

<dependency>
    <groupId>com.didi.financial</groupId>
    <artifactId>order</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

其對應的Maven倉庫中的位置就是:

com/didi/financial/order/1.0.0.RELEASE/order-1.0.0.RELEASE.jar

groupId中的點替換成資料夾斜槓,並以此把模組和版本都作為資料夾,最後的jar包檔名是模組名-版本號.jar的形式

倉庫

倉庫的分類

  • 本地倉庫:當前電腦或伺服器上的倉庫,為本地伺服器或電腦服務。我們的依賴會將依賴的jar從遠端倉庫中拉去到本地倉庫中來,這樣下一次獲取依賴的jar包時就只需要讀取本地參考的jar包即可。
  • 遠端倉庫:
    • 私服(一般用nexus搭建):搭建在區域網環境中,為區域網內的Maven工程提供服務,一般公司都會搭建自己的私服,這樣才能把公司各個專案模組間相互依賴的jar包釋出到私服上供其他依賴的專案模組拉去jar包依賴;另外一個作用作為訪問外網的代理伺服器,替我們上外網拉去想要的Maven依賴。
    • 中央倉庫:架設在Intenet上,為全世界所有Maven工程服務,當我們從本地及私服中找不到依賴的jar包時,會去中央倉庫搜尋。
    • 中央倉庫映象:為中央倉庫分擔流量,提升使用者訪問速度,比如阿里雲的maven映象。

倉庫.png

倉庫中儲存的內容

  • Maven自身所需要的外掛
  • 第三方框架或者工具的jar包
  • 我們自己開發的Maven工程
 <!--快照版寫法,只要加入SNAPSHOT就是快照版,對於快照版只要我們一發布,依賴快照版的其他專案便會在下次重啟時會優先去遠端倉庫中檢視是否有最新的,有則拉取新的快照版立刻更新到本地倉庫中-->
<version>1.0-SNAPSHOT</version>
<!--穩定發行版寫法,當我們專案模組測試通過後會打正式版,相對於快照版就是其是穩定的,釋出到了遠端倉庫,有一個專案依賴了這個庫,它第一次構建的時候會把該庫從遠端倉庫中下載到本地倉庫快取,以後再次構建都不會去訪問遠端倉庫了-->
<version>2.2.2.RELEASE</version>
<version>2.0.1-RELEASE</version>

注意: 對於快照版本,-SNAPSHOT必須大寫,如果小寫snapshot,maven會認為其是releases版本。 定義一個元件/模組為快照版本,只需要在pom檔案中在該模組的版本號後加上-SNAPSHOT即可(注意這裡必須是大寫)。release版本不允許修改,每次進行release版本修改,釋出必須提升版本號。而snapshot一般是開發過程中的迭代版本,snapshot更新後,引用的專案可以不修改版本號自動下載構建。

maven正式版本和快照版本的區別

依賴

依賴的範圍

  • maven解析依賴資訊時會到本地倉庫去尋找被依賴的jar包,對於公司專案中需要依賴的模組,比如order專案的 order-starter,這時候我們要對order專案的 order-starter執行mvn install命令將其安裝到倉庫中,這樣其他模組才能依賴到該jar包。
  • 依賴的範圍主要有:
    • compile——例子:spring-core.jar
    • test——例子:junit.jar
    • provided——不參與打包部署——例子:servlet-api.jar

注意:對於compile範圍的依賴對專案模組中的test程式是可以看得到的。而主程式main是看不到測試程式的編譯結果的。因為我們測試程式在開發階段需要執行測試主程式的程式碼,需要能看到,而我們的主程式是不需要依賴測試程式test的程式碼的,因此設定在打包安裝階段會排除掉test範圍的依賴,也因此看不到測試程式的程式碼。

依賴範圍.png

依賴範圍2.png

依賴的傳遞性

通過依賴的傳遞性這樣我們就不需要在每個模組工程中重複宣告依賴,只需要在需要依賴的第一個工程或者說最下面的工程中宣告依賴即可,這樣其他依賴這個工程的模組就自動間接依賴了這個jar包,有點類似於Java的繼承樹概念。

注意:非compile範圍的依賴不能傳遞,因此工程模組中如果有需要就要重新重複宣告依賴。

依賴的傳遞性.png

依賴的排除

因為依賴會有傳遞性,所有當我們並不需要傳遞過來的間接依賴時可以通過execlusions標籤進行依賴的排除,特別是當間接依賴特別多需要對工程進行“瘦身”時,就要用到。

示例:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.2.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
        </exclusion>
    </exclusions>
</dependency>

依賴的原則

  • 路徑最短者優先原則(依賴傳遞的路徑)
  • 先宣告者優先原則(dependency標籤中依賴宣告從上到下的先後順序)

依賴原則.png

統一管理所依賴jar包的版本——定義統一的版本號properties

之所以要定義統一的版本號是因為當我們依賴的jar包比如spring有好多個,這多個jar包一般版本號使用都是同樣的,這時候使用properties自定義標籤統一配置即可,再使用${}獲取配置值。

注意:properties標籤中宣告的自定義標籤凡是需要統一宣告再引用的場合都適用。

<properties>
    <springboot.version>2.2.2.RELEASE</springboot.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${springboot.version}</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-json</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

生命週期——構建環節執行的順序

  • 生命週期各個構建環節執行的順序是定義好的,不能被打亂,當我們執行一個命令比如mvn package時,在package前的compile、test等階段也都會依次執行,即無論執行什麼命令都會從這個生命週期的最初的位置開始執行。
  • Maven的核心程式定義了抽象的生命週期,而生命週期中各個階段的具體任務是由外掛來完成的。
  • 外掛和目標(即我們執行呼叫外掛功能的命令)
    • 生命週期的各個階段僅僅是定義了要執行的任務是什麼,就像我們定義的介面一樣,具體的執行由外掛來實現。
    • 各個階段和外掛的目標是對應的。
    • 相似的目標由特定相同的外掛來完成。比如compile和test-compile的執行外掛都是maven-compiler-plugin完成。

目標和外掛.png

Maven的三套生命週期

生命週期.png

生命週期1.png
生命週期2.png
生命週期3.png

繼承

目的是統一管理專案各個模組中對相同宣告的依賴的版本。

對於相同的依賴統一提取到父工程中,在子工程中宣告依賴引用時不指定版本號,以父工程的統一設定為準,方便管理修改。

步驟

  1. 建立一個打包方式為pom的父工程
  2. 子工程中宣告對父工程的引用
  3. 子工程刪除GAV中的GV即組織id和版本號
  4. 父工程中新增dependencyManagement統一依賴標籤
  5. 子工程刪除統一依賴jar包的版本號,交由父工程管理

注意:配置模組的父工程後,要對該模組執行安裝install命令時,如果沒有配置聚合,則要在執行安裝前先對父模組工程先執行install安裝命令才能install安裝成功。

示例

父工程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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hello.maven</groupId>
    <artifactId>hello-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
<!--    聚合各個工程模組-->
    <modules>
        <module>hello-spring</module>
        <module>hello-mybatis</module>
    </modules>
    <packaging>pom</packaging>
<!--配置依賴管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.2.2.RELEASE</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

子工程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>
        <artifactId>hello-parent</artifactId>
        <groupId>com.hello.maven</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hello-spring</artifactId>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

</project>
<?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>
        <artifactId>hello-parent</artifactId>
        <groupId>com.hello.maven</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
<!--    只需要宣告模組名,其他組織名和版本號交由父工程模組管理-->
    <artifactId>hello-mybatis</artifactId>

    <dependencies>
<!--        不需要宣告依賴的版本號,交由父工程統一管理-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

繼承1.png

聚合

目的是為了一鍵安裝各個模組工程,統一構建。

配置方式是在一個總的聚合工程中,一般就是父工程中去配置各個參與聚合的模組。

<!--    聚合各個工程模組-->
    <modules>
        <module>hello-spring</module>
        <module>hello-mybatis</module>
    </modules>

在聚合的工程目錄下執行install命令:

F:\waynecode\hello-parent>mvn install

安裝順序如下:

注意:maven會自己識別依賴關係進行安裝,並不會因為我們宣告的模組先後順序不同而導致安裝失敗。

安裝順序.png

Web工程自動部署

maven自動化構建配合Jenkins使用,用於伺服器部署。

待學習Jenkins持續整合時深入學習

方式1:

 <build>
        <resources>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.yml</include>
                    <include>*.properties</include>
                    <include>mappers/**</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.2</version>
                <executions>
                    <execution>
                        <id>exec-npm-install</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>npm</executable>
                            <arguments>
                                <argument>install</argument>
                                <argument>--registry=http://verdaccio.zc.com</argument>
                                <argument>--unsafe-perm</argument>
                            </arguments>
                            <workingDirectory>../zc-fe</workingDirectory>
                        </configuration>
                    </execution>
                    <execution>
                        <id>exec-npm-run-build</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>npm</executable>
                            <arguments>
                                <argument>run</argument>
                                <argument>build</argument>
                                <argument>${env}</argument>
                            </arguments>
                            <workingDirectory>../zc-fe</workingDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.outputDirectory}/static</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>../zc-fe/dist</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${main-class}</mainClass>
                    <outputDirectory>${project.parent.build.directory}/zc/</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>

方式2:

<build>
        <finalName>mall</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${basedir}/target/classes/static</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>../mall-admin/dist</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                    <execution>
                        <id>copy-resources-vue</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${basedir}/target/classes/static/vue</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>../mall-vue/dist</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

擴充套件

  1. JSP的本質是Servlet,是後端的技術。
  2. 統一的規範對IT開發領域非常重要,引申出來就是約定大於配置,配置大於編碼
  3. 把邏輯判斷等理性規範的事情都交給機器來做,就像以前的電話接線員被取消一樣,不需要這樣一個操作。
  4. 機器就是用來做特別枯燥無聊的重複性工作,所有如果有可機械化程式化的工作都會被機器取代。
  5. 解壓檔案注意放到非中文無空格的路徑下,避免很多錯誤的產生。
  6. 配置環境變數的規律——一般%MAVEN_HOME%配置的是bin目錄的上一級檔案路徑地址,而path配置的一般是帶bin的,比如%MAVEN_HOME%bin。
  7. 約定大於配置大於編碼。想通過配置或者約定解決問題就要對專案的框架以及架構有比較深的理解。
  8. 對於maven工程來說,只要有pom.xml就看做是一個maven工程

疑問

如何向maven中央倉庫釋出自己的jar包呢?

參考

簡單說和我們釋出到自己公司的倉庫是差不多的,就是擁有一個賬號密碼,配置到settings.xml中,然後向sonatype提交一個issue,通過後變可以構建釋出自己的jar包了。

如何用nexus搭建Maven私服?

使用Nexus搭建Maven私服

Maven私服Nexus的搭建

後續單獨寫一篇實踐文章。

Java專案的第一方、第二方、第三方的意思?

第一方是指JDK,第二方是指我們專案工程自己本身,第三方是指我們依賴藉助的其他專案或框架。

Maven設定JDK版本

maven設定jdk版本

方法1:找到maven工程的settings.xml檔案,找到 <profiles>標籤,在標籤內配置jdk版本即可。

<profile>
    <id>jdk-1.8</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>1.8</jdk>
    </activation>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>
</profile>

jdk版本.png

方法2:在maven專案的pom.xml中新增 ,在build->plugins標籤下新增:


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>utf-8</encoding>
    </configuration>
</plugin>

jsp-api依賴的scope作用範圍導致的空指標異常

這個是因為tomcat已經有提供了這個jar包,所有當我們在專案中依賴使用時,就要對該依賴配置作用範圍為provided,這樣才不會參與打包部署,也就不會產生衝突。

provided1.png
provided2.png

參考

Maven零基礎入門教程