• Unreal is funny !!!

springboot layered jar 原理

Java 站长 3年前 (2022-02-17) 95次浏览 已收录 0个评论
文章目录[隐藏]

前言

springboot 自 2.3 版本起,引入了 layered jar 特性,使得在 docker 镜像构建中,将 jar包根据更新频率分为不同的层,节省 docker 构建时间和占用空间,下面我们将介绍如何使用 和 其中原理。

Docker 构建使用

springboot 应用镜像的构建分为两步,编译阶段和运行阶段,Dockerfile 如下:

# 编译 java代码为 jar   # 这里的编译镜像是 事先做好的
FROM maven:3.8.1-adoptopenjdk-11 AS builder   
WORKDIR /usr/src/app
COPY pom.xml .
RUN mvn -B -s /usr/share/maven/ref/settings.xml dependency:go-offline
COPY . .
RUN mvn -B -s /usr/share/maven/ref/settings.xml package -DskipTests
RUN mv target/*.jar fatjar.jar
RUN java -Djarmode=layertools -jar fatjar.jar extract

#copy jar 到运行的镜像并启动

FROM  openjdk11:jre-11.0.11_9-alpine
WORKDIR app
COPY --from=builder /usr/src/app/dependencies/ ./
COPY --from=builder /usr/src/app/spring-boot-loader/ ./
COPY --from=builder /usr/src/app/snapshot-dependencies/ ./
COPY --from=builder /usr/src/app/application/ ./

ENV SPRING_PROFILE ${SPRING_PROFILE:-test}
ENV JAVA_OPTS -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0
ENTRYPOINT [ "/bin/sh", "-c", "java  -Dspring.profiles.active=$SPRING_PROFILE $JAVA_OPTS org.springframework.boot.loader.JarLauncher" ]

原理分析

通常呢,我们一个打包后的 jar包,zip 解压后,都会是这样的目录结构:
─BOOT-INF
│ ├─classes
│ │ └─com
│ │ ——–xxxx
│ │ ————-xxxx
│ │ ——————xxxx
│ └─lib
├─META-INF
——–maven
└─org
——-─springframework
—————–boot
—————–loader
—————–archive
—————–data
—————–jar
—————–jarmode
—————–util
其中 META-INF/MANIFEST.MF 文件中 Main-Class: org.springframework.boot.loader.JarLauncher 指定了 启动类。
我们执行jar文件有两种方式:
1. java -jar xxx.jar
2. 解压jar包, java com.xxx.Main (这里 springboot 应用中是 org.springframework.boot.loader.JarLauncher

解压jar 包的环节

java -Djarmode=layertools -jar fatjar.jar extract

这里呢,extract 命令调用了 jar包内的 BOOT-INF/lib/spring-boot-jarmode-layertools-2.6.3.jar, 使用 spring 的规则 对jar包进行解压(原理请看下面),

这里的解压后的目录结构是:
─application
│ ├─BOOT-INF
│ │ └─classes
│ │——–com
│ │————xxx
│ │—————xxxx
│ └─META-INF
│———-maven
├─dependencies
——–BOOT-INF
————lib
–snapshot-dependencies
——–BOOT-INF
————lib
├─spring-boot-loader
——-org
———-springframework
————-boot
—————loader
—————archive
—————data
—————jar
—————jarmode
—————util

上面四条 COPY 命令 将 denpendency , spring-boot-loader , snapshot-dependencies, application 内的文件分别拷贝到 运行镜像中,
拷贝后的文件 组成了 jar包 zip 解压后的形式,通过 java 启动类 的方式执行。
这四个目录变化频率也是依次变大的,每当应用程序修改,重新构建镜像,可有效利用 docker缓存,减少磁盘占用,减少构建时间。

spring-boot-jarmode-layertools.jar 源码分析

一个打包成jar的springboot 应用,jar 包内都会有两个独特的文件:
BOOT-INF/lib/spring-boot-jarmode-layertools-2.6.3.jar
BOOT-INF/layers.idx

看一下 layer.idx:

- "dependencies":
  - "BOOT-INF/lib/HdrHistogram-2.1.12.jar"
  - "BOOT-INF/lib/HikariCP-4.0.3.jar"
  - "BOOT-INF/lib/LatencyUtils-2.0.3.jar"
  - "BOOT-INF/lib/activation-1.1.1.jar"
  - "BOOT-INF/lib/aspectjweaver-1.9.7.jar"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
  - "BOOT-INF/lib/xxxx-redis-spring-boot-starter-2.5.0-SNAPSHOT.jar"
  - "BOOT-INF/lib/xxxx-common-1.0.1-SNAPSHOT.jar"
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

layer.idx 描述了 java -Djarmode=layertools -jar xxx.jar extract 后的目录结构,

spring-boot-jarmode-layertools-2.6.3.jar 的代码在 https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools

我们大概看下其中几个类:

org.springframework.boot.jarmode.layertools.IndexedLayers
这个类对应了 layer.idx 文件,解析并放入一个 Map, layers 中,

class IndexedLayers implements Layers {

	private final Map<String, List<String>> layers = new LinkedHashMap<>();

        // 解析layer.idx 文件,放入 layers 中备用
	IndexedLayers(String indexFile) {
		String[] lines = Arrays.stream(indexFile.split("\n")).map((line) -> line.replace("\r", ""))
				.filter(StringUtils::hasText).toArray(String[]::new);
		List<String> contents = null;
		for (String line : lines) {
			if (line.startsWith("- ")) {
				contents = new ArrayList<>();
				this.layers.put(line.substring(3, line.length() - 2), contents);
			}
			else if (line.startsWith("  - ")) {
				contents.add(line.substring(5, line.length() - 1));
			}
			else {
				throw new IllegalStateException("Layer index file is malformed");
			}
		}
		Assert.state(!this.layers.isEmpty(), "Empty layer index file loaded");
	}
       ..........
}

org.springframework.boot.jarmode.layertools.ExtractCommand
这个类执行解压的过程,读取 jar包遍历其中文件,根据 上面 IndexedLayers 中存的规则分别 保存在不同的目录中

class ExtractCommand extends Command {

	@Override
	protected void run(Map<Option, String> options, List<String> parameters) {
		try {
			File destination = options.containsKey(DESTINATION_OPTION) ? new File(options.get(DESTINATION_OPTION))
					: this.context.getWorkingDir();
			for (String layer : this.layers) {
                               // 创建 dependency, application 等 目录
				if (parameters.isEmpty() || parameters.contains(layer)) {
					mkDirs(new File(destination, layer));
				}
			}
			try (ZipInputStream zip = new ZipInputStream(new FileInputStream(this.context.getArchiveFile()))) {
				ZipEntry entry = zip.getNextEntry();
				Assert.state(entry != null, "File '" + this.context.getArchiveFile().toString()
						+ "' is not compatible with layertools; ensure jar file is valid and launch script is not enabled");
				while (entry != null) {
					if (!entry.isDirectory()) {
                                               // 找到文件应该保存的目录
						String layer = this.layers.getLayer(entry);
						if (parameters.isEmpty() || parameters.contains(layer)) {
                                                       //  保存文件
							write(zip, entry, new File(destination, layer));
						}
					}
					entry = zip.getNextEntry();
				}
			}
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}

}

其实作用就是对文件进行CV而已。


本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:springboot layered jar 原理
喜欢 (1)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址