load/find class 与 forname 在static代码块加载的不同

https://blog.csdn.net/qq_38312744/article/details/80170599

 

一些文章中说,forname加载static代码块,loadclass不加载,不尽准确

做一些测试,jdk1.8

 

public class A {

    static {
        System.out.println("A 加载");
    }

    public static void teststatic() {
        ;
    }

    public A() {
        // 隐式加载B
        B b = new B();

        // 打印B的类加载器
        System.out.println("B:" + b.getClass().getClassLoader());
    }
}

 

1) 直接new

 A a = new A();

 A 加载

 

2)forName

Class CA = Class.forName("lc.A");

 A 加载

 

3)loadClass

Class CA = Thread.currentThread().getContextClassLoader().loadClass("lc.A");

 无输出,未激活static代码块

 

4)loadClass+newInstance

        Class CA = Thread.currentThread().getContextClassLoader().loadClass("lc.A");
        CA.newInstance();

 A 加载

实例化激活static代码块

 

5)loadClass+静态方法

        Class CA0 = Thread.currentThread().getContextClassLoader().loadClass("lc.A");
        Method method = CA0.getMethod("teststatic");
        method.invoke(null);    // 会触发static代码块

 A加载

反射调用静态方法激活static代码块

 

6)自定义加载器+loadClass

        String dir = "file:/Users/sunyuming/Downloads/jars/MyTest-1.0-SNAPSHOT.jar";
        URL url = new URL(dir);
        URL[] urls2 = {url};
        MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);
        myUrlClassLoader.loadClass("lc.A");

无输出,loadclass未激活static代码块

 

7) 自定义加载器+laodClass+newInstance

        String dir = "file:/Users/sunyuming/Downloads/jars/MyTest-1.0-SNAPSHOT.jar";
        URL url = new URL(dir);
        URL[] urls2 = {url};
        MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);
        Class CA = myUrlClassLoader.loadClass("lc.A");
        CA.newInstance();

 A 加载

实例化激活static代码块

 

8)自定义加载器+findClass+newInstance

        Class CA0 = Thread.currentThread().getContextClassLoader().loadClass("lc.A");
        CA0.newInstance();
        String dir = "file:/Users/sunyuming/Downloads/jars/MyTest-1.0-SNAPSHOT.jar";
        URL url = new URL(dir);
        URL[] urls2 = {url};
        MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);
        Class CA = myUrlClassLoader.findClass("lc.A");
        CA.newInstance();

A 加载

A加载

findclass绕开双亲委派(因为属于两个类,classpath下一个,jars/中一个

 

9)完整

        A a = new A();
        System.out.println(1);
        Class CA_1 = Class.forName("lc.A");
        System.out.println(2);
        Class CA0 = Thread.currentThread().getContextClassLoader().loadClass("lc.A");
        CA0.newInstance();
        System.out.println(3);
        String dir = "file:/Users/sunyuming/Downloads/jars/MyTest-1.0-SNAPSHOT.jar";
        URL url = new URL(dir);
        URL[] urls2 = {url};
        MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);
        Class CA = myUrlClassLoader.findClass("lc.A");
        CA.newInstance();

 

A 加载
B:sun.misc.Launcher$AppClassLoader@5e481248
1
2
B:sun.misc.Launcher$AppClassLoader@5e481248
3
A 加载
B:sun.misc.Launcher$AppClassLoader@5e481248

 

因此:

 

 =========================================================

再来看jdbc driver

Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(URL, USER, PASSWORD);

 

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can\'t register driver!");
        }
    }
}

 

经过调试,执行顺序为:

Class.forName("com.mysql.jdbc.Driver");
DriverManager.registerDriver(new Driver());
conn = DriverManager.getConnection(URL, USER, PASSWORD);

 

 

换为:

Thread.currentThread().getContextClassLoader().loadClass("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(URL, USER, PASSWORD);

 

经过调试,执行顺序为:

Thread.currentThread().getContextClassLoader().loadClass("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(URL, USER, PASSWORD);
DriverManager.registerDriver(new Driver());

换为:

 

//            Class.forName("com.mysql.jdbc.Driver");
//            JdbcUtil.class.getClassLoader().loadClass("com.mysql.jdbc.Driver");
            /**
             * 以上两句本例都不需要
             * DriverMagager的静态函数getConnection激发DriverManager的静态代码块,在其中
             * 扫描当前线程类加载器中一切符合jdbc SPI规范的驱动类,然后加载并实例化
             */
            conn = DriverManager.getConnection(URL, USER, PASSWORD);

 

 

 

居然也可以,既不用forName,又不用loadClass也可以;

与我们之前的结论不一致,new 与 forname直接执行static代码块,load/findclass直到真正实例化或调用静态函数时执行static代码块

2020.1.10 我们未曾调用过com.mysql.jdbc.Driver的静态函数或实例化,这个代码能用是因为别的原因:

DriverManager.getConnection  调用DriverManager的静态函数,进而激发DriverManager的static代码块: 

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

 在这个里面做了些文章,更详细的,看:JDBC SPI 类加载机制

 

=======================================================================

 

 

  • 两者最大的区别

    Class.forName得到的class是已经初始化完成的

    Classloder.loaderClass得到的class是还没有链接的

 https://www.cnblogs.com/suibianle/p/6676215.html

有些情况是只需要知道这个类的存在而不需要初始化的情况使用Classloder.loaderClass

 

 

2020.1.7发现

forname还有一个版本重载:

    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)

 

可以选择是否初始化及使用哪个类加载器加载

若不指定,则会使用调用方的类加载器,什么叫调用方的类加载器:两种类别的类加载器(其实是4种)

public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

posted on 2019-01-19 22:29  silyvin  阅读(407)  评论(0编辑  收藏  举报