博客地址:liuzhengyangjava
ClassLoader
先抛出几个问题git
回答这些问题仍是要参考JDK的代码实现,另外还要依靠Java语言规范和Java 虚拟机规范github
本篇文章用类来泛指类和接口bootstrap
类加载器是用于加载Class的机制,Class能够以文件的形式或者是二进制流的形式存在,通常 按照最多见的Class文件来称呼。数据结构
<!-- -->ide
ClassLoader负责加载类,java.lang.ClassLoader
类是一个抽象类, 能够经过一个类的二进制名称,来定位类在哪里,并生成class
的数据定义。 最多见的策略是将类的名称转换成文件名并从文件系统中读取Class文件
.测试
每一个Class
对象都会有指向定义它的ClassLoader
的引用,经过Class#getClassLoader()
能够得到。url
类的二进制名(binary name)是Java语言规范规定的,常见的有:spa
java.lang.String test.loader.Test$Test2 // 静态内部类
Class文件能够存在jar包中,能够以目录形式存放。设计
Java的类加载结构有bootstrap class loader用来加载$JAVA_HOME/jre/lib下载的 rt.jar中的文件,其中是Java的核心类.Bootstrap classloader是JVM中的实现, 若是要在ClassLoader中表示其为父类,用null表示。 另外有ExtClassLoader加载lib/ext文件夹下的jar包 AppClassLoader是用来加载ClassPath目录下的jar包和Class文件。 常说这三者是父类关系,并非Java中的集成关系,而是ClassLoader中定义的 parent.
ClassLoader文件在第一次加载类的时候会先委托其父加载器加载,若是加载失败再本身加载。 这样,一些关键的类,如String等就不会遭到篡改。可是在J2EE中,一个Web容器下 可能有多个应用,每一个应用加载时有子类优先的需求,这时就须要覆盖默认的逻辑。 代码逻辑在ClassLoader类的loadClass(String name, boolean resolve)
方法中.ClassLoader#loadClass
调用的是ClassLoader#loadClass(name, false)
resolve表示的是是否进行连接步骤。 去掉一些不相关代码,loadClass方法的逻辑以下
类加载锁 synchronized (getClassLoadingLock(name)) { // 查看是否已经被加载过,会调用一个native方法判断 Class<?> c = findLoadedClass(name); if (c == null) { // 若是没加载过,则会进入加载过程 try { // 若是parent不是null,则说明是Java中的类加载器 if (parent != null) { // 调用parent的loadClass方法递归向上加载 c = parent.loadClass(name, false); } else { // 若是是null,说明是Bootstrap class loader, // 则使用Bootstrap加载器加载 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. // 调用findClass,会找到class的字节流而后使用 // defineClass()来定义类 c = findClass(name); } } if (resolve) { // 若是要连接,则进行连接,其中有验证、准备和符号引用解析过程 resolveClass(c); } return c; }
另外,Class的静态方法Class.forName(className)
也可以完成类的加载工做, 返回一个Class对象。会对类进行初始化,使用调用Class.forName的方法所在的类的类加载器 来加载。 Class.forName(className, initialize, loader)
方法会经过参数控制由是否进行初始化和由哪一个类加载器加载。
咱们在写程序时,常常会在classpath下放置一些配置文件,在运行时读取配置文件的内容 能够经过Class.getResource(), 例如
Test.class.getResourceAsStream("/config.properties") 这个方法会委托Test类的加载器来进行加载资源
当咱们判断两个Class对象是不是相等时,或判断是不是集成关系时,须要看它们的类加载器是不是同一个。 类加载器和类,组成了Class对象的标识。 测试代码
自定义的类加载器,修改默认的委托机制。 private static class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls) { super(urls); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return defineClass(name); } } public static class Class1 { static { System.out.println("Initialize class 1"); Class2.doSay(); Class<Class2> class2Class = Class2.class; System.out.println("Class2 Loader is " + class2Class.getClassLoader()); } public static void say() { } } public static class Class2 { static { System.out.println("Initialize Class2"); } public static void doSay() { System.out.println("Say"); } } public static void main(String[] args) throws Exception { URL path = ClassLoaderTest.class.getResource("/"); URL rtPath = Object.class.getResource("/"); MyClassLoader myClassLoader = new MyClassLoader(new URL[]{path, rtPath}); Class<?> class1 = myClassLoader.loadClass("classloader.ClassLoaderTest$Class1"); Class<?> aClass = Class.forName("classloader.ClassLoaderTest$Class1", true, myClassLoader); Class<?> aClass2 = Class.forName("classloader.ClassLoaderTest$Class1"); System.out.println(class1); System.out.println("Class 1 classLoader is " + class1.getClassLoader()); System.out.println(aClass); }
可以看出,类A触发类B的初始化时,会用类A的加载器去加载。