一文分析特殊的类加载器”ServiceLoader”

点击上方 IT牧场 ,选择 置顶或者星标技术干货每日送达!

前言

    

最近在看类加载器相关的知识发现Class.forName(“XXX”)和ClassLoader.load(“xxx”)在类加载流程里面是有比较大的区别。class.forName(“XXXX”)会执行加载-链接-初始化,而ClassLoader.load只会执行加载。

 如果我们写Test测试可以发现,class.forName方式确实是可以走到初始化环节,而classLoder.load方式并不会。基于此想到基于原生jdbc的方式去操作db的步骤(本文基于jdk8 、mysql驱动包mysql-connector-java:5.1.34)去验证。

验证过程

jdbc操作db的方式 第一步是需要注册数据库驱动的。实例如下

public class Test1 {
    public static void main(String[] args) throws Exception {
        Class.forName(“com.mysql.jdbc.Driver”) 
        String url = "jdbc:mysql:/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&verifyServerCertificate=false&allowMultiQueries=true";
        String user = "xx";
        String password = "xx.db.2019!";
        Connection connection = DriverManager.getConnection(url, user, password);
        PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 FROM DUAL ");
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            String string = resultSet.getString(1);
            System.out.println(string);
        }
        connection.close();


    }


}

Class.forName(“com.mysql.jdbc.Driver”) 会执行静态代码块并注册mysql的数据库驱动

此时程序运行会如期返回结果。

再将第三行换成如下代码

Thread.currentThread().getContextClassLoader().loadClass("com.mysql.jdbc.Driver");

发现结果依然能成功返回~

WTF ??

 两次验证结果证明类加载时机,跟之前所说不太一致。这就有点懵了,按理说第二次验证应该提示错误才对,因为没有显式注册驱动。于是再次验证一个场景~

再次验证

这里做一个测试,如果将测试类中的注册驱动代码删掉,测试代码还能会继续执行吗?

public class Test1 {
    public static void main(String[] args) throws Exception {


        String url = "jdbc:mysql://xx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&verifyServerCertificate=false&allowMultiQueries=true";
        String user = "xx";
        String password = "xx";
        Connection connection = DriverManager.getConnection(url, user, password);
        PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 FROM DUAL ");
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            String string = resultSet.getString(1);
            System.out.println(string);
        }
        connection.close();


    }


}

答案是肯定的。

分析

这里可能会疑问,为什么我不需要注册驱动,但是程序能继续执行?

答案要从DriverManager.getConnection()方法说起,当jvm执行此代码时要做一件事。类加载流程,将DriverManager加载到jvm中,并执行初始化方法(静态代码块)。DriverManager中的静态代码块中代码如下

 

这一行只是创建了一个ServiceLoader实例,注意ServiceLoader实现了Iterable接口。所以要重写iterator方法

 

lookupIterator是ServiceLoader的成员变量,也是ServiceLoader的内部类

 

LazyIterator才是ServiceLoader的核心。因为真正触发类加载过程的就是迭代的过程。代码复制如下

 private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                //获取文件全名 这里是/META-INF/services/java.sql.Driver                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                    //通过类加载器获取到config对象,此时对象包含两个jar包下的
                    //文件,分别是mysql驱动包和druid数据源下驱动包
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //pending是通过上面获取的文件,解析文件里面的内容获得
                //驱动类的全名
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

 对应mysql驱动包的加载文件如下

 

 

这些类名就是mysql驱动包的类全名。

LazyIterator迭代器的next()方法是真正开始将上面获取的类名加载的jvm的过程

 

最终初始化完成的实例对象会存到ServiceLoader中的providers中

 

结论

此时疑惑得到解答,我在DriverManage的文档注释中也找到了答案。

 

 中间这段大概意思是当getConnection()方法被调用时,DriverManager会用当前应用的类加载器选择一个合适的数据库驱动加载。言外之意,新版本中不需要在显示的注册驱动了。

 这里小伙伴可以了解下java中SPI机制。ServiceLoader是SPI机制的实现。可以通过定义接口,在不同的jar包实现接口,并以ServiceLoader规范的方式创建好相应文件。即可在应用中实现自动创建jar包中实现类的实例。这就是组件化,插件化实现的一种方式。

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术群讨论

近期热文

LinkedBlockingQueue vs ConcurrentLinkedQueue解读Java 8 中为并发而生的 ConcurrentHashMapRedis性能监控指标汇总最全的DevOps工具集合,再也不怕选型了!微服务架构下,解决数据库跨库查询的一些思路聊聊大厂面试官必问的 MySQL 锁机制

关注我

喜欢就点个"在看"呗^_^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值