全盘负责。就是当类加载器负责加载某个Class时,该Class所依赖的和所引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入
父类委托。所谓父类委托,就是先让父类加载器试图加载该Class。只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序需要使用时,先从缓存中搜索该Class,当缓存中不存在该Class,系统菜才读取该类对应的二进制数据,并将其转为Class对象,存入缓存区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
类加载器加载Class大致经过8个步骤
检测此Class是否载入过(即缓存区中是否有此Class),如果有则直接进入第8步,否者接着第2步
如果父类加载器(父类 gt+ 加载器,要么Parent一定是跟类加载器,要么本身就是跟类加载器)不存在,则调到第4步执行
请求使用父类加载器载入目标类,如果成功载入调到第8步
请求使用跟类加载器来载入目标类
当前类加载器尝试寻找Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,如果找不到执行第7步
从文件中载入Class,成功载入调到第8步
抛出ClassNotFoundException异常
返回对应的java.lang.Class对象
其中,第5、6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程。
2.3 创建并使用自定义的类加载器JVM除跟类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过拓展ClassLoader的子类,并重写该ClassLoader所包含的方法实现自定义的类加载器。ClassLoader有如下两个关键方法。
loadClass(String name,boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类的class对象
findClass(String name):根据指定名称来查找类
如果需要是实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,通常推荐重写findClass()方法而不是loadClass()方法。
classLoader()方法的执行步骤:
findLoadedClass():来检查是否加载类,如果加载直接返回。
父类加载器上调用loadClass()方法。如果父类加载器为null,则使用跟类加载器加载。
调用findClass(String)方法查找类
从上面看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更为复杂。
ClassLoader的一些方法:
Class defineClass(String name,byte[] b,int off,int len):负责将字节码分析成运行时数据结构,并检验有效性
findSystemClass(String name):从本地文件系统装入文件。
static getSystemClassLoader():返回系统类加载器
getParent():获取该类加载器的父类加载器
resolveClass(Class<?> c):链接指定的类
findClassLoader(String name):如果加载器加载了名为name的类,则返回该类对用的Class实例,否则返回null。该方法是类加载缓存机制的体现。
下面程序开发了一个自定义的ClassLoader。该classLoader通过重写findClass()方法来实现自定义的类加载机制。这个ClassLoader可以在加载类之前先编译该类的源文件,从而实现运行Java之前先编译该程序的目标,这样即可通过该classLoader运行Java源文件。
package com.gdut.basic; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { private byte[] getBytes(String fileName) { File file = new File(fileName); Long len = file.length(); byte[] raw = new byte[(int)len]; FileInputStream fin = new FileInputStream(file); //一次读取class文件的二进制数据 int r = fin.read(raw); if(r != len) { throw new IOException("无法读取文件"+r+"!="+raw); return null; } } private boolean compile(String javaFile) throws IOException { System.out.println("正在编译"+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; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; String findStub = name.replace(".", "http://www.likecs.com/"); String javaFileName = findStub+".java"; String classFileName = findStub+".class"; File javaFile = new File(javaFileName); File classFile = new File(classFileName); //但指定Java源文件存在,class文件不存在,或者Java源文件的修改时间比class文件修改的时间更晚时,重新编译 if(javaFile.exists() && classFile.exists() || javaFile.lastModified() > classFile.lastModified()) { try { if(!compile(javaFileName)|| !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundExcetion"+javaFileName); } }catch(IOException ie) { ie.printStackTrace(); } } if(classFile.exists()) { byte[] raw = getBytes(classFileName); clazz = defineClass(name,raw,0,raw.length); } //如果clazz为null,表明加载失败,则抛出异常 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); } }