双亲委派机制有什么缺点?如何解决

2020-05-23 17:11:33 java常见问答 12600

接触过java软件开发的小伙伴们应该对双亲委派机制不会太陌生,毕竟这个还算是比较基础的理论知识,虽然是优点大过缺点的,但是或许我们还是可以更加全面来了解一下它,比如说,双亲委派机制的缺点有哪些?

破坏双亲委派模型的情况如下哦:

我们知道在JDBC4.0以后,开始支持使用spi的方式来注册这个Driver的,具体做法就是在mysql的jar包里面的META-INF/services/java.sql.Driver文件中去指明当前使用的Driver是哪个,然后我们在使用的时候就直接这样就行了:

Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

可以看到这里直接获取连接,省去了上面的Class.forName()注册的过程。

现在,我们分析下看使用了这种spi服务的模式原本的过程是怎样的:

首先,从META-INF/services/java.sql.Driver文件中获取具体的实现类名“com.mysql.jdbc.Driver”

其次,加载这个类,这里肯定只能用class.forName("com.mysql.jdbc.Driver")来加载

好了,那么问题来了,Class.forName()加载用的是调用者的Classloader,这个调用者DriverManager是在rt.jar中的,ClassLoader是启动类加载器,而com.mysql.jdbc.Driver肯定不在/lib下的,所以是无法加载mysql中的这个类的。重点来了,这就是双亲委派模型的局限性了,父级加载器无法加载子级类加载器路径中的类。

那么,这个问题如何解决呢?因为按照目前情况来分析,这个mysql的drvier只有应用类加载器能加载的,那么我们只要在启动类加载器中有方法获取应用程序类加载器,然后再通过它去加载就可以了。这就是所谓的线程上下文加载器。

线程上下文类加载器是可以通过Thread.setContextClassLoaser()方法设置的,如果说不特殊设置就会从父类继承的,一般默认使用的是应用程序类加载器。

很明显,线程上下文类加载器让父级类加载器能通过调用子级类加载器来加载类,这就打破了双亲委派模型的原则了。

我们现在来看一下DriverManager是如何使用线程上下文类加载器去加载第三方jar包中的Driver类的呢?

public class DriverManager
{
    static
    {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    private static void loadInitialDrivers()
    {
        //省略代码
        //这里就是查找各个sql厂商在自己的jar包中通过spi注册的驱动
        ServiceLoaderloadedDrivers = ServiceLoader.load(Driver.class);
        IteratordriversIterator = loadedDrivers.iterator();
        try
        {
            while (driversIterator.hasNext())
            {
                driversIterator.next();
            }
        }
        catch (Throwable t)
        {
            // Do nothing
        }
        //省略代码
    }
}

使用的时候呢,我们直接去调用DriverManager.getConn()方法的时候就自然会触发静态代码块的执行,开始加载驱动了,然后我们再来看一下ServiceLoader.load()的具体实现:

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

可以看到核心其实就是拿到线程上下文类加载器,然后去构造了一个ServiceLoader,后续的具体查找过程,接下来,DriverManager的loadInitialDrivers()方法中有一句driversIterator.next();,它的具体实现如下:

private S nextService()
{
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class c = null;
    try
    {
        //此处的cn就是产商在META-INF/services/java.sql.Driver文件中注册的Driver具体实现类的名称
        //此处的loader就是之前构造ServiceLoader时传进去的线程上下文类加载器
        c = Class.forName(cn, false, loader);
    }
    catch (ClassNotFoundException x)
    {
        fail(service
            , "Provider " + cn + " not found");
    }
    //省略部分代码
}

现在呢,我们成功的做到了通过线程上下文类加载器拿到了应用程序类加载器(或者自定义的然后塞到线程上下文中的),同时我们也是查找到了厂商在子级的jar包中注册的驱动具体实现类名,这样我们就可以成功的在rt.jar包中的DriverManager中成功的加载了放在第三方应用程序包中的类了。

好了,那么以上就是有关双亲委派的所有内容了,还想了解更多java常见问答的话,记得多多来关注本站消息哦。

推荐阅读:

双亲委派模型的好处是什么?怎么理解双亲委派模型?

双亲委派机制的优点有哪些?我们为什么要用双亲委派机制?

mybatis优缺点是什么?有哪些优点和缺点?