• 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通过桥接模式将接口已经规范好了,而具体的实现则由具体数据库决定。



  • 相关阅读:
    Java反射在Android中的使用
    配置adb环境变量
    Android Studio 生成Release版,报Warning的解决办法
    Android Studio导入System Library步骤
    Windows 10 Java环境变量配置
    做一个有内涵的程序猿
    简述Python2与Python3的区别
    对不起,您输入的内容不合法
    python的数据类型
    python三器——装饰器/迭代器/生成器
  • 原文地址:https://www.cnblogs.com/zh-ch/p/12825129.html
Copyright © 2020-2023  润新知