Springboot构建包使用BOOT-INF中的class覆盖依赖包中的class

Springboot构建包使用BOOT-INF中的class覆盖依赖包中的class

1. 背景说明

在Springboot项目中,使用 spring-boot-maven-plugin 插件,构建采用layout:ZIP模式的可执行jar包(非FAT模式).项目中需要覆盖某个依赖包对应的class文件.实际打包后,依赖包中的class并没有覆盖.以下有两种场景,

  • Springboot主包项目中直接覆盖依赖包中的class
  • 多模块项目中,子项目间class相互覆盖

2. 复制依赖包class到主包BOOT-INF/classes

在多模块项目中,子项目之间的class需要相互覆盖,是想的方案为,把子项目需要覆盖的class复制到主项目BOOT-INF/classes目录下.这里需要借助maven-dependency-plugin插件,对应配置如下

xml 复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
	<execution>
		<id>unpack-dependency</id>
		<phase>process-resources</phase>
		<goals>
			<goal>unpack</goal>
		</goals>
		<configuration>
			<artifactItems>
				<artifactItem>
          <!--小游戏 地心侠士 公众号:小满小慢 QQ:464884492-->
					<groupId>com.herbert.a.common</groupId>
					<artifactId>common-web</artifactId>
					<version>1.0.0</version>
					<type>jar</type>
					<overWrite>true</overWrite>
					<includes>com/herbert/dxxs/**</includes>
					<outputDirectory>${project.build.outputDirectory}</outputDirectory>
				</artifactItem>
			</artifactItems>
		</configuration>
	</execution>
</executions>
</plugin>

其中 <includes>com/herbert/dxxs/**</includes> 为需要复制的class文件路径.支持通配符.

3. 主包BooT-INF/classes没有覆盖依赖包中的class

在主包jar同级的目录添加文件loader.properties,配置内容为

properties 复制代码
loader.path=lib,resources

在执行命令 java -jar hebert-1.0.0-SNAPSHOT.jar 时,采用org.springframework.boot.loader.PropertiesLauncher启动程序.在配置loader.path的的情况下,PropertiesLauncher会优先按照配置的classpath顺序加载class,最后再加载jar包中的 /BOOT-INF/classes 和 /BOOT-INF/lib

对应代码如下:

java 复制代码
// org.springframework.boot.loader.PropertiesLauncher.ClassPathArchives

ClassPathArchives() throws Exception {
	this.classPathArchives = new ArrayList<>();
  // 1. 加载 loader.path 配置class
  // 小游戏 地心侠士 公众号:小满小慢 QQ:464884492
	for (String path : PropertiesLauncher.this.paths) {
		for (Archive archive : getClassPathArchives(path)) {
			addClassPathArchive(archive);
		}
	}
  //2. 加载 jar 包中的 /BOOT-INF/classes 和 /BOOT-INF/lib
	addNestedEntries();
}

private void addNestedEntries() {
// The parent archive might have "BOOT-INF/lib/" and "BOOT-INF/classes/"
// directories, meaning we are running from an executable JAR. We add nested
// entries from there with low priority (i.e. at end).
  try {
    Iterator<Archive> archives = PropertiesLauncher.this.parent.getNestedArchives(null,
    		JarLauncher.NESTED_ARCHIVE_ENTRY_FILTER);
    while (archives.hasNext()) {
      // 小游戏 地心侠士 公众号:小满小慢 QQ:464884492
    	this.classPathArchives.add(archives.next());
    }
  }
  catch (IOException ex) {
  // Ignore
  }
}

private List<Archive> getClassPathArchives(String path) throws Exception {
	String root = cleanupPath(handleUrl(path));
	List<Archive> lib = new ArrayList<>();
	File file = new File(root);
  // 小游戏 地心侠士 公众号:小满小慢 QQ:464884492
	if (!"/".equals(root)) {
		if (!isAbsolutePath(root)) {
			file = new File(PropertiesLauncher.this.home, root);
		}
		if (file.isDirectory()) {
			debug("Adding classpath entries from " + file);
			Archive archive = new ExplodedArchive(file, false);
			lib.add(archive);
		}
	}
	Archive archive = getArchive(file);
	if (archive != null) {
		debug("Adding classpath entries from archive " + archive.getUrl() + root);
		lib.add(archive);
	}
	List<Archive> nestedArchives = getNestedArchives(root);
	if (nestedArchives != null) {
		debug("Adding classpath entries from nested " + root);
		lib.addAll(nestedArchives);
	}
	return lib;
}

其中 NESTED_ARCHIVE_ENTRY_FILTER 对应代码如下

java 复制代码
// org.springframework.boot.loader.JarLauncher
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
if (entry.isDirectory()) {
  //小游戏 地心侠士 公众号:小满小慢 QQ:464884492
	return entry.getName().equals("BOOT-INF/classes/");
}
return entry.getName().startsWith("BOOT-INF/lib/");
};

4. 修改loader.path

通过以上代码可以知道,在配置loader.path后,class的加载顺序与我们配置顺序有关.BOOT-INF/classes/ 和 BOOT-INF/lib/不显示配置的情况下,默认最后加载. 最终 loader.properties 配置内容为

properties 复制代码
loader.path=loader.path=BOOT-INF/classes,lib,resources

启动时需要查看加载了哪些classpath,可以在启动时添加参数 -Dloader.debug=true,完整命令如下

bash 复制代码
java -Dloader.debug=true -jar hebert-1.0.0-SNAPSHOT.jar

5. 确定class到底从哪里加载

  1. 使用jps命令获取进程ID
  2. 使用 java -jar .\arthas-boot.jar 进程ID
  3. 使用 sc -d 获取class加载信息,code-source属性为class的加载路径
Bash 复制代码
[arthas@13192]$ sc -d com.herbert.a.dxxs.commandUtils
 class-info        com.herbert.a.dxxs.commandUtils
 code-source       file:/D:/hebert-1.0.0-SNAPSHOT.jar!/BOOT-INF/classes!/
 name              com.herbert.a.dxxs.commandUtils
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       commandUtils
 modifier          public
 annotation
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-org.springframework.boot.loader.LaunchedURLClassLoader@5ce65a89
                     +-sun.misc.Launcher$AppClassLoader@5c647e05
                       +-sun.misc.Launcher$ExtClassLoader@45c8e616
 classLoaderHash   5ce65a89

6. 调试Springboot加载过程

主包的项目在pom文件中添加loader依赖

xml 复制代码
<dependency>
   <!--小游戏 地心侠士 公众号:小满小慢 QQ:464884492-->
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-loader</artifactId>
   <version>${spring-boot.version}</version>
   <scope>provided</scope>
   <optional>true</optional>
</dependency>

启动时添加远程调试,并设置在未连接时,挂起进程,对应命令如下

bash 复制代码
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -jar hebert-1.0.0-SNAPSHOT.jar
相关推荐
我就是全世界17 分钟前
OpenCode Go 套餐划算吗:先看你用哪个模型
开发语言·后端·golang
乌暮1 小时前
请求进门先安检:Spring 拦截器与统一功能实战指南
java·开发语言·后端·学习·spring
学长毕设1 小时前
基于SpringBoot的烘焙商品商品系统-附源码
java·spring boot·后端
付威20232 小时前
Durable Execution 怎样设计:从运行记录到中断恢复
后端
LHICOD3 小时前
Linux-2
java·linux·后端·jar·vmware·svm·vm
小杨互联网3 小时前
Go 现代 CLI 工具离线镜像合集
开发语言·后端·golang·开源·go·命令行
lazy H3 小时前
【计算机网络】第 15 篇:Java BIO、NIO 与高并发网络通信——从 Socket 理解 I/O 模型
网络·后端·tcp/ip·计算机网络·nio·408
geovindu4 小时前
rust: Semaphore Pattern
开发语言·后端·设计模式·rust·代理模式·企业级信号量模式·同步型模式
Thneonl5 小时前
Claude Code 频繁 Compacting?发布次日我根治了 2 个痛点
后端·rust
环境栈笔记7 小时前
指纹浏览器安卓怎么用?从运行位置到功能验收,先把三种方案分清
前端·人工智能·后端·自动化