SpringBoot-Maven打包压缩瘦身方式
作者:爱是与世界平行
一、Spring Boot 可执行 jar 分析
Spring Boot 中默认打包成的 jar 叫做 可执行 jar,这种 jar 不同于普通的 jar,普通的 jar 不可以通过 java -jar xxx.jar 命令执行,普通的 jar 主要是被其他应用依赖,Spring Boot 打成的 jar 可以执行,但是不可以被其他的应用所依赖,即使强制依赖,也无法获取里边的类。
但是可执行 jar 并不是 Spring Boot 独有的,Java 工程本身就可以打包成可执行 jar 。
Spring Boot 项目中一个默认的插件配置 spring-boot-maven-plugin ,这个打包插件存在 5 个方面的功能,从插件命令就可以看出:
五个功能分别是:
build-info
:生成项目的构建信息文件 build-info.propertiesrepackage
:这个是默认 goal,在 mvn package 执行之后,这个命令再次打包生成可执行的 jar,同时将 mvn package 生成的 jar 重命名为 *.originrun
:这个可以用来运行 Spring Boot 应用start
:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理stop
:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
这里功能,默认情况下使用就是 repackage 功能,其他功能要使用,则需要开发者显式配置。
1.1 打包
repackage 功能的 作用,就是在打包的时候,多做一点额外的事情:
- 1.首先 mvn package 命令 对项目进行打包,打成一个 jar,这个 jar 就是一个普通的 jar,可以被其他项目依赖,但是不可以被执行。
- 2.repackage 命令,对第一步 打包成的 jar 进行再次打包,将之打成一个 可执行 jar ,通过将第一步打成的 jar 重命名为 *.original 文件
举个例子:
对任意一个 Spring Boot 项目进行打包,可以执行 mvn package 命令,也可以直接在 IDEA 中点击 package ,如下 :
打包成功之后, target 中的文件如下:
这里有两个文件,第一个 restful-0.0.1-SNAPSHOT.jar 表示打包成的可执行 jar ,第二个 restful-0.0.1-SNAPSHOT.jar.original 则是在打包过程中 ,被重命名的 jar,这是一个不可执行 jar,但是可以被其他项目依赖的 jar。
1.2 两种 jar 的比较
可执行 jar 解压之后,目录如下:
可执行 jar 中,我们自己的代码是存在 于 BOOT-INF/classes/ 目录下,另外,还有一个 META-INF 的目录,该目录下有一个 MANIFEST.MF 文件,打开该文件,内容如下:
Manifest-Version: 1.0 Implementation-Title: restful Implementation-Version: 0.0.1-SNAPSHOT Start-Class: org.javaboy.restful.RestfulApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.1.6.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher
可以看到,这里定义了一个 Start-Class,这就是可执行 jar 的入口类,Spring-Boot-Classes 表示我们自己代码编译后的位置,Spring-Boot-Lib 则表示项目依赖的 jar 的位置。
换句话说,如果自己要打一个可执行 jar 包的话,除了添加相关依赖之外,还需要配置 META-INF/MANIFEST.MF 文件。
这是可执行 jar 的结构,那么不可执行 jar 的结构呢?
首先将默认的后缀 .original 除去,然后给文件重命名,重命名完成,进行解压:
解压后可以看到,不可执行 jar 根目录就相当于我们的 classpath,解压之后,直接就能看到我们的代码,它也有 META-INF/MANIFEST.MF 文件,但是文件中没有定义启动类等。
Manifest-Version: 1.0 Implementation-Title: restful Implementation-Version: 0.0.1-SNAPSHOT Build-Jdk-Spec: 1.8 Created-By: Maven Archiver 3.4.0
注意
不可以执行 jar 也没有将项目的依赖打包进来。
从这里我们就可以看出,两个 jar ,虽然都是 jar 包,但是内部结构是完全不同的,因此一个可以直接执行,另一个则可以被其他项目依赖。
1.3 一次打包两个 jar
一般来说,Spring Boot 直接打包成可执行 jar 就可以了,不建议将 Spring Boot 作为普通的 jar 被其他的项目所依赖。
如果有这种需求,建议将被依赖的部分,单独抽出来做一个普通的 Maven 项目,然后在 Spring Boot 中引用这个 Maven 项目。
如果非要将 Spring Boot 打包成一个普通 jar 被其他项目依赖,技术上来说,也是可以的,给 spring-boot-maven-plugin 插件添加如下配置:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <classifier>exec</classifier> </configuration> </plugin> </plugins> </build>
配置的 classifier 表示可执行 jar 的名字,配置了这个之后,在插件执行 repackage 命令时,就不会给 mvn package 所打成的 jar 重命名了,所以,打包后的 jar 如下:
第一个 jar 表示可以被其他项目依赖的 jar ,第二个 jar 则表示一个可执行 jar。
二、SpringBoot迭代发布JAR瘦身配置
默认情况下,插件 spring-boot-maven-plugin 会把整个项目打包成一个可运行的Jar包(即所谓的Flat Jar),导致了这个Jar包很大(通常有几十M+)。
把第三方的JAR与项目代码分离,第三方的JAR把移除到lib文件夹中,即可实现为我们的可执行JAR瘦身,配置如下:
<plugins> <!-- spring boot thin jar configuration --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!-- main 入口 --> <mainClass>com.bdfint.logistics.app.driver.LogisticsAppDriverApplication</mainClass> <!-- 设置为ZIP,此模式下spring-boot-maven-plugin会将Manifest.MF文件中的Main-Class设置为org.springframework.boot.loader.PropertiesLauncher --> <layout>ZIP</layout> <!-- 需要包含的jar包 --> <includes> <!-- 不包含任何jar包 --> <!--<include>--> <!--<groupId>nothing</groupId>--> <!--<artifactId>nothing</artifactId>--> <!--</include>--> <include> <groupId>com.bdfint.logistics</groupId> <artifactId>logistics-api</artifactId> </include> <include> <groupId>com.bdfint.logistics</groupId> <artifactId>logistics-common</artifactId> </include> </includes> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <!-- third-party jar into lib directory --> <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> <outputDirectory>${project.build.directory}/lib</outputDirectory> <!-- 需要排除的jar的 groupId --> <excludeGroupIds> com.bdfint.logistics </excludeGroupIds> </configuration> </execution> </executions> </plugin> </plugins>
接下来,执行打包命令:mvn clean package -Dmaven.test.skip=true,打包后在target目录下就包含我们的JAR和lib目录,如下图:
CMD定位到target目录下,执行命令:java -Dloader.path=./lib -jar logistics-app-driver-2.9.1.1.jar,即可把项目JAR跑起来!
三、引入内部编译的依赖
如果想引入内部编译的依赖包可以通过includes属性进行添加,利用引用了内部的dubbo可以通过如下配置:
配置完成后,再次执行编译:mvn clean package。
如果引入内部包过多,也可以通过excludeGroupIds属性去掉不变的依赖包。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layout>ZIP</layout> <!--去除在生产环境中不变的依赖--> <excludeGroupIds> org.springframework.boot, org.springframework, org.springframework.data, org.apache.tomcat.embed </excludeGroupIds> </configuration> </plugin> </plugins> </build>
注:layout 必须是 ZIP 、 excludeGroupIds 中时忽略也是就需要打在外部的 jar。
3.1 剔除不需要的依赖
3.1.1 方式一:排除一个具体的maven 模块,通过唯一的groupId和artifactId组合来实现。(如果有必要,可以加入classifier来唯一确认。)
<project> ... <build> ... <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.6.RELEASE</version> <configuration> <excludes> <exclude> <groupId>com.foo</groupId> <artifactId>bar</artifactId> </exclude> </excludes> </configuration> ... </plugin> ... </plugins> ... </build> ... </project>
3.1.2 方式二:排除和“指定的artifactId”相符的所有maven模块
<project> ... <build> ... <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.6.RELEASE</version> <configuration> <excludeArtifactIds>my-lib,another-lib</excludeArtifactIds> </configuration> ... </plugin> ... </plugins> ... </build> ... </project>
3.1.3 方式三:排除属于“指定的groupId”的所有maven模块
<project> ... <build> ... <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.6.RELEASE</version> <configuration> <excludeGroupIds>com.foo</excludeGroupIds> </configuration> ... </plugin> ... </plugins> ... </build> ... </project>
四、Spring Boot Thin Launcher
使用插件以后,原来打包45M的jar包,现在体积只有 40K,瘦身效果非常明显,并且也不改变原有的打包方式和部署方法。打包以后的jar也是可执行的。
第一次执行时,会自动下载所有的依赖包,并且缓存到本地,默认的缓存路径是${user.home}/.m2/,你也可以使用如下命令在启动时修改依赖的路径
java -Dthin.root=. -jar app-0.0.1-SNAPSHOT.jar
在pom.xml添加以下插件代码,然后正常地打包即可:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-thin-layout</artifactId> <version>1.0.3.RELEASE</version> </dependency> </dependencies> <configuration> <executable>true</executable> </configuration> </plugin> </plugins> </build>
五、 maven的assembly打包插件
assembly配置:
在项目中创建一个文件,放在src/main/assembly/assembly.xml中。
assembly中的具体配置:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> <!-- 必须写,否则打包时会有 assembly ID must be present and non-empty 错误 这个名字最终会追加到打包的名字的末尾,如项目的名字为 speed-api-0.0.1-SNAPSHOT, 则最终生成的包名为 speed-api-0.0.1-SNAPSHOT-bin.zip --> <id>bin</id> <!-- 打包后的文件格式,可以是zip,tar,tar.gz,tar.bz2,jar,war,dir --> <formats> <format>zip</format> </formats> <!-- 压缩包下是否生成和项目名相同的根目录 --> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <!-- 不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录 --> <useProjectArtifact>false</useProjectArtifact> <outputDirectory>lib</outputDirectory> <unpack>false</unpack> </dependencySet> </dependencySets> <fileSets> <!-- 把项目相关的说明文件,打包进zip文件的根目录 --> <fileSet> <directory>${project.basedir}</directory> <outputDirectory></outputDirectory> <includes> <include>README*</include> <include>LICENSE*</include> <include>NOTICE*</include> </includes> </fileSet> <!-- 把项目的配置文件,打包进zip文件的config目录 --> <fileSet> <directory>${project.basedir}/src/main/resources</directory> <outputDirectory>config</outputDirectory> </fileSet> <!-- 把项目的脚本文件,打包进zip文件的bin目录 --> <fileSet> <directory>${project.basedir}/src/main/bin</directory> <outputDirectory>bin</outputDirectory> </fileSet> <!-- 把项目自己编译出来的jar文件,打包进zip文件的根目录 --> <fileSet> <directory>${project.build.directory}</directory> <outputDirectory></outputDirectory> <includes> <include>*.jar</include> </includes> </fileSet> </fileSets> </assembly>
maven中的配置:
<build> <plugins> <!-- 指定启动类,将依赖打成外部jar包 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <!-- 生成的jar中,不要包含pom.xml和pom.properties这两个文件 --> <addMavenDescriptor>false</addMavenDescriptor> <manifest> <!-- 是否要把第三方jar放到manifest的classpath中 --> <addClasspath>true</addClasspath> <!-- 外部依赖jar包的最终位置 --> <classpathPrefix>lib/</classpathPrefix> <!-- 项目启动类 --> <mainClass>com.zbrx.speed.App</mainClass> </manifest> </archive> </configuration> </plugin> <!-- 使用assembly打包 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptors> <!-- assembly配置文件位置 --> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <!-- 打包发布时,跳过单元测试 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build>
最终打包后的效果:
压缩包里的文件内容:
config配置文件:
六、SpringBoot剔除静态文件
静态文件排除到项目外面,pom.xml 继续引入:
<resources> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> <excludes> <exclude>static/**</exclude> </excludes> </resource> </resources>
配置nginx,访问静态资源文件:
server { listen 80; server_name www.cloudbed.vip; location / { proxy_pass http://127.0.0.1:8080; } #静态文件交给nginx处理 location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|ioc|rar| zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$ { root /cloudbed/static; expires 30d; } location ~ .*\.(js|css)?$ { root /cloudbed/static; expires 1h; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
6.1 打包时排除resources下的资源文件
<build> ... <resources> <resource> <directory>src/main/resources</directory> <excludes> <exclude>**/*.properties</exclude> <exclude>**/*.xml</exclude> <exclude>**/*.yml</exclude> </excludes> </resource> </resources> <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> ... </plugins> ... </build>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。