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插件,对应配置如下

<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,配置内容为

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

对应代码如下:

// 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 对应代码如下

// 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 配置内容为

loader.path=loader.path=BOOT-INF/classes,lib,resources

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

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的加载路径
[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依赖

<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>

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

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -jar hebert-1.0.0-SNAPSHOT.jar
<think>好的,我现在需要解决用户在使用JDK17和Spring Boot项目打时,target目录下缺少BOOT-INF/lib目录的问题。首先,我要回忆一下Spring Boot的打机制。根据引用[3],spring-boot-maven-plugin负责打,生成的可执行jar的结构BOOT-INF/classes和BOOT-INF/lib,这些目录存放应用和依赖库。用户的问题是没有生成lib目录,导致依赖缺失。 接下来,我要考虑可能的原因。首先,检查插件配置是否正确。用户可能没有正确配置spring-boot-maven-plugin,或者在pom.xml中遗漏了必要的配置。例如,可能需要确认插件版本是否兼容JDK17,因为旧版本可能不支持新JDK。另外,引用[3]提到MANIFEST.MF中的Main-ClassJarLauncher,如果插件没有正确执行,可能导致结构不正确。 其次,依赖作用域的问题。如果某些依赖被声明为provided或test,这些依赖不会被含进BOOT-INF/lib中。用户需要检查pom.xml中的依赖是否有错误的作用域设置。 然后,JDK版本兼容性也是一个因素。JDK17可能引入了一些变化,导致插件在打时行为不同。需要确认使用Spring Boot版本是否支持JDK17,可能需要升级到较新的版本,比如Spring Boot 3.x,因为官方对JDK17的支持更好。 另外,构建过程的问题也不能排除。用户是否执行了正确的Maven命令,比如mvn clean package?有时候残留的构建文件可能导致问题,清理后再重新构建可能会有帮助。 解决方案方面,首先建议用户检查pom.xml中的插件配置,确保使用的是兼容的版本。例如,Spring Boot 2.5+支持JDK17,但可能需要具体版本。其次,检查依赖的作用域,确保没有不必要的provided或test。然后,升级Spring Boot和插件版本到支持JDK17的版本,比如Spring Boot 3.0+。同时,清理构建目录并重新打使用正确的命令。最后,检查构建日志,查看是否有错误或警告信息,这些可能指出具体问题所在。 可能还需要参考引用[2]中的内容,了解插件如何过滤和打依赖到BOOT-INF/lib目录。如果插件配置正确,但依赖仍然没有被含进去,可能需要检查Maven的依赖解析是否正常,是否有冲突或缺失的依赖。 总结下来,可能的原因括插件配置错误、依赖作用域问题、版本不兼容以及构建过程中的问题。需要逐一排查这些可能性,并验证解决方案是否有效。</think>### 问题原因分析 在使用 JDK17 与 Spring Boot 项目打时,若生成的 `target` 目录下的可执行 jar 中缺少 `BOOT-INF/lib/` 目录,可能是以下原因导致的: 1. **插件配置缺失或版本不兼容** Spring Boot 项目依赖 `spring-boot-maven-plugin` 插件生成可执行 jar 。若未正确配置该插件,或插件版本与 JDK17 不兼容,会导致依赖库未打到 `BOOT-INF/lib/` 目录[^3]。 2. **依赖作用域(Scope)错误** 如果项目依赖声明中误将作用域设置为 `provided` 或 `test`,这些依赖不会被含在最终的可执行 jar 中[^3]。 3. **JDK17 兼容性问题** Spring Boot 2.x 对 JDK17 的支持有限,可能需要升级到 Spring Boot 3.x(默认支持 JDK17)[^3]。 4. **构建过程异常** 未执行完整的 `mvn clean package` 命令,或构建过程中存在缓存干扰。 --- ### 解决方案 #### 1. 检查插件配置 确保 `pom.xml` 中已正确配置 `spring-boot-maven-plugin`,并指定兼容版本: ```xml <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.0.0+</version> <!-- 支持 JDK17 的版本 --> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` #### 2. 验证依赖作用域 检查所有依赖的作用域是否为 `compile`(默认值),避免使用 `provided` 或 `test`: ```xml <dependency> <groupId>com.example</groupId> <artifactId>example-library</artifactId> <version>1.0.0</version> <!-- 默认作用域为 compile --> </dependency> ``` #### 3. 升级 Spring Boot 版本 若使用 Spring Boot 2.x,建议升级至 Spring Boot 3.x(需同步调整 JDK17 兼容性配置): ```xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.0</version> <!-- 支持 JDK17 --> </parent> ``` #### 4. 清理并重新构建 执行完整的构建命令以清除缓存: ```bash mvn clean package -DskipTests ``` #### 5. 检查构建日志 查看 Maven 构建日志中是否有以下关键输出,确认插件已正确执行: ``` [INFO] --- spring-boot-maven-plugin:3.0.0:repackage (default) @ your-project --- [INFO] Replacing main artifact with repackaged archive ``` --- ### 验证结果 成功打后,可通过以下命令检查 jar 结构: ```bash jar tf target/your-project.jar | grep BOOT-INF/lib/ ``` 若输出含依赖的 jar 文件名,则问题已解决。 --- ### 扩展说明 Spring Boot 的打机制通过 `JarLauncher` 加载 `BOOT-INF/lib/` 中的依赖,若目录缺失会导致加载失败[^1]。使用 `spring-boot-maven-plugin` 时,插件会自动将依赖复制到 `BOOT-INF/lib/`,并生成正确的 `MANIFEST.MF` 文件[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值