这是一个创建mysql连接的代码
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mydb";
Connection connection = DriverManager.getConnection(url, "user", "pwd");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM mytable");
上面有一段Class.forName("com.mysql.cj.jdbc.Driver"),这是意味着将com.mysql.jdbc.Driver这个类加载到jvm,我们找到这个类看看。
在这个类里我们看到有个static方法,标记了static的静态代码块将会在类加载的时候执行一次,里面对这个驱动类进行了注册。那为什么项目启动的时候不会加载这个类呢?我们不是用java -cp指定了类路径吗?而我们要显示的Class.forName去加载呢?是,我们-cp可以指定类路径,但并不意味着Jvm会加载这些类,虚拟机采用的是按需加载,这是因为JVM进行类加载的时候只会加载我们使用到的类,比如我们写了个A类那他就会加载这个A类,如果A类依赖了其他的类,那就会先去加载A类所需要的其他类。所以jvm的加载的类的内存使用还是要看我们使用的类有多少。
接下来说一下为什么jdk6开始我们不用显示的Class.forName了。
首先我们了解以下SPI(Service Provider Interface)机制,它提供了一种在运行时自动查找和加载实现类的方式。通过SPI机制,开发人员可以编写接口定义,而服务提供者可以编写对这些接口的实现。然后,应用程序可以通过在Classpath中查找服务提供者实现的方式来自动加载并使用这些实现,jdk就是使用了这种spi机制帮数据库厂商进行了Class.forName,所以我们不需要再自己写了,我们来看看jdk是怎么做的。
在rt.jar包里面的java.sql里面有个DriverManager类。
我们点进去这个方法
然后点ServiceLoader这个类
看到这个常量 "META-INF/services/" ,他会去所有依赖的包里面去找这个文件夹下这个文件, 然后我们找到mysql的依赖包。
可以看到,他有个java.sql.Driver文件,这个文件名就是按照jdk给的接口Driver全类名,按照规范文件名用全类名,而文件内容就写数据库厂商自己的实现类。例如我们的mysql的实现类就是com.mysql.cj.jdbc.Driver也就是我们开头需要Class.forName需要引入的包。
接下来我们点回去ServiceLoad里面的load方法
可以看到,里面用当前线程的上下文获取ClassLoader,因为rt.jar等核心包是通过BootstrapClassLoader去加载的,但是我们外部的jar是需要通过AppClassLoader去加载的,所以它利用这种方式就能加载到外部Jar的类。
回到这里我们点击去hasNext方法看,最终会跳到hasNextService这个方法
这个PREFIX就是我们上面的常量了
然后hastNext获取了需要的配置信息就看看netx方法,它最终会跳到nextService方法
在这里面最终也是通过Class.forName来加载我们的驱动类的 ,这样做的好处就是以后再有一个数据库厂商需要jdk帮他加载驱动只需要实现Driver类(类名要保持一致),然后在META-INF/services/放以Driver接口为全类名,内容为实现类的全类名,这样jdk就能自动帮他加载驱动了,而不用程序员再显示调用Class.forName,这种机制在Spring的自动装配里面也是十分类似,而Spring里面是放在META-INF的spring.factorics,好了,文章就讲到这里,如有错误欢迎指出。