上图为JDK 8中ClassLoader的族谱,可见除了总所周知的AppClassLoader和ExtClassLoader外,JDK中还有很多其它ClassLoader,既然这么多ClassLoader存在,也就不那么神秘了,那么如何自定义ClassLoader了?最简单的方式当然是继承现有的ClassLoader实现类,避免重复发明轮子,所以我们先了解一下ClassLoader类的实现。
findClass方法:这是自定义class loader类必须覆盖的方法,用于告诉class loader到哪里去加载类,比如某个目录或者JAR URL等。参数name为要加载的类全名,如java.lang.String。该方法作为类加载的步骤之一被loadClass()方法调用。
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
loadClass方法:这是classloader加载类的入口方法,觉得方法实现代码写得很够清晰就全贴出来了,附加一张简单的活动图辅助说明方法逻辑。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
getParent方法:用于获取class loader的parent,没有返回null。
public final ClassLoader getParent()
findLoadedClass方法:返回已经加载的类。该方法直接调用本地方法实现。
protected final Class<?> findLoadedClass(String name)
resolveClass方法:用于连接一个Class,如果已经连接则什么都不做。该方法直接调用本地方法实现。
protected final void resolveClass(Class<?> c)
defineClass方法:将字节码转换为Class实例,即加载.class文件后需要创建一个对应的java.lang.Class对象用于描述该Class。该方法直接调用本地方法实现。
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
借一个图,理解更清晰点:
根据以上分析,自定义一个class loader 只需要集成ClassLoader类并覆盖findClass方法即可,我们也自己搞一个看看。
Car接口:
package com.stevex.app.classloader; public interface Car { public void run(); }
BMW类:
package com.stevex.app.classloader; public class BMW implements Car { public void run() { System.out.println("BMW"); } }
SteveClassLoader类:自定义的class loader类
package com.stevex.app.classloader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class SteveClassLoader extends ClassLoader { @Override public Class<?> findClass(String name) { byte[] bt = loadClassData(name); return defineClass(name, bt, 0, bt.length); } private byte[] loadClassData(String className) { // read class InputStream is = getClass().getClassLoader().getResourceAsStream( className.replace(".", "/") + ".class"); ByteArrayOutputStream byteSt = new ByteArrayOutputStream(); // write into byte int len = 0; try { while ((len = is.read()) != -1) { byteSt.write(len); } } catch (IOException e) { e.printStackTrace(); } // convert into byte array return byteSt.toByteArray(); } }
SteveClassLoaderTest类:测试类,SteveClassLoader默认构造函数会设置System class loader为parent,测试时执行loadClass方法会发现BMW类是委托AppClassLoader加载的,所以AppClassLoader可以访问到,不会出错;
执行findClass2方法就会发生错误,因为我们直接使用SteveClassLoader加载BMW类,而不是委托给parent加载,根据class loader命名空间规则(简单来讲,每个class loader 都有自己唯一的命名空间,每个class loader 只能访问自己命名空间中的类,一个class可以被不同的class loader重复加载,但同一个class只能被同一个class loader加载一次,如果一个类是委托parent加载的,那么加载后,这个类就类似共享的,parent和child都可以访问到这个类,因为parent是不会委托child加载类的,所以child加载的类parent访问不到),子加载器的命名空间包含了parent加载的所有类,反过来则不成立,SteveClassLoaderTest类是AppClassLoader加载的,所以其看不见由SteveClassLoader加载的BMW类,但Car接口是可以访问的,所以赋给Car类型不会出错。
在findClass1方法中,我们直接使用反射调用run方法就没事了。
package com.stevex.app.classloader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class SteveClassLoaderTest { public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException { SteveClassLoader loader = new SteveClassLoader(); loadClass(loader); findClass1(loader); //findClass2(loader); } private static void findClass1(SteveClassLoader loader) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Class<?> c = loader.findClass("com.stevex.app.classloader.BMW"); System.out.println("Loaded by :" + c.getClassLoader()); Object ob = c.newInstance(); Method md = c.getMethod("run"); md.invoke(ob); } private static void loadClass(SteveClassLoader loader) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class<?> c = loader.loadClass("com.stevex.app.classloader.BMW"); System.out.println("Loaded by :" + c.getClassLoader()); Car car = (Car) c.newInstance(); car.run(); BMW bmw = (BMW) c.newInstance(); bmw.run(); } private static void findClass2(SteveClassLoader loader) throws InstantiationException, IllegalAccessException { Class<?> c = loader.findClass("com.stevex.app.classloader.BMW"); System.out.println("Loaded by :" + c.getClassLoader()); Car car = (Car) c.newInstance(); car.run(); BMW bmw = (BMW) c.newInstance(); bmw.run(); } }