JDBC中Class.forName到底做了什么以及相关设计模式

JDBC操作数据库时我们第一步是调用Class.forName注册数据库驱动

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","rootroot");
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from user where id = 1");
        while (resultSet.next()){
            String name = resultSet.getString("name");
            System.out.println(name);
        }
    }
}

为什么调用Class.forName就能够把驱动加载进来呢?第一反应是看forName方法做了什么操作

/**
     * Returns the {@code Class} object associated with the class or
     * interface with the given string name.  Invoking this method is
     * equivalent to:
     *
     * <blockquote>
     *  {@code Class.forName(className, true, currentLoader)}
     * </blockquote>
     *
     *返回与给定字符串名的类或接口相关联的{@code Class}对象。调用这个方法相当于:Class.forName(className, true, currentLoader)
     */
    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }           
从注释可以得出forName(String className)是会初始化类的,也就是说类中的静态变量和静态代码块会被执行,所以com.mysql.jdbc.Driver类将会被加载并初始化
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
    static {
        try {
            // 这里把自己注册了
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

查看Driver源码也证实了这点,就是在类被加载时在静态代码块初始化了Driver对象。DriverManager.registerDriver(new Driver) 等价于Class.forName("com.mysql.jdbc.Driver")。到此我们便清楚的知道了驱动类是怎么被加载的。

接下来我们再看DriverManager.registerDriver方法,看看究竟数据库驱动是怎么被注册和使用的

    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
 
    /**
     * Registers the given driver with the {@code DriverManager}.
     * A newly-loaded driver class should call
     * the method {@code registerDriver} to make itself
     * known to the {@code DriverManager}. If the driver is currently
     * registered, no action is taken.
     *
     * @param driver the new JDBC Driver that is to be registered with the
     *               {@code DriverManager}
     * @param da     the {@code DriverAction} implementation to be used when
     *               {@code DriverManager#deregisterDriver} is called
     * @exception SQLException if a database access error occurs
     * @exception NullPointerException if {@code driver} is null
     * @since 1.8
     */
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

从源码里我们可以清晰的看到被new的driver对象被加到了一个list中。注册好驱动下一步则是获取连接Connection

//  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());
            // 这里利用注册进来的driver对象创建了连接对象
                    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");
    }

实际上获取连的是被注册进去的com.mysql.jdbc.Driver创建的。分析这几个类可以看出JDBC使用了桥接模式来达到接口与具体实现的分离。所以我们能够随意的变化底层数据库,但是jdbc的使用方法不变,唯一变的仅仅是注册时的驱动而已,jdbc通过桥接模式将接口已经规范好了,而具体的实现则由具体数据库决定。

 

 



posted @ 2020-05-04 04:19  link_ed  阅读(430)  评论(0编辑  收藏  举报