今天尝试集成一个第三方SDK,在IDE里运行正常,放到服务器上却遇到了NPE,反编译一看,原来在这一行:
String path = Test.class.getClassLoader().getResource("").getPath(); // Test.class.getClassLoader().getResource("") == null
检查一番,原来是classpath中只有jar包,没有目录。 Test.class.getClassLoader().getResource("")
返回classpath中第一个目录的路径。如果没有目录,返回null。
写程序验证,果然如此。
代码1 验证程序
import java.net.URL;public class A {public static void main(String... args) {checkResource("");checkResource("/resources/a.txt");checkResource("resources/a.txt");}public static void checkResource(String name) {System.out.println("resource:" + name);URL url = A.class.getClassLoader().getResource(name);System.out.println(url);if (url != null) {System.out.println(url.getPath());}System.out.println("---------------------");} }
$ javac A.java; jar cvf A.jar A.class resources/ ; java -cp A.jar A # 程序输出 resource: null --------------------- resource:/resources/a.txt null --------------------- resource:resources/a.txt jar:file:/root/A.jar!/resources/a.txt file:/root/A.jar!/resources/a.txt ---------------------
查看JDK11源代码:
代码2 ClassLoader
public URL getResource(String name) {URL url;if (parent != null) {url = parent.getResource(name);} else {url = getBootstrapResource(name);}if (url == null) {url = findResource(name);}return url; }
ClassLoader是一个抽象类,实际使用的是URLClassLoader。在JDK11里,URLClassLoader将操作委托给URLClassPath。URLClassPath内部使用3个加载器:Loader、FileLoader和JarLoader,分别从远程(HTTP)、本地目录和jar包中加载资源。
JarLoader使用JarFile读取jar文件。JarFile派生自ZipFile,加载jar文件项的代码是
JNIEXPORT jlong JNICALL Java_java_util_zip_ZipFile_getEntry(JNIEnv *env, jclass cls, jlong zfile,jbyteArray name, jboolean addSlash) { #define MAXNAME 1024jzfile *zip = jlong_to_ptr(zfile);jsize ulen = (*env)->GetArrayLength(env, name);char buf[MAXNAME+2], *path;jzentry *ze;if (ulen > MAXNAME) {path = malloc(ulen + 2);if (path == 0) {JNU_ThrowOutOfMemoryError(env, 0);return 0;}} else {path = buf;}(*env)->GetByteArrayRegion(env, name, 0, ulen, (jbyte *)path);path[ulen] = '\0';if (addSlash == JNI_FALSE) {ze = ZIP_GetEntry(zip, path, 0);} else {ze = ZIP_GetEntry(zip, path, (jint)ulen);}if (path != buf) {free(path);}return ptr_to_jlong(ze); }
注意到 path[ulen] = '\0'
这一行,当 name == ""
时,JarLoader无法加载资源。
FileLoader使用 file = new File(dir, name.replace('/', File.separatorChar))
加载资源,当 name == ""
时, new File("")
返回当前目录。因此当classpath中只有jar包时, Test.class.getClassLoader().getResource("") == null
,而如果classpath包含目录,Test.class.getClassLoader().getResource("") == null
返回第一个目录路径。