詳細的原理就不多說了,網上一大把, 但是, 看了很多很多, 即使看了jdk 源碼, 說了羅里吧嗦, 還是不很明白:
到底如何正確自定義ClassLoader, 需要注意什么
ExtClassLoader 是什么鬼
自定義ClassLoader具體是如何加載 類的。。
直接上代碼:
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; import java.util.Date; import com.lk.AbcBean; public class ClassLoaderLK extends ClassLoader { /** * @param args */ public static void main(String[] args) { // this.class.getSystemClassLoader(); String ext = "java.ext.dirs"; System.out.println("java.ext.dirs :\n" + System.getProperty(ext)); String cp = "java.class.path"; System.out.println("java.class.path :\n" + System.getProperty(cp)); ClassLoader currentClassloader = ClassLoaderLK.class.getClassLoader(); String pp = "d:\\testcl\\"; ClassLoaderLK cl = new ClassLoaderLK(currentClassloader, pp); System.out.println(); System.out.println("currentClassloader is " + currentClassloader); System.out.println(); String name = "com.lk.AbcBean.class"; name = "com.lk.AbcBean"; try { Class<?> loadClass = cl.loadClass(name); Object object = loadClass.newInstance(); // AbcBean ss = (AbcBean) object; // 無法轉換的 (1) // ss.greeting(); (1) System.out.println(); System.out.println(" invoke some method !"); System.out.println(); Method method = loadClass.getMethod("greeting"); method.invoke(object); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private ClassLoader parent = null; // parent classloader private String path; public ClassLoaderLK(ClassLoader parent, String path) { super(parent); this.parent = parent; // 這樣做其實是無用的 this.path = path; } public ClassLoaderLK(String path) { this.path = path; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // return super.loadClass(name); Class<?> cls = findLoadedClass(name); if (cls == null) { // cls = getSystemClassLoader().loadClass(name); (2)// SystemClassLoader 會從classpath下加載 // if (cls == null) {(2) // 默認情況下, 當前cl的parent是 SystemClassLoader, // 而當前cl的parent的parent 才是ExtClassLoader ClassLoader parent2 = getParent().getParent(); // System.out.println("Classloader is : " + parent2); try { System.out.println("try to use ExtClassLoader to load class : " + name); cls = parent2.loadClass(name); } catch (ClassNotFoundException e) { System.out.println("ExtClassLoader.loadClass :" + name + " Failed"); } // }(2) if (cls == null) { System.out.println("try to ClassLoaderLK load class : " + name); cls = findClass(name); if (cls == null) { System.out.println("ClassLoaderLK.loadClass :" + name + " Failed"); } else { System.out.println("ClassLoaderLK.loadClass :" + name + " Successful"); } } else { System.out.println("ExtClassLoader.loadClass :" + name + " Successful"); } } return cls; } @Override @SuppressWarnings("rawtypes") protected Class<?> findClass(String name) throws ClassNotFoundException { // return super.findClass(name); System.out.println( "try findClass " + name); InputStream is = null; Class class1 = null; try { String classPath = name.replace(".", "\\") + ".class"; // String[] fqnArr = name.split("\\."); // split("."); 是不行的, 必須split("\\.") // if (fqnArr == null || fqnArr.length == 0) { // System.out.println("ClassLoaderLK.findClass()"); // fqnArr = name.split("\\."); // } else { // System.out.println( name + fqnArr.length); // } String classFile = path + classPath; byte[] data = getClassFileBytes(classFile ); class1 = defineClass(name, data, 0, data.length); if (class1 == null) { System.out.println("ClassLoaderLK.findClass() ERR "); throw new ClassFormatError(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } return class1; } private byte[] getClassFileBytes(String classFile) throws Exception { FileInputStream fis = new FileInputStream(classFile ); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { int i = fileC.read(buffer); if (i == 0 || i == -1) { break; } buffer.flip(); outC.write(buffer); buffer.clear(); } fis.close(); return baos.toByteArray(); } }
隨便的一個java 類, 簡單起見,就寫一個bean吧
package com.lk; import java.util.Date; public class AbcBean { @Override public String toString() { return "AbcBean [name=" + name + ", age=" + age + "]"; } String name; int age; Date birthDay; public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void greeting() { System.out.println("AbcBean.greeting()"); } }
直接執行,結果:
currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0 try to use ExtClassLoader to load class : com.lk.AbcBean ExtClassLoader.loadClass :com.lk.AbcBean Failed try to ClassLoaderLK load class : com.lk.AbcBean try findClass com.lk.AbcBean java.io.FileNotFoundException: d:\testcl\com\lk\AbcBean.class (系統找不到指定的路徑。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:138) at java.io.FileInputStream.<init>(FileInputStream.java:97) at ClassLoaderLK.getClassFileBytes(ClassLoaderLK.java:172) at ClassLoaderLK.findClass(ClassLoaderLK.java:145) at ClassLoaderLK.loadClass(ClassLoaderLK.java:112) at ClassLoaderLK.main(ClassLoaderLK.java:38) Exception in thread "main" java.lang.NullPointerException at ClassLoaderLK.main(ClassLoaderLK.java:40) ClassLoaderLK.loadClass :com.lk.AbcBean Failed
將com.lk 目錄全部復制到 d:\\testcl\\ 下,
currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0 try to use ExtClassLoader to load class : com.lk.AbcBean ExtClassLoader.loadClass :com.lk.AbcBean Failed try to ClassLoaderLK load class : com.lk.AbcBean try findClass com.lk.AbcBean try to use ExtClassLoader to load class : java.lang.Object ExtClassLoader.loadClass :java.lang.Object Successful ClassLoaderLK.loadClass :com.lk.AbcBean Successful invoke some method ! try to use ExtClassLoader to load class : java.lang.String ExtClassLoader.loadClass :java.lang.String Successful try to use ExtClassLoader to load class : java.lang.System ExtClassLoader.loadClass :java.lang.System Successful try to use ExtClassLoader to load class : java.io.PrintStream ExtClassLoader.loadClass :java.io.PrintStream Successful AbcBean.greeting()
將AbcBean打包成 jar 放置到 jdk 下的jre 的ext目錄 ( 打包成 zip 也是可行的! 但是rar是不行的!!! why ? 估計zip和jar都是使用的java 的zip流, 而rar是后面產生的新格式,故沒有被支持。另外, 僅僅拷貝class 過去也是不行的! )
執行結果:
currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0 try to use ExtClassLoader to load class : com.lk.AbcBean ExtClassLoader.loadClass :com.lk.AbcBean Successful invoke some method ! AbcBean.greeting()
可見ExtClassLoader 是如何作用了的吧!!
總結,
1 從 invoke some method 前后的日志,可見 類加載 的大致過程。
2 代碼中 (1), 的部分是注釋了的, 因為 不同類加載器加載的類是 不能直接cast的。。 但把(1),(2) 同時解開注釋, 又可以了, 這是因為他們都是使用的系統類加載器, 自定義的類加載器相當於沒有生效。。( 這個當然不是我們需要的結果。)
3 loadClass, findClass 兩個方法的復寫是必須的。 上面代碼中的loadClass 的寫法其實有點問題, 參照classloader 源碼, 應該還需要一步: parent加載不上了, 使用bootstrap 加載, 不過感覺一般應該是用不上的—— 誰需要去替換 jdk 的rt.jar 的類 ??
4 ExtClassLoader 是去加載 jdk 下 jre ext 目錄的類似jar 的文件—— 后綴是不是jar 不要緊, 內容是jar就行了。