Maven 是一款構建和管理 Java 項目的工具。
分模塊設計與開發
介紹
所謂分模塊設計,顧名思義指的就是我們在設計一個 Java 項目的時候,將一個 Java 項目拆分成多個模塊進行開發。
1). 未分模塊設計的問題
如果項目不分模塊,也就意味著所有的業務代碼是不是都寫在這一個 Java 項目當中。隨著這個項目的業務擴張,項目當中的業務功能可能會越來越多。
假如我們開發的是一個大型的電商項目,里面可能就包括了商品模塊的功能、搜索模塊的功能、購物車模塊、訂單模塊、用戶中心等等。這些所有的業務代碼我們都在一個 Java 項目當中編寫。
此時大家可以試想一下,假如我們開發的是一個大型的電商網站,這個項目組至少幾十號甚至幾百號開發人員,這些開發人員全部操作這一個 Java 項目。此時大家就會發現我們項目管理和維護起來將會非常的困難。而且大家再來看,假如在我們的項目當中,我們自己定義了一些通用的工具類以及通用的組件,而公司還有其他的項目組,其他項目組也想使用我們所封裝的這些組件和工具類,其實是非常不方便的。因為 Java 項目當中包含了當前項目的所有業務代碼,所以就造成了這里面所封裝的一些組件會難以復用。
總結起來,主要兩點問題:不方便項目的維護和管理、項目中的通用組件難以復用。
2). 分模塊設計
分模塊設計我們在進行項目設計階段,就可以將一個大的項目拆分成若干個模塊,每一個模塊都是獨立的。
比如我們可以將商品的相關功能放在商品模塊當中,搜索的相關業務功能我都封裝在搜索模塊當中,還有像購物車模塊、訂單模塊。而為了組件的復用,我們也可以將項目當中的實體類、工具類以及我們定義的通用的組件都單獨的抽取到一個模塊當中。
如果當前這個模塊,比如訂單模塊需要用到這些實體類以及工具類或者這些通用組件,此時直接在訂單模塊當中引入工具類的坐標就可以了。這樣我們就將一個項目拆分成了若干個模塊兒,這就是分模塊兒設計。
分模塊兒設計之后,大家再來看。我們在進行項目管理的時候,我就可以幾個人一組,幾個人來負責訂單模塊兒,另外幾個人來負責購物車模塊兒,這樣更加便于項目的管理以及項目的后期維護。
而且分模塊設計之后,如果我們需要用到另外一個模塊的功能,我們直接依賴模塊就可以了。比如商品模塊、搜索模塊、購物車訂單模塊都需要依賴于通用組件當中封裝的一些工具類,我只需要引入通用組件的坐標就可以了。
分模塊設計就是將項目按照功能/結構拆分成若干個子模塊,方便項目的管理維護、拓展,也方便模塊鍵的相互調用、資源共享。
實踐
分析
好,我們明白了什么是分模塊設計以及分模塊設計的優勢之后,接下來我們就來看一下我們之前所開發的案例工程。
我們可以看到在這個項目當中,除了我們所開發的部門管理以及員工管理、登錄認證等相關業務功能以外,我們是不是也定義了一些實體類,也就是pojo包下存放的一些類,像分頁結果的封裝類PageBean、 統一響應結果Result,我們還定義了一些通用的工具類,像Jwts、阿里云OSS操作的工具類等等。
如果在當前公司的其他項目組當中,也想使用我們所封裝的這些公共的組件,該怎么辦?大家可以思考一下。
? ??●?方案一:直接依賴我們當前項目 tlias-web-management ,但是存在兩大缺點:
? ? ? ??○?這個項目當中包含所有的業務功能代碼,而想共享的資源,僅僅是pojo下的實體類,以及 utils 下的工具類。如果全部都依賴進來,項目在啟動時將會把所有的類都加載進來,會影響性能。
? ? ? ??○?如果直接把這個項目都依賴進來了,那也就意味著我們所有的業務代碼都對外公開了,這個是非常不安全的。
? ??●?方案二:分模塊設計
? ? ? ??○?將pojo包下的實體類,抽取到一個maven模塊中 tlias-pojo
? ? ? ??○?將utils包下的工具類,抽取到一個maven模塊中 tlias-utils
? ? ? ??○?其他的業務代碼,放在tlias-web-management這個模塊中,在該模塊中需要用到實體類pojo、工具類utils,直接引入對應的依賴即可。
注意:分模塊開發需要先針對模塊功能進行設計,再進行編碼。不會先將工程開發完畢,然后進行拆分。
PS:當前我們是為了演示分模塊開發,所以是基于我們前面開發的案例項目進行拆分的,實際中都是分模塊設計,然后再開發的。
實現
思路我們分析完畢,接下來,我們就根據我們分析的思路,按照如下模塊進行拆分:
1. 創建maven模塊 tlias-pojo,存放實體類
A. 創建一個正常的Maven模塊,模塊名tlias-pojo
B. 然后在tlias-pojo中創建一個包 com.itheima.pojo (和原來案例項目中的pojo包名一致)
C. 將原來案例項目 tlias-web-management 中的pojo包下的實體類,復制到tlias-pojo模塊中
D. 在 tlias-pojo 模塊的pom.xml文件中引入依賴
<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency>
</dependencies>
E. 刪除原有案例項目tlias-web-management的pojo包【直接刪除不要猶豫,我們已經將該模塊拆分出去了】,然后在pom.xml中引入 tlias-pojo的依賴
<dependency><groupId>com.itheima</groupId><artifactId>tlias-pojo</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
2. 創建Maven模塊 tlias-utils,存放相關工具類
A. 創建一個正常的Maven模塊,模塊名tlias-utils
B. 然后在 tlias-utils 中創建一個包 com.itheima.utils (和原來案例項目中的utils包名一致)
C. 將原來案例項目 tlias-web-management 中的utils包下的實體類,復制到tlias-utils模塊中
D. 在 tlias-utils 模塊的pom.xml文件中引入依賴
<dependencies><!--JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!--阿里云OSS--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency><!-- no more than 2.3.3--><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version></dependency><!--WEB開發--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.5</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency>
</dependencies>
E. 刪除原有案例項目tlias-web-management的utils包【直接刪除不要猶豫,我們已經將該模塊拆分出去了】,然后在pom.xml中引入 tlias-utils的依賴
<dependency><groupId>com.itheima</groupId><artifactId>tlias-utils</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
到此呢,就已經完成了模塊的拆分,拆分出了 tlias-pojo、tlias-utils、tlias-web-management ,如果其他項目中需要用到 pojo,或者 utils工具類,就可以直接引入依賴。
總結
1). 什么是分模塊設計:將項目按照功能拆分成若干個子模塊
2). 為什么要分模塊設計:方便項目的管理維護、擴展,也方便模塊間的相互調用,資源共享
3). 注意事項:分模塊設計需要先針對模塊功能進行設計,再進行編碼。不會先將工程開發完畢,然后進行拆分
集成與聚合
在案例項目分模塊開發之后啊,我們會看到tlias-pojo、tlias-utils、tlias-web-management中都引入了一個依賴 lombok 的依賴。我們在三個模塊中分別配置了一次。
如果是做一個大型的項目,這三個模塊當中重復的依賴可能會很多很多。如果每一個 Maven 模塊里面,我們都來單獨的配置一次,功能雖然能實現,但是配置是比較繁瑣的。
而接下來我們要講解的 Maven 的繼承用來解決這問題的。
繼承
我們可以再創建一個父工程 tlias-parent ,然后讓上述的三個模塊 tlias-pojo、tlias-utils、tlias-web-management 都來繼承這個父工程 。 然后再將各個模塊中都共有的依賴,都提取到父工程 tlias-parent中進行配置,只要子工程繼承了父工程,依賴它也會繼承下來,這樣就無需在各個子工程中進行配置了。
●?概念:繼承描述的是兩個工程間的關系,與java中的繼承相似,子工程可以繼承父工程中的配置信息,常見于依賴關系的繼承。
●?作用:簡化依賴配置、統一管理依賴
●?實現:
<parent><groupId>...</groupId><artifactId>...</artifactId><version>...</version><relativePath>....</relativePath>
</parent>
這是我們在這里先介紹一下什么是繼承以及繼承的作用,以及在 maven 當中如何來實現這層繼承關系。接下來我們就來創建這樣一個 parent 父工程,我們就可以將各個子工程當中共有的這部分依賴統一的定義在父工程 parent 當中,從而來簡化子工程的依賴配置。接下來我們來看一下具體的操作步驟。
我們在這里先介紹一下什么是繼承以及繼承的作用,以及在 maven 當中如何來實現這層繼承關系。接下來我們就來創建這樣一個 parent 父工程,我們就可以將各個子工程當中共有的這部分依賴,統一的定義在父工程 parent 當中,從而來簡化子工程的依賴配置。
繼承關系
思路分析
我們當前的項目 tlias-web-management,還稍微有一點特殊,因為是一個springboot項目,而所有的springboot項目都有一個統一的父工程,就是spring-boot-starter-parent。 與java語言類似,Maven不支持多繼承,一個maven項目只能繼承一個父工程,如果繼承了spring-boot-starter-parent,就沒法繼承我們自己定義的父工程 tlias-parent了。
那我們怎么來解決這個問題呢?
那此時,大家可以想一下,Java雖然不支持多繼承,但是可以支持多重繼承,比如:A 繼承 B, B 繼承C。 那在Maven中也是支持多重繼承的,所以呢,我們就可以讓 我們自己創建的三個模塊,都繼承tlias-parent,而tlias-parent 再繼承 spring-boot-starter-parent,就可以了。 具體結構如下:
實現
1). 創建maven模塊 tlias-parent ,該工程為父工程,設置打包方式pom(默認jar)。
工程結構如下:
父工程tlias-parent的pom.xml文件配置如下:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.5</version><relativePath/> <!-- lookup parent from repository -->
</parent><groupId>com.itheima</groupId>
<artifactId>tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
Maven打包方式:
? ??●?jar:普通模塊打包,springboot項目基本都是jar包(內嵌tomcat運行)
? ??●?war:普通web程序打包,需要部署在外部的tomcat服務器中運行
? ??●?pom:父工程或聚合工程,該模塊不寫代碼,僅進行依賴管理
2). 在子工程的pom.xml文件中,配置繼承關系。
<parent><groupId>com.itheima</groupId><artifactId>tlias-parent</artifactId><version>1.0-SNAPSHOT</version><relativePath>../tlias-parent/pom.xml</relativePath>
</parent><artifactId>tlias-utils</artifactId>
<version>1.0-SNAPSHOT</version>
這里是以 tlias-utils 為例,指定了其父工程。其他的模塊,都是相同的配置方式。
注意:
? ? ● 在子工程中,配置了繼承關系之后,坐標中的groupId是可以省略的,因為會自動繼承父工程的 。
? ? ● relativePath指定父工程的pom文件的相對位置(如果不指定,將從本地倉庫/遠程倉庫查找該工程)。
? ? ? ? ○ ../ 代表的上一級目錄
3). 在父工程中配置各個工程共有的依賴(子工程會自動繼承父工程的依賴)。
<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency>
</dependencies>
此時,我們已經將各個子工程中共有的依賴(lombok),都定義在了父工程中,子工程中的這一項依賴,就可以直接刪除了。刪除之后,我們會看到父工程中配置的依賴 lombok,子工程直接繼承下來了。
工程結構說明:
? ??●?我們當前的項目結構為:
因為我們是項目開發完畢之后,給大家基于現有項目拆分的各個模塊,tlias-web-management已經存在了,然后再創建各個模塊與父工程,所以父工程與模塊之間是平級的。
而實際項目中,可能還會見到下面的工程結構:
而在真實的企業開發中,都是先設計好模塊之后,再開始創建模塊,開發項目。 那此時呢,一般都會先創建父工程 tlias-parent,然后將創建的各個子模塊,都放在父工程parent下面。 這樣層級結構會更加清晰一些。
PS:上面兩種工程結構,都是可以正常使用的,沒有一點問題。 只不過,第二種結構,看起來,父子工程結構更加清晰、更加直觀。
版本鎖定
場景
如果項目中各個模塊中都公共的這部分依賴,我們可以直接定義在父工程中,從而簡化子工程的配置。 然而在項目開發中,還有一部分依賴,并不是各個模塊都共有的,可能只是其中的一小部分模塊中使用到了這個依賴。
比如:在tlias-web-management、tlias-web-system、tlias-web-report這三個子工程中,都使用到了jwt的依賴。 但是 tlias-pojo、tlias-utils中并不需要這個依賴,那此時,這個依賴,我們不會直接配置在父工程 tlias-parent中,而是哪個模塊需要,就在哪個模塊中配置。
而由于是一個項目中的多個模塊,那多個模塊中,我們要使用的同一個依賴的版本要一致,這樣便于項目依賴的統一管理。比如:這個jwt依賴,我們都使用的是 0.9.1 這個版本。
那假如說,我們項目要升級,要使用到jwt最新版本 0.9.2 中的一個新功能,那此時需要將依賴的版本升級到0.9.2,那此時該怎么做呢 ?
第一步:去找當前項目中所有的模塊的pom.xml配置文件,看哪些模塊用到了jwt的依賴。
第二步:找到這個依賴之后,將其版本version,更換為 0.9.2。
問題:如果項目拆分的模塊比較多,每一次更換版本,我們都得找到這個項目中的每一個模塊,一個一個的更改。 很容易就會出現,遺漏掉一個模塊,忘記更換版本的情況。
那我們又該如何來解決這個問題,如何來統一管理各個依賴的版本呢?
答案:Maven的版本鎖定功能。
介紹
在maven中,可以在父工程的pom文件中通過 <dependencyManagement>
來統一管理依賴版本。
父工程:
<!--統一管理依賴版本-->
<dependencyManagement><dependencies><!--JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency></dependencies>
</dependencyManagement>
子工程:
<dependencies><!--JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency>
</dependencies>
注意:
? ??●?在父工程中所配置的 <dependencyManagement>
只能統一管理依賴版本,并不會將這個依賴直接引入進來。 這點和 <dependencies>
是不同的。
? ??●?子工程要使用這個依賴,還是需要引入的,只是此時就無需指定 <version>
版本號了,父工程統一管理。變更依賴版本,只需在父工程中統一變更。
實現
接下來,我們就可以將tlias-utils模塊中單獨配置的依賴,將其版本統一交給 tlias-parent 進行統一管理。
具體步驟如下:
1). tlias-parent 中的配置
<!--統一管理依賴版本-->
<dependencyManagement><dependencies><!--JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!--阿里云OSS--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency><!-- no more than 2.3.3--><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version></dependency></dependencies>
</dependencyManagement>
2). tlias-utils中的pom.xml配置
如果依賴的版本已經在父工程進行了統一管理,所以在子工程中就無需再配置依賴的版本了。
<dependencies><!--JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency><!--阿里云OSS--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId></dependency><!-- no more than 2.3.3--><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId></dependency><!--WEB開發--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
我們之所以,在springboot項目中很多時候,引入依賴坐標,都不需要指定依賴的版本 <version>
,是因為在父工程 spring-boot-starter-parent中已經通過 <dependencyManagement>
對依賴的版本進行了統一的管理維護。
屬性配置
我們也可以通過自定義屬性及屬性引用的形式,在父工程中將依賴的版本號進行集中管理維護。 具體語法為:
1). 自定義屬性
<properties><lombok.version>1.18.24</lombok.version>
</properties>
2). 引用屬性
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version>
</dependency>
接下來,我們就可以在父工程中,將所有的版本號,都集中管理維護起來。
<properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><lombok.version>1.18.24</lombok.version><jjwt.version>0.9.1</jjwt.version><aliyun.oss.version>3.15.1</aliyun.oss.version><jaxb.version>2.3.1</jaxb.version><activation.version>1.1.1</activation.version><jaxb.runtime.version>2.3.3</jaxb.runtime.version>
</properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency>
</dependencies><!--統一管理依賴版本-->
<dependencyManagement><dependencies><!--JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jjwt.version}</version></dependency><!--阿里云OSS--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>${aliyun.oss.version}</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>${jaxb.version}</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>${activation.version}</version></dependency><!-- no more than 2.3.3--><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>${jaxb.runtime.version}</version></dependency></dependencies>
</dependencyManagement>
版本集中管理之后,我們要想修改依賴的版本,就只需要在父工程中自定義屬性的位置,修改對應的屬性值即可。
面試題:<dependencyManagement>
與 <dependencies>
的區別是什么?
? ??●?<dependencies>
是直接依賴,在父工程配置了依賴,子工程會直接繼承下來。
? ??●?<dependencyManagement>
是統一管理依賴版本,不會直接依賴,還需要在子工程中引入所需依賴(無需指定版本)
聚合
分模塊設計與開發之后啊,我們的項目被拆分為多個模塊,而模塊之間的關系,可能錯綜復雜。 那就比如我們當前的案例項目,結構如下(相對還是比較簡單的):
此時,tlias-web-management 模塊的父工程是 tlias-parent,該模塊又依賴了tlias-pojo、tlias-utils模塊。 那此時,我們要想將 tlias-web-management 模塊打包,是比較繁瑣的。因為在進行項目打包時,maven會從本地倉庫中來查找tlias-parent父工程,以及它所依賴的模塊tlias-pojo、tlias-utils,而本地倉庫目前是沒有這幾個依賴的。
所以,我們再打包tlias-web-management 模塊前,需要將 tlias-parent、tlias-pojo、tlias-utils分別執行install生命周期安裝到maven的本地倉庫,然后再針對于 tlias-web-management 模塊執行package進行打包操作。
那此時,大家試想一下,如果開發一個大型項目,拆分的模塊很多,模塊之間的依賴關系錯綜復雜,那此時要進行項目的打包、安裝操作,是非常繁瑣的。 而我們接下來,要講解的maven的聚合就是來解決這個問題的,通過maven的聚合就可以輕松實現項目的一鍵構建(清理、編譯、測試、打包、安裝等)。
介紹
●?聚合:將多個模塊組織成一個整體,同時進行項目的構建。
●?聚合工程:一個不具有業務功能的“空”工程(有且僅有一個pom文件) 【PS:一般來說,繼承關系中的父工程與聚合關系中的聚合工程是同一個】
●?作用:快速構建項目(無需根據依賴關系手動構建,直接在聚合工程上構建即可)
實現
在maven中,我們可以在聚合工程中通過 <moudules>
設置當前聚合工程所包含的子模塊的名稱。我們可以在 tlias-parent中,添加如下配置,來指定當前聚合工程,需要聚合的模塊:
<!--聚合其他模塊-->
<modules><module>../tlias-pojo</module><module>../tlias-utils</module><module>../tlias-web-management</module>
</modules>
那此時,我們要進行編譯、打包、安裝操作,就無需在每一個模塊上操作了。只需要在聚合工程上,統一進行操作就可以了。
測試:執行在聚合工程 tlias-parent 中執行 package 打包指令
那 tlias-parent 中所聚合的其他模塊全部都會執行 package 指令,這就是通過聚合實現項目的一鍵構建(一鍵清理clean、一鍵編譯compile、一鍵測試test、一鍵打包package、一鍵安裝install等)。
繼承與聚合對比
? ? ●?作用
? ? ? ??○?聚合用于快速構建項目
? ? ? ??○?繼承用于簡化依賴配置、統一管理依賴
? ??●?相同點:
? ? ? ??○ 聚合與繼承的pom.xml文件打包方式均為pom,通常將兩種關系制作到同一個pom文件中
? ? ? ??○ 聚合與繼承均屬于設計型模塊,并無實際的模塊內容
? ??● 不同點:
? ? ? ??○ 聚合是在聚合工程中配置關系,聚合可以感知到參與聚合的模塊有哪些
? ? ? ??○ 集成是在子模塊中配置關系,父模塊無法感知哪些子模塊繼承了自己