2. 自定义类加载器2. 1几个重要函数2.1.1 loadClassloadClass默认实现如下:
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false);}
再看看loadClass(String name, boolean resolve)函数:
[url=][/url]
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; }}[url=][/url]
从上面代码可以明显看出,loadClass(String, boolean)函数即实现了双亲委派模型!整个大致过程如下:
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
话句话说,如果自定义类加载器,就必须重写findClass方法!
2.1.1 find ClassfindClass的默认实现如下:
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name);}
可以看出,抽象类ClassLoader的findClass函数默认是抛出异常的。而前面我们知道,loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数,因此我们必须要在loadClass这个函数里面实现将一个指定类名称转换为Class对象.
如果是是读取一个指定的名称的类为字节数组的话,这很好办。但是如何将字节数组转为Class对象呢?很简单,Java提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象啦~
2.1.1 defineClassdefineClass主要的功能是:
将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。如,假设class文件是加密过的,则需要解密后作为形参传入defineClass函数。
defineClass默认实现如下:
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null);}
2.2 函数调用过程上一节所提的函数调用过程如下:
自定义函数调用过程
2.3 简单示例首先,我们定义一个待加载的普通Java类:Test.java。放在com.huachao.cl包下:
[url=][/url]
package com.huachao.cl;public class Test { public void hello() { System.out.println("恩,是的,我是由 " + getClass().getClassLoader().getClass() + " 加载进来的"); }}[url=][/url]
注意:
如果你是直接在当前项目里面创建,待Test.java编译后,请把Test.class文件拷贝走,再将Test.java删除。因为如果Test.class存放在当前项目中,根据双亲委派模型可知,会通过sun.misc.Launcher$AppClassLoader 类加载器加载。为了让我们自定义的类加载器加载,我们把Test.class文件放入到其他目录。
在本例中,我们Test.class文件存放的目录如下:
class文件目录
接下来就是自定义我们的类加载器:
[url=][/url]
import java.io.FileInputStream;import java.lang.reflect.Method;public class Main { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } }; public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("D:/test"); Class clazz = classLoader.loadClass("com.huachao.cl.Test"); Object obj = clazz.newInstance(); Method helloMethod = clazz.getDeclaredMethod("hello", null); helloMethod.invoke(obj, null); }}[url=][/url]
最后运行结果如下:
恩,是的,我是由 class Main$MyClassLoader 加载进来的 |