ClassLoader

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.

“双亲” 原文是 parent delegate,在 Java 文档原文的意思:将加载类的任务委派给上一级类加载器,而双亲的翻译却让人摸不着头脑。

public abstract class ClassLoader {

    上级类加载器
    private final ClassLoader parent;

    protected Class<?> loadClass(String name, boolean resolve) {

        1、检查是否加载过
        Class<?> c = findLoadedClass(name);

        if (c == null) {
            try {
                if (parent != null) {
                    2.1 递归委派给上级类加载器
                    c = parent.loadClass(name, false);
                } else {
                    2.2 委派给顶级启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                2.3 上级类加载器无法加载,抛出异常
            }

            3. 交给自己加载,findClass() 我后文再说
            if (c == null) {
                c = findClass(name);
            }
        }
        return c;
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
}

Class.forName

Class.forName(xxx.xx.xx)返回的是一个类
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,
也就是说JVM会执行该类的静态代码段你要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。

public class StaticClass {
    static {
        System.out.println("被加载啦");
    }

    public StaticClass (){
        System.out.println("构造方法");
    }
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class myStatic = Class.forName("com.class_load.StaticClass");
        myStatic.newInstance();


        Class myStatic1 = Class.forName("com.class_load.StaticClass");
        myStatic1.newInstance();

        Class myStatic2 = StaticClass.class;
        myStatic2.newInstance();
    }
}

输出是:

被加载啦
构造方法
构造方法
构造方法
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class myStatic1 = Class.forName("com.class_load.StaticClass");
    }
}

输出:
被加载啦
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class myStatic = StaticClass.class;
    }
}

执行后是没有任何输出的。

Java 还提供了另一种方法来生成对 Class 对象的引用,即使用类字面常量。形如:xxx.class 的形式。但是这种形式创建 Class 对象引用时,不会自动初始化 Class 对象。初始化被延迟到了对静态方法或者非常数静态域首次引用时才执行。

public class Initable {  
    static final int staticFinal = 47;  
    static final int staticFianl2 = ClassInitialization.rand.nextInt(1000);  
    static {  
        System.out.println("Initializing Initable");  
    }  
}
public class Initable2 {  
    static int staticNonFinal = 147;  
    static {  
        System.out.println("Initializing Initable2");  
    }  
}
public class Initable3 {  
    static int staticNonFinal = 74;  
    static {  
        System.out.println("Initializing Initable3");  
    }  
}
public class ClassInitialization {  
    public static Random rand = new Random(47);  
    public static void main(String[] args) throws ClassNotFoundException {  
        Class initable = Initable.class;  
        System.out.println("after creating Initable ref");  
        //Does not trigger initialization  
        System.out.println(Initable.staticFinal);  
        //Does trigger initialization  
        System.out.println(Initable.staticFianl2);  
        //Does trigger initialization  
        System.out.println(Initable2.staticNonFinal);  
        Class initable3 = Class.forName("chapter13.t2.Initable3");  
        System.out.println("after creating Initable3 ref");  
        System.out.println(Initable3.staticNonFinal);  
    }  
}

执行输出结果:

after creating Initable ref  
47  
Initializing Initable  
258  
Initializing Initable2  
147  
Initializing Initable3  
after creating Initable3 ref  
74  

从对 initable 引用的创建中可以看到,仅使用 .class 语法来获取对类的引用不会发生初始化。但是 Class.forName() 立即就进行了初始化。如果一个 static final 值是“编译期常量”,就像 Initable.staticFinal 那样,那么这个值不需要对 Initable 类进行初始化就可以被读取。但是如果只是将一个域设置为 static 和 final 的,还不足以确保这种行为,例如 Initable.staticFianl2 的访问将强制进行类型的初始化,因为他不是一个编译器常量。

classLoader.load 没有执行初始化

Class.forName(className)装载的class已经被实例化,classLoader.loadClass().则只是将信息装载给JVM。在JDBC中 Class.forName(“com.mysql.jdbc.Driver”),如果换成getClass().getClassLoader().loadClass(“com.mysql.jdbc.Driver”),就不可以,因为它只是想JVM装载了Driver的类信息,但是没有实例化,也就不能执行相应的操作,因为Driver是需要被初始化才能被使用的。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
        currentClassLoader.loadClass("com.class_load.StaticClass");
    }
}

没有任何输出。

三个默认ClassLoader

public class Test {
 
    public static void main(String [] args) {
 
        ClassLoader cl = Test.class.getClassLoader();
 
        while (cl != null) {
            System.out.println(cl + "'s parent ClassLoader is " + cl.getParent());
            cl = cl.getParent();
        }
    }
 
}
 
