要理解这个输出,我们就得说一下双亲委派模型,「如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式」,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成。「双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码」。
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以「避免类的重复加载」,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
「其次是考虑到安全因素,java核心api中定义类型不会被随意替换」,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
检查和加载过程以及系统提供的ClassLoader的作用如下图。文章一开始的问题,用双亲加载来解释就很容易理解用的是原生api中的String类
类加载器的关系如下:
启动类加载器,由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
应用程序类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
用户自定义类加载器,父类加载器肯定为AppClassLoader。自定义类加载器,父类加载器肯定为AppClassLoader。
当我们刚开始用JDBC操作数据库时,你一定写过如下的代码。先加载驱动实现类,然后通过DriverManager获取数据库链接
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://myhost/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "test", "test")
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!");
}
}
}
在Driver类中向DriverManager注册对应的驱动实现类
在JDBC4.0以后,开始支持使用SPI的方式来注册这个Driver,具体做法就是在mysql的jar包中的META-INF/services/java.sql.Driver 文件中指明当前使用的Driver是哪个。
「SPI就是策略模式,根据配置来决定运行时接口的实现类是哪个」
「这样当使用不同的驱动时,我们不需要手动通过Class.forName加载驱动类,只需要引入相应的jar包即可」。于是上面的代码就可以改成如下形式
Connection conn = DriverManager.getConnection("jdbc:mysql://myhost/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "test", "test");
「那么对应的驱动类是何时加载的呢?」
我们从META-INF/services/java.sql.Driver文件中获取具体的实现类“com.mysql.jdbc.Driver”
通过Class.forName("com.mysql.jdbc.Driver")将这个类加载进来
DriverManager是在rt.jar包中,所以DriverManager是通过启动类加载器加载进来的。而Class.forName()加载用的是调用者的ClassLoader,所以如果用启动类加载器加载com.mysql.jdbc.Driver,肯定加载不到(「因为一般情况下启动类加载器只加载rt.jar包中的类哈」)。