通过打包优化,可以将SpringBoot 打包后的jar包体积大大的减小,加快传输效率,减少部署时间
将SpringBoot 打包后的jar包解压可以得到3个文件夹
$ tree -d
.
├── BOOT-INF
│ ├── classes # 自己编写的代码
│ └── lib # 第三方依赖jar
├── META-INF
└── org
目录
- 正常打包
- 优化打包
- 2023-06-24补充
- 遇到的问题
- 参考文章
正常打包
默认的配置 pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
启动方式
# 打包
$ maven package
# 启动项目
$ java -jar ./target/demo-0.0.1-SNAPSHOT.jar
优化打包
将正常打包后的产物,这里是./target/demo-0.0.1-SNAPSHOT.jar
,解压
# 拷贝lib文件夹到target目录
cp -R ./target/demo-0.0.1-SNAPSHOT/BOOT-INF/lib ./target
优化的配置 pom.xml
<build>
<plugins>
<!-- 跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!--spring-boot-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--指定项目的启动类-->
<mainClass>com.example.demo.Application</mainClass>
<layout>ZIP</layout>
<!-- 导入依赖 jar -->
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<!--剔除其它的依赖,只需要保留最简单的结构-->
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!--拷贝依赖到jar外面的lib目录-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-lib</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
启动方式
# 打包
$ maven package
# 启动项目
java -Dloader.path=./target/lib -jar ./target/demo-0.0.2-SNAPSHOT.jar
对比优化前后的jar包体积大小,明显可以看到优化后的效果
$ ls -lh target/
25M demo-0.0.1-SNAPSHOT.jar
156K demo-0.0.2-SNAPSHOT.jar
2023-06-24补充
上面的配置方式有一个问题,使用以上配置之后
本地开发环境,通过IDEA直接启动没有问题;不过,命令行方式mvn spring-run:run
启动会报错
- 通过IDEA直接启动
- 命令行方式
mvn spring-run:run
启动会报错
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
at com.example.demo.Application.main(Application.java:10)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
... 1 more
解决方式,应该分打包场景来配置插件
项目结构
$ tree
.
├── Makefile
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── demo
│ ├── Application.java
│ ├── config
│ │ └── AppConfig.java
│ └── controller
│ └── IndexController.java
└── resources
├── application-dev.yml
├── application-pro.yml
├── application.yml
└── assembly.xml
完整 pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.2-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<!--开发环境-->
<profile>
<id>development</id>
<activation>
<!-- 设置默认激活 -->
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.active>dev</profile.active>
</properties>
<build>
<finalName>${name}-development</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
<!--生产环境-->
<profile>
<id>production</id>
<properties>
<profile.active>pro</profile.active>
</properties>
<build>
<finalName>${name}-production</finalName>
<plugins>
<!-- 生成jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<archive>
<!-- 生成的jar中不要包含pom.xml和pom.properties这两个文件 -->
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<!--项目的启动类-->
<mainClass>com.example.demo.Application</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!--拷贝依赖到jar外面的lib目录-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-lib</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<!-- 打包后的包名是否包含assembly的id名 -->
<appendAssemblyId>false</appendAssemblyId>
<!-- tar或者zip包的输出目录 -->
<outputDirectory>${project.build.directory}/dist/</outputDirectory>
<descriptors>
<!-- 引用的assembly配置文件-->
<descriptor>src/main/resources/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- 绑定到package生命周期阶段上 -->
<phase>package</phase>
<goals>
<!-- 只运行一次 -->
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<!--排除resources下面的yml-->
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/assembly.xml</exclude>
</excludes>
</resource>
</resources>
</build>
</profile>
</profiles>
</project>
assembly.xml
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>package</id>
<formats>
<!-- zip,tar,tar.gz,tgz,tar.bz2,tar.snappy,tar.xz,txz,jar,dir,war -->
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<!-- config -->
<fileSet>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>*.yml</include>
</includes>
<filtered>true</filtered>
<outputDirectory>${file.separator}/config</outputDirectory>
</fileSet>
<!-- lib -->
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>${file.separator}/lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<!-- jar -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>${file.separator}</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
不同场景下的配置文件
application.yml
spring:
profiles:
# 取pom.xml当中profile当中配置的profile.active标签
active: @profile.active@
app:
name: default
application-pro.yml
app:
name: pro
application-dev.yml
app:
name: dev
配置类 AppConfig.java
package com.example.demo.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class AppConfig {
@Value("${app.name}")
private String name;
}
控制器 IndexController.java
package com.example.demo.controller;
import com.example.demo.config.AppConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
@Autowired
private AppConfig appConfig;
@GetMapping("/")
public String index() {
return "Hello: " + appConfig.getName();
}
}
启动类 Application.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
可以使用 Makefile 整合命令,这样会更方便
Makefile
# 开发环境
.PHONY: dev
dev:
mvn spring-run:run
# 发布生产环境
.PHONY: build
build:
mvn clean package -Pproduction
1、启动开发环境
make dev
访问:http://localhost:8080/
响应结果:
Hello: dev
2、发布生产环境
make build
查看打包后的文件,实现了lib和config的分离,自己写的代码仅4.3KB,部署拷贝就快了很多
$ ls -lh target/dist/demo-production
160B Jun 24 22:23 config
4.3K Jun 24 22:23 demo-production.jar
1.1K Jun 24 22:23 lib
启动项目
java -jar demo-production.jar
访问:http://localhost:8080/
响应结果:
Hello: pro
遇到的问题
打包完成之后,发现jar的体积还是很大,几十兆,原因是打包的配置里多配置了如下配置,需要删除
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
参考文章
- 学会这招,来给你的 SpringBoot 工程部署的 jar 包瘦瘦身吧!
- 给你的 SpringBoot 工程部署的 jar 包瘦瘦身吧!
- 基于Maven的profiles多环境配置
- SpringBoot项目打包分离lib,配置和资源文件部署总结
- springboot-填坑系列-jar启动分离依赖lib和配置