返回结果:
sun.misc.Launcher$AppClassLoader@18b4aac2's parent ClassLoader is sun.misc.Launcher$ExtClassLoader@4617c264
sun.misc.Launcher$ExtClassLoader@4617c264's parent ClassLoader is null
1. Bootstrap ClassLoader

Bootstrap ClassLoader称为启动类加载器,是Java类加载层次中最顶级的加载,负责加载JDK中的核心类库,如:rt.jar、resource.jar、charsets.jar等,Bootstrap类加载器的加载目录由系统属性(sun.boot.class.path)指定,目录如下:

import java.net.URL;

public class Test {
 
    public static void main(String [] args) {
 
        // 也可通过 System.getProperty("sun.boot.class.path" 获取
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i].toExternalForm());
        }
 
    }
 
}
 
本机JDK环境返回结果:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/classes
2. Extension ClassLoader

Extension ClassLoader称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有文件。Extension ClassLoader是JVM启动类(sun.misc.Launcher)的内部类,继承自java.net.URLClassLoader类。其加载目录由系统属性(java.ext.dirs)指定。本机加载目录如下:

public class Test {
 
    public static void main(String [] args) {
 
 
        String paths = System.getProperty("java.ext.dirs");
 
        String [] pathList = paths.split(File.pathSeparator);
 
        for(String path : pathList) {
            System.out.println(path);
        }
    }
 
}
 
本机返回结果:
/Users/Jerry/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
3.App ClassLoader

App ClassLoader称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。App ClassLoader既是加载系统属性(java.class.path)的类加载器,加载目录如下:

package test;
 
import java.io.File;
 
public class Test {
 
    public static void main(String [] args) {
 
 
        String paths = System.getProperty("java.class.path");
 
        String [] pathList = paths.split(File.pathSeparator);
 
        for(String path : pathList) {
            System.out.println(path);
        }
    }
 
}
 
本机命令行编译并执行:java -cp /Users/Jerry/documents:/Users/Jerry/documents/workspace/xf-test test.Test
 
返回结果:
/Users/Jerry/documents
/Users/Jerry/documents/workspace/xf-test

这里,如果不指定classpath,本地执行返回当前目录(”.”),如果通过IDE执行,则会返回IDE设定的classpath值

4.自定义ClassLoader

除了Java默认提供的三种ClassLoader外,用户也可以根据自己需要,通过继承ClassLoader超类定义属于自己的ClassLoader。范例如下:
 定义自己的ClassLoader类,当通过ClassLoader.loadClass()方法加载类时,如果加载不到对应的类,则会调用自定义ClassLoader的findClass方法继续加载类,详见下一节:

public class MyClassLoader extends ClassLoader{
 
    public static final String driver = "/Users/Jerry/Desktop/";
    public static final String fileTyep = ".class";
 
    public Class findClass(String name) {
        byte[] data = loadClassData(name);
        return defineClass(data, 0, data.length);
    }
 
    public byte[] loadClassData(String name) {
        FileInputStream fis = null;
        byte[] data = null;
        try {
            File file = new File(driver + name + fileTyep);
            System.out.println(file.getAbsolutePath());
            fis = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int ch = 0;
            while ((ch = fis.read()) != -1) {
                baos.write(ch);
            }
            data = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("loadClassData-IOException");
        }
        return data;
    }
}

编写测试类,编译,并将class文件移动到在MyClassLoade的driver目录下:

package test;
 
public class Test {
 
    public void sayHello(String name) {
        System.out.println("hello " + name);
    }

测试MyClassLoader:

public class Test {
 
    public static void main(String[] args) {
        MyClassLoader cl1 = new MyClassLoader();
        try {
            Class c1 = cl1.findClass("Test");  // 或调用:Class c1 = cl1.loadClass("Test");
            Object object = c1.newInstance();
            Method method = c1.getMethod("sayHello", String.class);
            method.invoke(object, "Jerry");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
 
}  
 
返回结果:
/Users/Jerry/Desktop/Test.class
hello Jerry

在这里插入图片描述

【以上实验,均已经验证过】

参考文章:
1、https://blog.csdn.net/u010841296/article/details/89731566
2、https://www.jianshu.com/p/f4318e626a22
3、https://www.runoob.com/w3cnote/java-class-forname.html
4、https://blog.csdn.net/syilt/article/details/90706332
5、https://blog.csdn.net/jiamingku/article/details/82415056

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值