JVM中提供了三层的ClassLoader:
Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。 ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。 AppClassLoader:主要负责加载应用程序的主函数类
那如果有一个Hello.class文件是如何被加载到JVM中的呢?
从上图中我们就更容易理解了,当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理会先检查自己是否已经加载过,如果没有再往上。注意这个过程,知道到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的。如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。 为什么要设计这种机制 这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。
SPI打破双亲委派机制
Thread.currentThread().setContextClassLoader(MainClass.class.getClassLoader().getParent());//加载到AppClassLoader的父类
Connection connection = DriverManager.getConnection
("jdbc:mysql://localhost:3306/mysql?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true" ,"root","331213");//必须要通过AppClassLoader加载
System.out.println(connection);
双亲委派机制是一种至下而上的加载方式,那么SPI是如何打破这种关系? 以JDBC加载驱动为例: 在JDBC4.0之后支持SPI方式加载java.sql.Driver的实现类。SPI实现方式为,通过ServiceLoader.load(Driver.class)方法,去各自实现Driver接口的lib的META-INF/services/java.sql.Driver文件里找到实现类的名字,通过Thread.currentThread().getContextClassLoader()类加载器加载实现类并返回实例。 最终矛盾出现在,要在ExtClassLoader加载的类里,调用AppClassLoader去加载实现类,双亲委派机制是一种至下而上的加载方式,无法实现父类调用子类,而父类无法加载到META-INF/services/java.sql.Driver文件,故报错
Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/mysql?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
at java.sql.DriverManager.getConnection(DriverManager.java:689)
at java.sql.DriverManager.getConnection(DriverManager.java:247)
at com.wanghh.hotels.modules.MainClass.main(MainClass.java:30)