0%

Spring-Boot 1.4.0-RELEASE略显不负责任的版本----UrlClassLoader问题追查

场景描述:问题的发生来源一个工作中的问题,简要描述是在一个多module的maven project中,job module依赖server module完成工作,在intelliJ中执行良好,但是用maven package打包的时候,就会报找不到server module中对应的类。

于是乎,楼主开始了问题的排查。

怀疑是maven的锅
首先,怀疑是本地maven问题,尝试了各种解决办法,如maven -U, 删除本地maven库中server module的相应版本等。

察觉到问题关节
一通忙碌之后,楼主注意到报错的细节,不是找不到jar包,也不是server module的某一个类找不到,而是无论引用server module中的哪个类,package的时候都会报找不到响应的类。于是开始怀疑是不是server module的jar包有什么与众不同。

对比问题jar与普通jar的不同之处
那么楼主便将server module的jar包unzip开,查看细节

有问题jar的结构
其中问题jar的包名是com.admin.server
jar–
--BOOT-INF
--META-INF
--org
--springframework
--*

普通jar结构
普通jar的包名是com.admin.job
jar–
--META-INF
--com
--admin
--job
\
.class

从中楼主比较出了不同,问题jar的主目录下并没有自己包名(com.admin.server)的目录,而普通的可依赖jar则有,那么是谁会修改这个jar的目录结构呢?

spring-boot接锅
我们仔细观察可以发现问题jar的主目录下多了一个BOOT-INF,于是把矛头指向了spring-boot,在观察一下server module的pom.xml,楼主感觉真実はいつも一つ (真相只有一个)



${project.artifactId}


org.springframework.boot
spring-boot-maven-plugin
1.4.0-RELEASE



repackage





从上述代码我们可以看出spring-boot做了个maven的plugin,在repackage时期的时候接手了打包过程。所以有理由怀疑这次问题是由于升级到了spring-boot 1.4.0-RELEASE引起的。

经过简单的搜索,便找到了stack overflow上得解答以及spring-boot自己的文档说明,spring-boot文档

然而intresting
找到了问题后,稍一兴奋,便有一个问题萦绕在楼主心头,久久不能散去,classloader究竟是怎样在jar文件中查找类的呢?

阅读了一会URLClassLoader的源码,感觉不得要领,于是开始了写测试用例调试URLClassLoader的过程。

将上述问题jar作为全路径输入,查找其中的类名,debug走起


@Test
public void test() throws MalformedURLException {
String pathname = “//target/admin-server-exec.jar”;
try {
Class<?> aClass = new URLClassLoader(new URL[]{FileURL.makeURL(pathname)}, null)
.loadClass(“com.admin.server.web.controller.AdminController.class”);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

不断跟踪深入,直到发现一处

esource getResource(String var1, boolean var2) {
if(this.metaIndex != null && !this.metaIndex.mayContain(var1)) {
return null;
} else {
try {
this.ensureOpen();
} catch (IOException var5) {
throw new InternalError(var5);
}

JarEntry var3 = this.jar.getJarEntry(var1);

以及getJarEntry的实现:

public ZipEntry getEntry(String name) {
if (name == null) {
throw new NullPointerException(“name”);
}
long jzentry = 0;
synchronized (this) {
ensureOpen();
jzentry = getEntry(jzfile, zc.getBytes(name), true);
if (jzentry != 0) {
ZipEntry ze = getZipEntry(name, jzentry);
freeEntry(jzfile, jzentry);
return ze;
}
}
return null;
}

那么一切豁然开朗,从jar中查找一个类,最后的实现便是查找待寻找的名字是否在zip的相应位置,由此可见,被spring-boot重新打包之后的jar中找不到相应的类非常合理。