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到底从哪里加载
- 使用jps命令获取进程ID
- 使用 java -jar .\arthas-boot.jar 进程ID
- 使用 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