之前在 從使用傳統Web框架到切換到Spring Boot后的總結 中提到關於 Spring Boot 編譯打包,Spring Boot 應用程序不用額外部署到外部容器中,可以直接通過 Maven 命令將項目編譯成可執行的 jar 包,然后通過 java -jar 命令啟動即可,非常方便。
最近有小伙伴私信我說,打 jar 包方便是方便,就是每次打包出來的 jar 太大了,先不說上傳時間的問題,如果只修改了 1 個類就需要重新打包項目,然后重新上傳項目到服務器,怎么覺得還不如我之前使用 war 包方便呢,使用 war 包時,雖然要部署到 Tomcat 中,但只需要將修改的 class 替換一下,重啟一下 Tomcat 就可以了。。。
其實到底選擇哪種打包方式,主要還是看個人習慣和業務場景需求,畢竟 Spring Boot 也支持打包 war 包的。
今天的重點不是打包方式,而是解決困惑了小伙伴打包的 jar 太大的問題。
正常打包項目
給 Spring Boot 打包大家應該很熟了吧,只需要在 pom.xml 文件中配置 spring-boot-maven-plugin
打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后在項目根目錄執行 mvn clean pavkage
就可以完成打包了,如下是我本地的一個項目打包情況:
可以看到打包出的 jar 應用是相當的大了,如小伙伴說的一樣,如果每次修改一個 class 文件或者配置文件,就需要重新打包然后上傳服務器的話,那確實是太麻煩了,可能上傳就浪費大部分時間。。。
應用瘦身(分離lib和配置文件)
其實 jar 包大的原因在於所有的依賴包全部集成在 jar 包里面,如下是瘦身前的 jar 包內部結構:
其中 classes 就是我們項目的代碼,僅僅1.3M,而 129MB 的 lib 目錄是項目中所有的依賴(比如spinrg、Hibernate等依賴),如果我們能把這個 lib 目錄提取出來,整個項目就會變得特別小了。說干就干。
我們知道 Spring Boot 的打包終究是依賴於 Maven ,所以想到更改打包信息,無非就是指定 Maven 的配置。
在 pom.xml 添加如下信息(后文解釋):
<build>
<finalName>你想要的jar包名稱</finalName>
<plugins>
<!-- 1、編譯出不帶 lib 文件夾的Jar包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--表示編譯版本配置有效-->
<fork>true</fork>
<!--引入第三方jar包時,不添加則引入的第三方jar不會被打入jar包中-->
<includeSystemScope>true</includeSystemScope>
<!--排除第三方jar文件-->
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 2、完成對Java代碼的編譯,可以指定項目源碼的jdk版本,編譯后的jdk版本,以及編碼 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- 源代碼使用的JDK版本 -->
<source>${java.version}</source>
<!-- 需要生成的目標class文件的編譯版本 -->
<target>${java.version}</target>
<!-- 字符集編碼 -->
<encoding>UTF-8</encoding>
<!-- 用來傳遞編譯器自身不包含但是卻支持的參數選項 -->
<compilerArguments>
<verbose/>
<!-- windwos環境(二選一) -->
<bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar</bootclasspath>
<!-- Linux環境(二選一) -->
<bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<!-- 3、將所有依賴的jar文件復制到target/lib目錄 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!--復制到哪個路徑,${project.build.directory} 缺醒為 target,其他內置參數見下面解釋-->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<!-- 4、指定啟動類,指定配置文件,將依賴打成外部jar包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!-- 是否要把第三方jar加入到類構建路徑 -->
<addClasspath>true</addClasspath>
<!-- 外部依賴jar包的最終位置 -->
<classpathPrefix>lib/</classpathPrefix>
<!-- 項目啟動類 -->
<mainClass>com.javam4.MyApplication</mainClass>
</manifest>
</archive>
<!--資源文件不打進jar包中,做到配置跟項目分離的效果-->
<excludes>
<!-- 業務jar中過濾application.properties/yml文件,在jar包外控制 -->
<exclude>*.properties</exclude>
<exclude>*.xml</exclude>
<exclude>*.yml</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
如下一一細拆如上配置:
1、spring-boot-maven-plugin
Springboot 默認使用 spring-boot-maven-plugin 來打包,這個插件會將項目所有的依賴打入項目 jar 包里面,正常打包時 spring-boot-maven-plugin 結構如下:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.javam4.MyApplication</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
如下是提取的修改項:
<configuration>
<!--表示編譯版本配置有效-->
<fork>true</fork>
<!--引入第三方jar包時,不添加則引入的第三方jar不會被打入jar包中-->
<includeSystemScope>true</includeSystemScope>
<!--排除第三方jar文件-->
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
修改的作用:
- includeSystemScope:jar包分兩種,一種是spring、mybatis等這種項目依賴的,再就是我們外部手動引入的第三方 jar 依賴,如果該參數不設置為 true 的話是不能被打包進來的~
- includes:這個節點就是排除項目中所有的 jar,那還怎么打包?
其實我們需要將打包插件替換為 maven-jar-plugin,然后使用該插件拷貝依賴到 jar 到外面的 lib 目錄。
2、maven-xxx-plugin
從 2、3、4 你會發現用到了 maven-xxx-plugin 格式的三種插件,簡單說一下這三者的作用:
-
maven-compiler-plugin:
完成對Java代碼的編譯,可以指定項目源碼的jdk版本、編譯后的jdk版本,以及編碼,如果不寫這個插件也是沒問題的,不寫會使用默認的 jdk 版本來處理,只是這樣容易出現版本不匹配的問題,比如本地maven環境用的3.3.9版本,默認會使用jdk1.5進行編譯,而項目中用的jdk1.8的編譯環境,那就會導致打包時編譯不通過。
-
maven-dependency-plugin:
作用就是將所有依賴的jar文件復制到指定目錄下,其中涉及到的
${project.xx}
見下文補充。 -
maven-jar-plugin:
主要作用就是將maven工程打包成jar包。
主要說一下 maven-jar-plugin 插件的如下配置:
<configuration>
<!--資源文件不打進jar包中,做到配置跟項目分離的效果-->
<excludes>
<!-- 業務jar中過濾application.properties/yml文件,在jar包外控制 -->
<exclude>*.properties</exclude>
<exclude>*.xml</exclude>
<exclude>*.yml</exclude>
</excludes>
</configuration>
打包時排除資源配置文件,如果排除了配置文件那么項目啟動是怎么讀取呢?
配置文件有這么一個默認的優先級:
當前項目config目錄下
> 當前項目根目錄下
> 類路徑config目錄下
> 類路徑根目錄下
因此只需要將配置文件復制一份到與 jar 包平級目錄下,或者與jar包平行config目錄下,就能優先使用此配置文件,達到了偽分離目的。
最終的目錄結構如下:
Maven 中的內置變量說明:
${basedir}
項目根目錄${project.build.directory}
構建目錄,缺省為target${project.build.outputDirectory}
構建過程輸出目錄,缺省為target/classes${project.build.finalName}
產出物名稱,缺省為{project.artifactId}-${project.version}${project.packaging}
打包類型,缺省為jar${project.packaging}
打包類型,缺省為jar${project.xxx}
當前pom文件的任意節點的內容
瘦身總結
Spring Boot 框架提供了一套自己的打包機制 — spring-boot-maven-plugin,Springboot 默認使用該插件來打包,打包時會將項目所有的依賴打入項目 jar 包里面,如果我們想要抽離依賴的 jar 僅僅使用該插件是不行的,就需要將打包插件替換為 maven-jar-plugin,並拷貝所有的依賴到 jar 外面的 lib 目錄。
項目打包時,在分離依賴 jar 包基礎上,我們又排除了配置文件,因為配置文件有一個默認的讀取路徑:
當前項目config目錄下
> 當前項目根目錄下
> 類路徑config目錄下
> 類路徑根目錄下
我們只需要在當前項目 jar 包同級目錄創建一個 config 文件夾,然后將配置文件復制一份,這樣就達到了偽分離目的。
之后再修改配置文件,比如端口號、數據庫連接信息等,就不需要重新打包項目了,直接修改完配置文件重啟項目就可以了。
而經過分離依賴后的 jar 包從原來的100多兆到現在的1兆,如果后面需要變更業務邏輯,只需要輕量的編譯項目,快速的實現項目的上傳替換,有效的減少了網絡開銷,提高項目部署的效率。
博客地址:https://niceyoo.cnblogs.com
更多原創內容可以移步我的公眾號,回復「面試」獲取我整理的2020面經。