JDBC那点事儿:(一)Class.forName(“xxxxxxx”):
author:马衍硕
since:2020-02-24
这是常见的一个jdbc连接数据库,执行查询的一个操作。
import java.sql.*;
public class JdbcDemo {
public static void main(String[] args) throws Exception{
String url = "jdbc:mysql://127.0.0.1:3306/alg?characterEncoding=utf8&serverTimezone=UTC";
String username = "root";
String password = "199606";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, username, password);
String sql = "SELECT * FROM tb_account";
try(Statement stmt = conn.createStatement()){
ResultSet rs = stmt.executeQuery(sql);
if(rs.next()){
System.out.println(rs.getString("id"));
}
}
}
}
看到这句代码我们会很迷惑
Class.forName("com.mysql.cj.jdbc.Driver");
这句代码干了什么?其工作原理是什么?他有什么作用?
为了更好的了解jdbc连接数据库的机制,我们看一下getConnection()的源码;
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
仔细阅读源代码,我没找到了关键的for循环:
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
可以分析出,具体的连接工作,是由registeredDrivers中的driver完成。
Connection con = aDriver.driver.connect(url, info);
那registeredDrivers中的driver是什么时候存在的?
好的,总结一下我们现在的疑问:
1、Class.forName(xxxxx)干了什么事?
2、registeredDrivers中的driver是什么时候存在的?
一、Class.forName(xxxxx)干了什么事?
先看源码:
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
注意:
这是java调用其他地方的接口的一个声明关键字,意思是这个方法不是java实现的,native方法是直接交给c/c++来实现。java只能调用,由操作系统实现。
官方文档:
地址:https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#forName-java.lang.String-
public static Class<?> forName(String className)
throws ClassNotFoundException
Returns the Class object associated with the class or interface with the given string name. Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of the current class.
For example, the following code fragment returns the runtime Class descriptor for the class named java.lang.Thread:
Class t = Class.forName("java.lang.Thread")
A call to forName("X") causes the class named X to be initialized.
Parameters:
className - the fully qualified name of the desired class.
Returns:
the Class object for the class with the specified name.
Throws:
LinkageError - if the linkage fails
ExceptionInInitializerError - if the initialization provoked by this method fails
ClassNotFoundException - if the class cannot be located
翻译:
public static Class<?> forName(String className)
throws ClassNotFoundException
返回具有给定字符串名称的与类或接口关联的Class对象。 调用此方法等效于:
Class.forName(className,true,currentLoader)
其中currentLoader表示当前类的定义类加载器。
例如,以下代码片段返回名为java.lang.Thread的类的运行时类描述符:
Class t = Class.forName("java.lang.Thread")
调用forName(“ X”)导致初始化名为X的类。
参数:
className-所需类的完全限定名称。
返回值:
具有指定名称的类的Class对象。
抛出:
LinkageError-如果链接失败
ExceptionInInitializerError-如果此方法引发的初始化失败
ClassNotFoundException-如果无法找到该类
可以分析出:
Class.forName的作用是装载一个类并且对其进行实例化的操作。
既然是实例化,那么我们new一个可以吗?试一下
import java.sql.*;
public class JdbcDemo {
public static void main(String[] args) throws Exception{
String url = "jdbc:mysql://127.0.0.1:3306/alg?characterEncoding=utf8&serverTimezone=UTC";
String username = "root";
String password = "199606";
//Class.forName("com.mysql.cj.jdbc.Driver");
new com.mysql.cj.jdbc.Driver();
Connection conn = DriverManager.getConnection(url, username, password);
String sql = "SELECT * FROM tb_account";
try(Statement stmt = conn.createStatement()){
ResultSet rs = stmt.executeQuery(sql);
if(rs.next()){
System.out.println(rs.getString("id"));
}
}
}
}
事实证明,也可以!!!
第一个问题我们已经明白了,Class.forName(String classNamae)是对类进行了装载类/实例化的操作,那么实例化com.mysql.cj.jdbc.Driver 与registeredDrivers是不是有什么神秘的联系?
接下来理所当然的想看看实例化com.mysql.cj.jdbc.Driver的过程中发生了什么?
二、registeredDrivers中的driver是什么时候存在的?
源码:
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
* Free Software Foundation.
*
* This program is also distributed with certain software (including but not
* limited to OpenSSL) that is licensed under separate terms, as designated in a
* particular file or component or in included license documentation. The
* authors of MySQL hereby grant you an additional permission to link the
* program and your derivative works with the separately licensed software that
* they have included with MySQL.
*
* Without limiting anything contained in the foregoing, this file, which is
* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
* version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
* for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.cj.jdbc;
import java.sql.SQLException;
/**
* The Java SQL framework allows for multiple database drivers. Each driver should supply a class that implements the Driver interface
*
* <p>
* The DriverManager will try to load as many drivers as it can find and then for any given connection request, it will ask each driver in turn to try to
* connect to the target URL.
*
* <p>
* It is strongly recommended that each Driver class should be small and standalone so that the Driver class can be loaded and queried without bringing in vast
* quantities of supporting code.
*
* <p>
* When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a
* driver by doing Class.forName("foo.bah.Driver")
*/
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
suprise !!! 仔细看静态代码块,原来在加载类的时候就完成了实例化Driver(),并向registerDriver添加的工作
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
所有的问题我们都搞清楚了,这里我们来总结一下:
1、Class.forName(“com.mysql.cj.jdbc.Driver”)完成加载Driver的操作,在Driver类静态代码块中,实例化Driver(),添加到registerDriver中;
2、在getConnection()中,使用registerDriver中对应的driver以及用户名密码等信息,完成连接;
以上,我爱源码!