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 创建并使用自定义的类加载器