高扩展性场景考虑使用模板设计模式
例如 java中,jdbc连接不同的数据库服务的实现.
sun公司在最初想要用java实现集成连接所有数据厂商的数据服务软件,不过后来发现这是一件庞大到几乎不可能完成的事情.于是sun公司和数据服务厂商联合会议制定了jdbc接口规范,java负责给出数据库连接的规范,而不同的厂商则负责实现连接自身数据服务软件的部分即可,后来这部分被称作"驱动".
其大致思路是
java 在java.sql包中,给出了 Driver 接口规范,同时实现了 DriverManager 类,用来负责数据库驱动的注册(registerDriver),数据库连接的获取(getConnection);而Driver和Connection本身是接口类,其具体实现则有不同的厂商自己去实现,他们只需要继承这两个接口类,那么我们就能够在我们的application中调用数据服务,而不用去关心其具体实现细节.
(1)客户端部分
java.sql.Driver 部分源码 :
package java.sql;
public interface Driver {
// 获取连接
Connection connect(String url, java.util.Properties info) throws SQLException;
}
java.sql.DriverManager 部分源码 :
package java.sql;
// 注册驱动静态方法
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
...
// 获取连接
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;
// 在注册Driver集合中,找到我们注册的数据服务厂商提供的实现类,这样我们就能用Driver调用getConnection的API,获取数据库的链接资源
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");
}
在最新版本的 jdbc(com.mysql.cj.jdbc) 中,调用了DriverManager.registerDriver(new Driver())这一方法,来注册驱动;
注册驱动后,在调用了DriverManager.getConnection() API中,遍历注册Driver集合,找到我们可用的第三方实现的Driver实现子类,调用其提供的connect() API获取连接即可建立与数据服务软件的连接;
(2)驱动部分
一 . mysql官方驱动 (registerDriver 和 connect 分别在com.mysql.cj.jdbc.Driver类 和父类 (NonRegisteringDriver) 中实现)
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
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!");
}
}
}
其父类 NonRegisteringDriver :
package com.mysql.cj.jdbc;
......
public class NonRegisteringDriver implements Driver {
......
// 获取连接的API
public Connection connect(String url, Properties info) throws SQLException {
try {
try {
ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
if(conStr.getType() == null) {
return null;
} else {
switch(null.$SwitchMap$com$mysql$cj$conf$ConnectionUrl$Type[conStr.getType().ordinal()]) {
case 1:
return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl)conStr);
case 2:
return FailoverConnectionProxy.createProxyInstance(conStr);
case 3:
return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl)conStr);
case 4:
default:
return ConnectionImpl.getInstance(conStr.getMainHost());
}
}
} catch (CJException var5) {
throw (UnableToConnectException)ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var5.toString()}), var5);
}
} catch (CJException var6) {
throw SQLExceptionsMapping.translateException(var6);
}
}
}
......
二 . 第三方驱动
我们以阿里的DruidDriver(Druid驱动)中的实现为例 ,部分源码:
package com.alibaba.druid.proxy;
// 提供了注册驱动的API
public static boolean registerDriver(Driver driver) {
try {
DriverManager.registerDriver(driver);
try {
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("com.alibaba.druid:type=DruidDriver");
if(!mbeanServer.isRegistered(objectName)) {
mbeanServer.registerMBean(instance, objectName);
}
} catch (Throwable var3) {
if(LOG == null) {
LOG = LogFactory.getLog(DruidDriver.class);
}
LOG.warn("register druid-driver mbean error", var3);
}
return true;
} catch (Exception var4) {
if(LOG == null) {
LOG = LogFactory.getLog(DruidDriver.class);
}
LOG.error("registerDriver error", var4);
return false;
}
}
......
// 获取连接的API
public Connection connect(String url, Properties info) throws SQLException {
if(!this.acceptsURL(url)) {
return null;
} else {
this.connectCount.incrementAndGet();
DataSourceProxyImpl dataSource = this.getDataSource(url, info);
return dataSource.connect(info);
}
}
我们可以看到,通过继承或实现java.sql包下的Driver,并编写内部的逻辑,用户可以自由选择应用不同的Driver子类实例,并用该实例获取不同的Connection子类实例,达到访问不同数据服务的目的;而对于这些Connection实例的管理,又可以用池化的思想提升其利用率,优化使用性能
③可以向池化思想靠近;
在某些场景中某些类只需要被实例化一次,后续所有客户端只需要公用此同一实例时,静态构造方法内部可以实现局部的缓存,以便为整个应用提供一个单例的实体,避免了相同功能的实例的重复创健,销毁过程中的开销.
在②中提到,数据库建立连接后,对于这些Connection的生命周期的管理,便演变成了数据库连接池,关于连接池的一些应用,以后有时间再看看源码.
评论区