?今天嘗試集成一個第三方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
?返回第一個目錄路徑。