美文网首页java-日常java基础程序员
JDBC源码解析(二):获取connection

JDBC源码解析(二):获取connection

作者: 2453cf172ab4 | 来源:发表于2017-03-11 17:50 被阅读641次

    0x00 前言

    上一节分析了jdbc的Driver注册过程,这一节分析一下jdbc是如何获取connection的。

    0x01 connection的建立过程

    DriverManager.getConnection 做了什么

    conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test", "root", "root");
    

    可以看出,getConnection方法返回的是一个Connection对象,在下面的for循环中,会遍历registeredDrivers中存放的所有的Driver,直到找到一个合适的Driver,然后进行连接。通过这个方法我已经可以知道jdbc就是这样来获取连接的,但是具体是怎么来获取这个connection,还需要详细地看后面的代码。

    private static Connection getConnection(
            String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
        ......
            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);
                        }
                    } 
        ......
        }
    

    connect方法

    java.sql.Driver接口中定义了一个connect方法,然后由具体的实现类来完成connect的功能。

    我们先看一下mysql的实现。

    com.mysql.jdbc.Driver中,并没有详细的实现,但是它继承了NonRegisteringDriver,那么connect应该就在NonRegisteringDriver中了。

    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        public Driver() throws SQLException {
        }
    }
    

    现在定位到com.mysql.jdbc.NonRegisteringDriverconnect中。

    可以看出,有一句if ((props = parseURL(url, info)) == null)来对url进行判断,如果url不符合规则的话,就会返回一个null值,这里的url就是指我们在程序中写的jdbc:mysql://192.168.108.145/test这样的语句。mysql的parseURL方法十分十分的长,暂时不再细究它了...

    继续向下看,可以发现具体的获取连接的地方在com.mysql.jdbc.ConnectionImpl.getInstance

    public java.sql.Connection connect(String url, Properties info) throws SQLException {
            if (url != null) {
                if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
                    return connectLoadBalanced(url, info);
                } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
                    return connectReplicationConnection(url, info);
                }
            }
            Properties props = null;
            if ((props = parseURL(url, info)) == null) {
                return null;
            }
    
            if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
                return connectFailover(url, info);
            }
    
            try {
                Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);
                return newConn;
            } 
        ......
        }
    

    漫长的寻找连接的道路

    为了找到具体获取连接的地方,我们继续详细地向下找代码。

    根据方法的调用关系,我们依次找到下面的四个方法,但是在最后一个方法这里线索断了一下。

    • com.mysql.jdbc.NonRegisteringDriver.connect

    • com.mysql.jdbc.ConnectionImpl.getInstance

    • com.mysql.jdbc.Util.handleNewInstance.handleNewInstance

    • java.lang.reflect.Constructor.newInstance

    ``

    com.mysql.jdbc.ConnectionImpl

        protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url)
                throws SQLException {
            if (!Util.isJdbc4()) {
                return new ConnectionImpl(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
            }
    
            return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, new Object[] { hostToConnectTo, Integer.valueOf(portToConnectTo), info,
                    databaseToConnectTo, url }, null);
        }
    

    com.mysql.jdbc.Util.handleNewInstance

    public static final Object handleNewInstance(Constructor<?> ctor, Object[] args, ExceptionInterceptor exceptionInterceptor) throws SQLException {
            try {
    
                return ctor.newInstance(args);
            } catch (IllegalArgumentException e) {
            ......
        }
    

    java.lang.reflect.Constructor

        public T newInstance(Object ... initargs)
            throws InstantiationException, IllegalAccessException,
                   IllegalArgumentException, InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass(2);
    
                    checkAccess(caller, clazz, null, modifiers);
                }
            }
            if ((clazz.getModifiers() & Modifier.ENUM) != 0)
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
            ConstructorAccessor ca = constructorAccessor;   // read volatile
            if (ca == null) {
                ca = acquireConstructorAccessor();
            }
            return (T) ca.newInstance(initargs);
        }
    

    也就是说自己找的方向有了偏差,在com.mysql.jdbc.ConnectionImpl.getInstance中,有一句return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, ......),看来突破口在这里了。然后找JDBC_4_CONNECTION_CTOR是什么。

    可以看出在这里,调用了Class.forName("com.mysql.jdbc.JDBC4Connection").getConstructor(......)。然后需要看一下com.mysql.jdbc.JDBC4Connection是做什么的。

    com.mysql.jdbc.ConnectionImpl

        static {
            mapTransIsolationNameToValue = new HashMap<String, Integer>(8);
            mapTransIsolationNameToValue.put("READ-UNCOMMITED", TRANSACTION_READ_UNCOMMITTED);
            mapTransIsolationNameToValue.put("READ-UNCOMMITTED", TRANSACTION_READ_UNCOMMITTED);
            mapTransIsolationNameToValue.put("READ-COMMITTED", TRANSACTION_READ_COMMITTED);
            mapTransIsolationNameToValue.put("REPEATABLE-READ", TRANSACTION_REPEATABLE_READ);
            mapTransIsolationNameToValue.put("SERIALIZABLE", TRANSACTION_SERIALIZABLE);
    
            if (Util.isJdbc4()) {
                try {
                    JDBC_4_CONNECTION_CTOR = Class.forName("com.mysql.jdbc.JDBC4Connection").getConstructor(
                            new Class[] { String.class, Integer.TYPE, Properties.class, String.class, String.class });
                } catch (SecurityException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(e);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            } else {
                JDBC_4_CONNECTION_CTOR = null;
            }
        }
    

    继续找到了com.mysql.jdbc.JDBC4Connection,该类继承了com.mysql.jdbc.ConnectionImpl,因此在构造的时候也会先调用父类的构造器。

    public class JDBC4Connection extends ConnectionImpl {
        private JDBC4ClientInfoProvider infoProvider;
    
        public JDBC4Connection(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
            super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
        }
    }
    

    com.mysql.jdbc.ConnectionImpl构造器比较长,在其结尾处,发现它调用了NonRegisteringDriver.trackConnection(this)

        public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
            ......
            NonRegisteringDriver.trackConnection(this);
        }
    

    com.mysql.jdbc.NonRegisteringDriver

        protected static void trackConnection(Connection newConn) {
            ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue);
            connectionPhantomRefs.put(phantomRef, phantomRef);
        }
    

    com.mysql.jdbc.NonRegisteringDriver.ConnectionPhantomReferenceNonRegisteringDriver的内部类。

     static class ConnectionPhantomReference extends PhantomReference<ConnectionImpl> {
            private NetworkResources io;
    
            ConnectionPhantomReference(ConnectionImpl connectionImpl, ReferenceQueue<ConnectionImpl> q) {
                super(connectionImpl, q);
    
                try {
                    this.io = connectionImpl.getIO().getNetworkResources();
                } catch (SQLException e) {
                    // if we somehow got here and there's really no i/o, we deal with it later
                }
            }
    
            void cleanup() {
                if (this.io != null) {
                    try {
                        this.io.forceClose();
                    } finally {
                        this.io = null;
                    }
                }
            }
        }
    

    继续向下追,会用到两个比较重要的类:NetworkResourcesMysqlIO,他们和mysql的具体连接相关。

    com.mysql.jdbc.NetworkResources

    class NetworkResources {
        private final Socket mysqlConnection;
        private final InputStream mysqlInput;
        private final OutputStream mysqlOutput;
        protected NetworkResources(Socket mysqlConnection, InputStream mysqlInput, OutputStream mysqlOutput) {
            this.mysqlConnection = mysqlConnection;
            this.mysqlInput = mysqlInput;
            this.mysqlOutput = mysqlOutput;
        }
    }
    

    最后可以看出真正和mysql进行连接是在com.mysql.jdbc.MysqlIO中实现的,在这里有mysql连接的各种参数。

    This class is used by Connection for communicating with the MySQL server.

        //Mysql连接的一些设置参数
        private static final int CLIENT_LONG_PASSWORD = 0x00000001; /* new more secure passwords */
        private static final int CLIENT_FOUND_ROWS = 0x00000002;
        private static final int CLIENT_LONG_FLAG = 0x00000004; /* Get all column flags */
        protected static final int CLIENT_CONNECT_WITH_DB = 0x00000008;
        private static final int CLIENT_COMPRESS = 0x00000020; /* Can use compression protcol */
        private static final int CLIENT_LOCAL_FILES = 0x00000080; /* Can use LOAD DATA LOCAL */
        private static final int CLIENT_PROTOCOL_41 = 0x00000200; // for > 4.1.1
        ......
    
        //MysqlIO构造函数
        public MysqlIO(String host, int port, Properties props, String socketFactoryClassName, MySQLConnection conn, int socketTimeout, int useBufferRowSizeThreshold) throws IOException, SQLException {
            
            ......
            
            try {
                // 创建Socket对象,和MySql服务器建立连接  
                this.mysqlConnection = this.socketFactory.connect(this.host, this.port, props);
    
                if (socketTimeout != 0) {
                    try {
                        this.mysqlConnection.setSoTimeout(socketTimeout);
                    } catch (Exception ex) {
                        /* Ignore if the platform does not support it */
                    }
                }
                // 获取Socket对象
                this.mysqlConnection = this.socketFactory.beforeHandshake();
    
                if (this.connection.getUseReadAheadInput()) {
                    this.mysqlInput = new ReadAheadInputStream(this.mysqlConnection.getInputStream(), 16384, this.connection.getTraceProtocol(),
                            this.connection.getLog());
                } else if (this.connection.useUnbufferedInput()) {
                    this.mysqlInput = this.mysqlConnection.getInputStream();
                } else {
                    this.mysqlInput = new BufferedInputStream(this.mysqlConnection.getInputStream(), 16384);
                }
    
                //封装ScoketOutputStream输出流 
                this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection.getOutputStream(), 16384);
    
                this.isInteractiveClient = this.connection.getInteractiveClient();
                this.profileSql = this.connection.getProfileSql();
                this.autoGenerateTestcaseScript = this.connection.getAutoGenerateTestcaseScript();
    
                this.needToGrabQueryFromPacket = (this.profileSql || this.logSlowQueries || this.autoGenerateTestcaseScript);
                //封装SocketInputStream输入流  
                if (this.connection.getUseNanosForElapsedTime() && Util.nanoTimeAvailable()) {
                    this.useNanosForElapsedTime = true;
    
                    this.queryTimingUnits = Messages.getString("Nanoseconds");
                } else {
                    this.queryTimingUnits = Messages.getString("Milliseconds");
                }
    
                if (this.connection.getLogSlowQueries()) {
                    calculateSlowQueryThreshold();
                }
            } catch (IOException ioEx) {
                throw SQLError.createCommunicationsException(this.connection, 0, 0, ioEx, getExceptionInterceptor());
            }
        }
    

    0xFF 总结

    现在从头再梳理一下jdbc的注册过程。

    第一步:

    先看一下自己写的代码:

    conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test", "root", "root");
    

    第二步:

    getConnection方法中会获取到一个具体的connection。Connection con = aDriver.driver.connect(url, info);那么这个connection是怎么获取的呢,不同的厂商有自己特有的实现方式,对于mysql来说,在绕了一大圈后,最终调用了MysqlIO这个类来完成了和mysql-servier的连接。

    第三步:

    MysqlIO在获取到连接后,以NetworkResources的方式返回,这个NetworkResources就是mysql连接的一些基本信息。最后的连接由DriverManager的getConnection来获取。

    第四步:

    没了。


    作者:dantezhao |简书 | CSDN | GITHUB

    文章地址:http://www.jianshu.com/p/e2d267497cf6
    个人主页:http://www.jianshu.com/u/2453cf172ab4
    文章可以转载, 但必须以超链接形式标明文章原始出处和作者信息

    微信公众号

    相关文章

      网友评论

        本文标题:JDBC源码解析(二):获取connection

        本文链接:https://www.haomeiwen.com/subject/xpqzgttx.html