18.2.3 创建并使用自定义的类加载器 JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。查阅API文档中关于ClassLoader的方法不难发现, ClassLoader中包含了大量的protected方法—这些方法都可被子类重写。
ClassLoader类关键方法 ClassLoader类有如下两个关键方法。
方法
描述
loadClass(String name, boolean resolve)
该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的 Class对象。
findClass(String name)
根据指定名称来查找类
如果需要实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,通常推荐重写findClass()方法,而不是重写loadClass()方法 。
loadClass方法的执行步骤 loadClass()方法的执行步骤如下。
用findClass(String)来检查是否已经加载类,如果已经加载则直接返回.
在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载。
调用findClass(String)方法查找类。
从上面步骤中可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托、缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更为复杂。
defineClass方法 在ClassLoader里还有一个核心方法: Class defineClass(String name, byte[] b, int off,int len),该方法负责将指定类的字节码文件(即Class文件,如Hello.class)读入字节数组b内,并把它转换为Class对象,该字节码文件可以来源于文件、网络等.defineClass方法管理JVM的许多复杂的实现,它负责将字节码分析成运行时数据结构,并校验有效性等。不过不用担心该方法是final方法,所以程序员无须重写该方法。
ClassLoader普通方法 除此之外, ClassLoader里还包含如下一些普通方法。
方法
描述
findSystemClass(String name)
从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用defineClass方法将原始字节转换成Class对象,以将该文件转换成类。
static getSystemClassLoader()
这是一个静态方法,用于返回系统类加载器。
getParent()
获取该类加载器的父类加载器。
resolveClass(Class<?> c)
链接指定的类。类加载器可以使用此方法来链接类c。读者无须理会关于此方法的太多细节.
findLoadedClass(String name)
如果此Java虚拟机已加载了名为name的类,则直接返回该类对应的Class实例,否则返回null。该方法是Java类加载缓存机制的体现。
下面程序开发了一个自定义的ClassLoader,该ClassLoader通过重写findClass()方法来实现自定义的类加载机制 。这个ClassLoader可以在加载类之前先编译该类的源文件,从而实现运行Java之前先编译该程序的目标,这样即可通过该ClassLoader直接运行Java源文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 import java.io.*;import java.lang.reflect.*;public class CompileClassLoader extends ClassLoader { private byte [] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte [] raw = new byte [(int )len]; try ( FileInputStream fin = new FileInputStream(file)) { int r = fin.read(raw); if (r != len) throw new IOException("无法读取全部文件:" + r + " != " + len); return raw; } } private boolean compile (String javaFile) throws IOException { System.out.println("CompileClassLoader:正在编译 " + javaFile + "..." ); Process p = Runtime.getRuntime().exec("javac " + javaFile); try { p.waitFor(); } catch (InterruptedException ie) { System.out.println(ie); } int ret = p.exitValue(); return ret == 0 ; } protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null ; String fileStub = name.replace("." , "/" ); String javaFilename = fileStub + ".java" ; String classFilename = fileStub + ".class" ; File javaFile = new File(javaFilename); File classFile = new File(classFilename); if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { if (!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException( "ClassNotFoundExcetpion:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } if (classFile.exists()) { try { byte [] raw = getBytes(classFilename); clazz = defineClass(name,raw,0 ,raw.length); } catch (IOException ie) { ie.printStackTrace(); } } if (clazz == null ) { throw new ClassNotFoundException(name); } return clazz; } public static void main (String[] args) throws Exception { if (args.length < 1 ) { System.out.println("缺少目标类,请按如下格式运行Java源文件:" ); System.out.println("java CompileClassLoader ClassName" ); } String progClass = args[0 ]; String[] progArgs = new String[args.length-1 ]; System.arraycopy(args , 1 , progArgs , 0 , progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); Class<?> clazz = ccl.loadClass(progClass); Method main = clazz.getMethod("main" , (new String[0 ]).getClass()); Object[] argsArray = {progArgs}; main.invoke(null ,argsArray); } }
上面程序中的重写了findClass()方法,通过重写该方法就可以实现自定义的类加载机制。在本类的findClass()方法中先检查需要加载类的Class文件是否存在,如果不存在则先编译源文件,再调用ClassLoader的defineClass()方法来加载这个Class文件,并生成相应的Class对象。 接下来可以随意提供一个简单的主类,该主类无须编译就可以使用上面的CompileClassLoader来运行它。
1 2 3 4 5 6 7 8 9 10 public class Hello { public static void main (String[] args) { for (String arg : args) { System.out.println("运行Hello的参数:" + arg); } } }
无须编译该Hello.java,可以直接使用如下命令来运行该Hello.java程序
1 java CompileClassLoader Hello 这是命令行参数
运行结果如下:
1 2 3 G:\Desktop \codes \18\18.2>java CompileClassLoader Hello 这是命令行参数 CompileClassLoader :正在编译 Hello.java ...运行Hello 的参数:这是命令行参数
本示例程序提供的类加载器功能比较简单,仅仅提供了在运行之前先编译Java源文件的功能。
自定义类加载器可以实现什么功能 实际上,使用自定义的类加载器,可以实现如下常见功能。
执行代码前自动验证数字签名
根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译*.class文件。
根据用户需求来动态地加载类。
根据应用需求把其他数据以字节码的形式加载到应用中。
原文链接: 18.2.3 创建并使用自定义的类加载器