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

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(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 {
        private NetworkResources io;

        ConnectionPhantomReference(ConnectionImpl connectionImpl, ReferenceQueue 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_第1张图片
微信公众号

你可能感兴趣的:(JDBC源码解析(二):获取connection)