結論:Class.forName加載類時,使用當前類被加載時的加載器,因為,被依賴的類也必須使用相同的加載器加載,才能正確加載被加載的類。
最近使用hutool工具加載類jar包中的類時,遇到類找不到的問題。記錄如下
自定義加載方法
class MyLoader{
/**
* 自定義加載方法
* @param jarPath
*/
public static void loadJar(String jarPath) {
File jarFile = new File(jarPath);
// 從URLClassLoader類中獲取類所在文件夾的方法,jar也可以認為是一個文件夾
Method method = null;
try {
method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException | SecurityException e1) {
e1.printStackTrace();
}
//獲取方法的訪問權限以便寫回
boolean accessible = method.isAccessible();
try {
method.setAccessible(true);
// 獲取系統(tǒng)類加載器
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URL url = jarFile.toURI().toURL();
method.invoke(classLoader, url);
System.out.println(classLoader.getURLs());
} catch (Exception e) {
e.printStackTrace();
} finally {
method.setAccessible(accessible);
}
}
//測試是否有效
public static void useLoadJar() throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
loadJar("E:\\workspace\\java\\javatest\\businessjar01\\target\\businessjar01-1.0-SNAPSHOT.jar");
Class<?> business = Class.forName("com.gz.Test");
Constructor<?> constructor = business.getConstructor();
Object o = constructor.newInstance();
}
}
本質是將jar包所在路徑加入到ClassLoader(這里是systemclassloader,與MyLoader加載器相同),而Class.forName("com.gz.Test")這行代碼所在的類MyLoader正是被系統(tǒng)類加載器加載的,所以能正確加載Test。
使用hutool的JarClassLoader.load
JarClassLoader.load(new File("E:\\workspace\\java\\javatest\\businessjar01\\target"));
Class<?> business = Class.forName("com.gz.Test");
//會找不到Test,報錯:java.lang.ClassNotFoundException: com.gz.Test
//因為:
// 當前類的加載器為系統(tǒng)類加載器,所有Class.forName加載時,會使用系統(tǒng)類加載器去加載類。
//JarClassLoader.load(new File("E:\\workspace\\java\\javatest\\businessjar01\\target"))看hutool的源碼是new JarClassLoader()了一個新的類加載器,因此添加的搜索路徑都是到新的類加載器的,
//而下面的代碼Class.forName("com.gz.Test");使用當前類加載器加載(MyLoader的加載器,即系統(tǒng)類加載器),因此,Class.forName("com.gz.Test")會使用系統(tǒng)類加載器去加載Test類,但是系統(tǒng)類加載器并
//沒有被添加Test的路徑,因為JarClassLoader.load()方法,new了一個新的類加載器,Test的路徑被添加到新的類加載器了,所以會報錯.
Class<?> business = Class.forName("com.gz.Test");
Constructor<?> constructor = business.getConstructor();
Object o = constructor.newInstance();
使用hutool的JarClassLoader.loadJar,并制定加載器
JarClassLoader.loadJar((URLClassLoader)ClassLoader.getSystemClassLoader(),new File("E:\\workspace\\java\\javatest\\businessjar01\\target"));
Class<?> business = Class.forName("com.gz.Test");
Constructor<?> constructor = business.getConstructor();
Object o = constructor.newInstance();
可以正確加載,因為制定了加載器為系統(tǒng)類加載器,與Class.forName要使用的加載器相同,所以可以找到Test
使用hutool的ClassLoaderUtil.loadClass
public static void testhutools()throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException{
Class<?> business = ClassLoaderUtil.loadClass(new File("E:\\workspace\\java\\javatest\\businessjar01\\target"), "com.gz.Test");
//Class.forName("com.gz.Test");
Constructor<?> constructor = business.getConstructor();
Object o = constructor.newInstance();
}
可以正常加載,通過分析lassLoaderUtil.loadClass源碼,可知道,雖然也是新建了一個JarClassLoader,并將jar路徑添加到這個類加載器,但是加載類時,并沒有使用Class.forName方法,而是使用類加載器的loadClass方法。自然可以把Test加載到jvm。
class.forName和loadclass的不同
可參考文章
https://blog.csdn.net/L13763338360/article/details/106014430/
class.forName()前者除了將類的.class文件加載到jvm中之外,還會對類進行解釋,執(zhí)行類中的static塊。而classLoader只干一件事情,就是將.class文件加載到jvm中,不會執(zhí)行static中的內容,只有在newInstance才會去執(zhí)行static塊。
Class.forName得到的class是已經(jīng)初始化完成的,Classloder.loaderClass得到的class是還沒有鏈接的。