腾讯云:江南一点雨:Spring Boot2 系列教程(四十)Spring Boot 可执行 jar 分析
脚本之家:田心双木:详解SpringBoot迭代发布JAR瘦身配置
CSDN:希尔伯特:SpringBoot瘦身打包部署
简书:思_路:Springboot 打包瘦身
张扎瓦的博客:springboot将项目与依赖分开打包
柒’s Blog:SpringBoot 2.0 开发案例之百倍级减肥瘦身之旅
周钦雄:Spring cloud的Maven插件(一):repackage目标
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 个方面的功能,从插件命令就可以看出:
五个功能分别是:
这里功能,默认情况下使用就是 repackage 功能,其他功能要使用,则需要开发者显式配置。
repackage 功能的 作用,就是在打包的时候,多做一点额外的事情:
举个例子:
对任意一个 Spring Boot 项目进行打包,可以执行 mvn package 命令,也可以直接在 IDEA 中点击 package ,如下 :
打包成功之后, target 中的文件如下:
这里有两个文件,第一个 restful-0.0.1-SNAPSHOT.jar 表示打包成的可执行 jar ,第二个 restful-0.0.1-SNAPSHOT.jar.original 则是在打包过程中 ,被重命名的 jar,这是一个不可执行 jar,但是可以被其他项目依赖的 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 包,但是内部结构是完全不同的,因此一个可以直接执行,另一个则可以被其他项目依赖。
一般来说,Spring Boot 直接打包成可执行 jar 就可以了,不建议将 Spring Boot 作为普通的 jar 被其他的项目所依赖。如果有这种需求,建议将被依赖的部分,单独抽出来做一个普通的 Maven 项目,然后在 Spring Boot 中引用这个 Maven 项目。
如果非要将 Spring Boot 打包成一个普通 jar 被其他项目依赖,技术上来说,也是可以的,给 spring-boot-maven-plugin 插件添加如下配置:
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<classifier>execclassifier>
configuration>
plugin>
plugins>
build>
配置的 classifier 表示可执行 jar 的名字,配置了这个之后,在插件执行 repackage 命令时,就不会给 mvn package 所打成的 jar 重命名了,所以,打包后的 jar 如下:
第一个 jar 表示可以被其他项目依赖的 jar ,第二个 jar 则表示一个可执行 jar。
默认情况下,插件 spring-boot-maven-plugin 会把整个项目打包成一个可运行的Jar包(即所谓的Flat Jar),导致了这个Jar包很大(通常有几十M+)。
把第三方的JAR与项目代码分离,第三方的JAR把移除到lib文件夹中,即可实现为我们的可执行JAR瘦身,配置如下:
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<mainClass>com.bdfint.logistics.app.driver.LogisticsAppDriverApplicationmainClass>
<layout>ZIPlayout>
<includes>
<include>
<groupId>com.bdfint.logisticsgroupId>
<artifactId>logistics-apiartifactId>
include>
<include>
<groupId>com.bdfint.logisticsgroupId>
<artifactId>logistics-commonartifactId>
include>
includes>
configuration>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-dependency-pluginartifactId>
<executions>
<execution>
<id>copy-dependenciesid>
<phase>prepare-packagephase>
<goals>
<goal>copy-dependenciesgoal>
goals>
<configuration>
<outputDirectory>${project.build.directory}/liboutputDirectory>
<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.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<layout>ZIPlayout>
<excludeGroupIds>
org.springframework.boot,
org.springframework,
org.springframework.data,
org.apache.tomcat.embed
excludeGroupIds>
configuration>
plugin>
plugins>
build>
注:layout 必须是 ZIP 、 excludeGroupIds 中时忽略也是就需要打在外部的 jar。
1 <project>
2 ...
3 <build>
4 ...
5 <plugins>
6 ...
7 <plugin>
8 <groupId>org.springframework.bootgroupId>
9 <artifactId>spring-boot-maven-pluginartifactId>
10 <version>1.5.6.RELEASEversion>
11 <configuration>
12 <excludes>
13 <exclude>
14 <groupId>com.foogroupId>
15 <artifactId>barartifactId>
16 exclude>
17 excludes>
18 configuration>
19 ...
20 plugin>
21 ...
22 plugins>
23 ...
24 build>
25 ...
26 project>
1 <project>
2 ...
3 <build>
4 ...
5 <plugins>
6 ...
7 <plugin>
8 <groupId>org.springframework.bootgroupId>
9 <artifactId>spring-boot-maven-pluginartifactId>
10 <version>1.5.6.RELEASEversion>
11 <configuration>
12 <excludeArtifactIds>my-lib,another-libexcludeArtifactIds>
13 configuration>
14 ...
15 plugin>
16 ...
17 plugins>
18 ...
19 build>
20 ...
21 project>
1 <project>
2 ...
3 <build>
4 ...
5 <plugins>
6 ...
7 <plugin>
8 <groupId>org.springframework.bootgroupId>
9 <artifactId>spring-boot-maven-pluginartifactId>
10 <version>1.5.6.RELEASEversion>
11 <configuration>
12 <excludeGroupIds>com.fooexcludeGroupIds>
13 configuration>
14 ...
15 plugin>
16 ...
17 plugins>
18 ...
19 build>
20 ...
21 project>
使用插件以后,原来打包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.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimentalgroupId>
<artifactId>spring-boot-thin-layoutartifactId>
<version>1.0.3.RELEASEversion>
dependency>
dependencies>
<configuration>
<executable>trueexecutable>
configuration>
plugin>
plugins>
build>
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">
<id>binid>
<formats>
<format>zipformat>
formats>
<includeBaseDirectory>falseincludeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>falseuseProjectArtifact>
<outputDirectory>liboutputDirectory>
<unpack>falseunpack>
dependencySet>
dependencySets>
<fileSets>
<fileSet>
<directory>${project.basedir}directory>
<outputDirectory>outputDirectory>
<includes>
<include>README*include>
<include>LICENSE*include>
<include>NOTICE*include>
includes>
fileSet>
<fileSet>
<directory>${project.basedir}/src/main/resourcesdirectory>
<outputDirectory>configoutputDirectory>
fileSet>
<fileSet>
<directory>${project.basedir}/src/main/bindirectory>
<outputDirectory>binoutputDirectory>
fileSet>
<fileSet>
<directory>${project.build.directory}directory>
<outputDirectory>outputDirectory>
<includes>
<include>*.jarinclude>
includes>
fileSet>
fileSets>
assembly>
maven中的配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-jar-pluginartifactId>
<configuration>
<archive>
<addMavenDescriptor>falseaddMavenDescriptor>
<manifest>
<addClasspath>trueaddClasspath>
<classpathPrefix>lib/classpathPrefix>
<mainClass>com.zbrx.speed.AppmainClass>
manifest>
archive>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-assembly-pluginartifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xmldescriptor>
descriptors>
configuration>
<executions>
<execution>
<id>make-assemblyid>
<phase>packagephase>
<goals>
<goal>singlegoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-surefire-pluginartifactId>
<configuration>
<skipTests>trueskipTests>
configuration>
plugin>
plugins>
build>
最终打包后的效果:
压缩包里的文件内容:
config配置文件:
静态文件排除到项目外面,pom.xml 继续引入:
<resources>
<resource>
<filtering>truefiltering>
<directory>src/main/resourcesdirectory>
<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;
}
}
1 <build>
2 ...
3 <resources>
4 <resource>
5 <directory>src/main/resourcesdirectory>
6 <excludes>
7 <exclude>**/*.propertiesexclude>
8 <exclude>**/*.xmlexclude>
9 <exclude>**/*.ymlexclude>
10 excludes>
11 resource>
12 resources>
13 <plugins>
14 ...
15 <plugin>
16 <groupId>org.springframework.bootgroupId>
17 <artifactId>spring-boot-maven-pluginartifactId>
18 <executions>
19 <execution>
20 <goals>
21 <goal>repackagegoal>
22 goals>
23 execution>
24 executions>
25 plugin>
26 ...
27 plugins>
28 ...
29 build>