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
相关推荐
苇柠1 小时前
Spring框架基础(1)
java·后端·spring
xiucai_cs1 小时前
布隆过滤器原理与Spring Boot实战
java·spring boot·后端·布隆过滤器
向阳花自开1 小时前
Spring Boot 常用注解速查表
java·spring boot·后端
程序视点2 小时前
如何高效率使用 Cursor ?
前端·后端·cursor
sp422 小时前
设计一个 Java 本地缓存系统
后端
东阳马生架构3 小时前
Dubbo源码—5.SPI机制和线程模型
后端
用户7785371836963 小时前
踩坑记录:Claude Code Router 配置 Gemini Balance API
后端
疯狂的程序猴3 小时前
移动端网页调试实战,网络请求延迟与超时问题全链路排查指南
后端
Ice__Cai3 小时前
Python 基础详解:数据类型(Data Types)—— 程序的“数据基石”
开发语言·后端·python·数据类型