双亲委派机制以及打破双亲委派机制

双亲委派机制以及打破双亲委派机制

双亲委派机制

Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存中生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式.

双亲委派机制的优点:

  • 1、避免类的重复加载
  • 2、保护程序的安全性,防止核心API随意被篡改
    双亲委派机制以及打破双亲委派机制_第1张图片
    双亲委派机制以及打破双亲委派机制_第2张图片

沙箱安全机制:保证对Java信息源代码的保护

何为沙箱安全机制?即如果我们自己建立和源代码相同的包,例如java/lang/String.Class,在我们去使用类加载器去加载此类时,为了防止你自定义的类对源码的破坏,所以他默认不是使用你的String类的本身的系统加载器去加载它,而是选择率先使用引导类加载器去加载,而引导类在加载的过程中会先去加载JDK自带的文件(rt.jar包中的java/lang/String.class),而不是你自己定义的String.class,报错信息会提示没有main方法 ,就是因为加载的是rt.jar包下的String类,这样就可以做到保证对java核心源代码的保护,这即是沙箱保护机制.

那么双亲委派机制存在哪些缺点,又有哪些打破双亲委派机制的例子呢?

通过上面双亲委派机制的特点,我们知道了以下结论:

由于BootStrapClassLoader是顶级类加载器,并且它不能使用AppClassLoader加载器加载的类,因为BootstrapClassloader无法委派AppClassLoader来加载类。故当我们的上层类加载器需要下层类加载器帮忙加载类,就会出现问题,这便是双亲委派机制的缺点,那么哪些场景发生了这些事情,而Java又是如何打破这种双亲委派机制的呢?

我们来举一个经典的打破双亲委派机制的例子:Java SPI机制

SPI(Service Provider Interface),主要是应用于厂商自定义组件或插件中。

java SPI提供一种服务发现的机制,即为某个接口寻找服务实现的机制,我们在需要在jar包的META-INF/services/目录里面创建一个一服务接口命令的文件,而该文件就是实现这个接口的具体实现类,当有外部程序需要装配我们的这个模块接口的时候,就可以通过META-INF/services/目录的配置文件找到对应的实现类的类名,并将其实例化,完成该接口模块的注入。而在JDK中,也提供了一个服务实现查找的一个工具类:java.util.ServiceLoader。

我们以数据库连接jar包为例:mysql-connector-java-5.1.37.jar,它遵循SPI的规范

双亲委派机制以及打破双亲委派机制_第3张图片

我们的java.sql.Driver接口定义在java.sql包下,而该包的位置位于jdk\jre\lib\rt.jar,其中java.sql包中还提供了其它相应的类和接口比如管理驱动的类:DriverManager类等等,这样我们就很清楚地知道了java.sql是由BootStrapClassLoader加载器加载的

而我们的接口的实现类com.mysql.jdbc.Driver是第三方实现的类库,是由AppClassLoader加载器加载的

那么问题来了:我们使用DriverManager获取Connection的时候,必然会加载到com.mysql.jdbc.Driver类,此时即是BootstrapClassloader加载的类使用了由AppClassLoader加载的类,很明显和双亲委托机制的原理相悖!!!

JVM采取了一种作弊的方式打破了双亲委派机制,从而完成了上层类加载器使用下层类加载器进行加载的类:

在BootstrapClassLoader或ExtClassLoader加载的类A中,如果使用到AppClassLoader类加载器加载的类B,由于双亲委托机制不能向下委托,那可以在类A中通过线程上下文类加载器获得AppClassLoader,从而去加载类B

我们通过源码来看一下:

首先我们创建一个web项目,然后导入mysql的驱动包,我们编写一下测试代码:

public class TestClassLoader {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        String driverClassName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/mybatis";
        String username = "root";
        String password = "root";
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        //执行驱动"com.mysql.jdbc.Driver"
        Class.forName("com.mysql.jdbc.Driver");

        //获取连接
        connection = DriverManager.getConnection(url,username,password);
    }
}

此时我们知道这段代码肯定是能正常执行了,第一步加载我们的mysql的驱动,然后利用DriverManager获取连接

那么我们将第一步的执行驱动代码注释掉,你猜猜还会执行成功吗??
根据上面我们说的,答案是肯定的!!!我们进入源码探究一下

DriverManager类的内部存在一个静态代码块,其中有一个**loadInitialDrivers()**方法:

双亲委派机制以及打破双亲委派机制_第4张图片

我们点进去,里面有一行代码:

//JDK中提供的ServiceLoader来实现服务实现查找功能
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);

双亲委派机制以及打破双亲委派机制_第5张图片

我们点进去这个方法看看:

public static  ServiceLoader load(Class service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

这段代码便是打破双亲委派机制,实现com.mysql.jdbc.Driver驱动类加载的核心,即我们获取线程上下文加载器Thread.currentThread().getContextClassLoader(),然后利用线程上下文类加载器进行mysql驱动的类加载,以达到打破双亲委派机制的作用

PS:此时如果我们改变线程上下文类加载器,就会使得此时程序出现错误,大家也就明白了其中的流程和原理了

public class TestClassLoader {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        String driverClassName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/mybatis";
        String username = "root";
        String password = "root";
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        //改变当前线程上下文类加载器为当前类的扩展类加载器
        Thread.currentThread().setContextClassLoader(TestClassLoader.class.getClassLoader().getParent());
        
        //执行驱动"com.mysql.jdbc.Driver"
        //Class.forName("com.mysql.jdbc.Driver");

        //获取连接
        connection = DriverManager.getConnection(url,username,password);
    }
}

执行结果:

Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/mybatis
	at java.sql.DriverManager.getConnection(DriverManager.java:689)
	at java.sql.DriverManager.getConnection(DriverManager.java:247)
	at it.feng.test.TestClassLoader.main(TestClassLoader.java:25)

你可能感兴趣的:(Java学习,java,mysql,jdbc)