服务提供者框架+JDBC驱动源码分析
1.服务提供者框架介绍
服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。
例如,JDBC mySQL和JDBC Oracle,两种驱动,我们使用的时候,只需要简单的注册驱动然后getConnection就能得到Connection对象,其实这两个驱动有不同的实现,如连接两个数据库的方式不同,但我们使用的时候,不用区别就能得到Connection服务接口。
服务者提供框架有四个组件:
- 服务接口:服务的实现,由提供者实现
- 提供者注册API:注册驱动,让客户端能访问
- 服务访问API:驱动内实现,客户端获取服务接口
- (可选)服务提供者接口: 负责创建服务实现的实例
2.简单的代码框架
//服务提供者框架简述
**//服务接口**
public interface Service {
...//定义提供哪些服务
}
**//服务提供者接口 负责创建实例**
public interface Provider {
Service.newInstance();//自定义服务,负责创建实例返回
}
//服务注册以及获取接口
public class Services {
private Services () { }//不能实例化
//保存服务提供者的实例
private static final Map<String,Provider> providers =
new ConcurrentHashMap<String,Provider>();
private static final String DEAFULT_PROVIDER_NAME = "<def>";
**//服务提供者注册API**
public static void registerDefaultProvider(Provider p){
registerProvider(DEAFULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provider p){
providers.put(name, p);
}
**//服务访问API**
public static Service getService(){
return newInstance(DEAFULT_PROVIDER_NAME);
}
public static Service getService(String name){
Provider p = providers.get(name);
if( p == null){
throw new IllegalArgumentException(
"No provider registered with name: " + name );
}
return p.newService();
}
}
Service是一个服务接口,定义服务有哪些功能要实现,Provider是一个服务提供者接口,负责实现服务接口定义的功能,并实例化服务接口,Services 注册服务提供者(registerProvider),通过名称映射保存服务提供者的实例,同时提供服务访问的接口(getService)。当我们想要使用Service定义的功能时,我们只需要注册服务提供者,然后getService就可以得到服务实例。这样就使得Provider1,Provider2能定义不同的实现,来提供相同的服务,而我们在使用的时候,是感受不到Service实现不同的。这就是服务提供者框架的好处。
3.JDBC mySQL的应用
使用JDBC mySQL得到数据库的连接,并执行得到数据库放回结果,整个过程的代码如下:
public class JDBCCon {
private Connection conn = null;
private PreparedStatement pstmt = null;
private ResultSet rs = null;
private String sql = "SELECT * FROM table WHERE id = ? ";
public void selectTable (int id){
try{
//STEP1: 加载数据库驱动程序
Class.forName("com.mysql.jdbc.Driver");
}catch (ClassNotFoundException e){e.printStackTrace(); }
String url = "jdbc:mysql://localhost:port/DB_Name";
try{
//STEP2: DriverManager获得一个数据库连接对象
conn = DriverManager.getConnection(url,"userName","password");
}catch(SQLException e){ e.printStackTrace(); }
try{
//STEP3: 创建Statement(SQL执行环境)
pstmt = conn.prepareStatement(sql);
pstmt = setInt(1, id);
//STEP4: 执行SQL语句
rs = pstmt.executeQuery();
//STEP5 : 处理结果
while(rs.next() ){ /*......*/ }
}catch (SQLException e) { e.printStackTrace(); }
finally{
//STEP6: 关闭数据库连接
DbClose.close(rs,pstmt, conn);
}
}
}
java.sql.Connection对象就是服务接口,从服务框架来说,step1注册服务提供者,step2得到服务接口对象,step3使用服务提供的功能,我们不用区分JDBC mySQL和JDBC Oracle的实现的不同,就能调用方法得到相同的Connection服务。接下来会从mySQL JDBC源码执行路径,来解析step1和step2的执行过程。
4.STEP1:Class.forName如何注册驱动
Class.forName("com.mysql.jdbc.Driver");
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//Static fields/initializers Register ourselves with the DriverManager
static {
try {
**java.sql.DriverManager.registerDriver(new Driver())**;
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
//Constructors
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
public class DriverManager {
/* write copy of the drivers vector */
private static java.util.Vector writeDrivers = new java.util.Vector();
/* write copy of the drivers vector */
private static java.util.Vector readDrivers = new java.util.Vector();
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
DriverInfo di = new DriverInfo();
di.driver = driver;
di.driverClass = driver.getClass();
di.driverClassName = di.driverClass.getName();
// Not Required -- drivers.addElement(di);
**writeDrivers.addElement(di);**
println("registerDriver: " + di);
/* update the read copy of drivers vector */
readDrivers = (java.util.Vector) writeDrivers.clone();
}
}
//这里有个疑问,为什么DriverManager要使用两个vector去保存driver实例?
通过Class.forName(“com.mysql.jdbc.Driver”)来加载mysql的jdbc驱动,通过静态初始化块在DriverManager中注册自己(驱动信息封装成DriverInfo驱动信息类,加入集合中)。 Mysql的com.mysql.jdbc.Driver类实现了java.sql.Driver接口,任何数据库提供商的驱动类都必须实现这个接口。在DriverManager类中使用的都是接口Driver类型的驱动,也就是说驱动的使用不依赖于具体的实现,这无疑给我们的使用带来很大的方便。如果需要换用其他的数据库的话,只需要把Class.forName()中的参数换掉就可以了,可以说是非常方便的.
5.STEP2:DriverManager.getConnection
获取数据库的连接本质其实就是客户端取得一个远程MySQL服务器的TCP长连接。
DriverManager.getConnection(url,"userName","password");
public class DriverManager {
//not only one public getConnection method ,see more in standard source code
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
ClassLoader callerCL = DriverManager.getCallerClassLoader();
if (user != null) { info.put("user", user ); }
if (password != null) { info.put("password", password); }
return (getConnection(url, info, callerCL));
}
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
java.util.Vector drivers = null;
if(url == null) {
throw new SQLException("The url cannot be null", "08001"); }
println("DriverManager.getConnection(\"" + url + "\")");
drivers = readDrivers;//源码中有同步锁,这里简写了
SQLException reason = null;
for (int i = 0; i < drivers.size(); i++) {
DriverInfo di = (DriverInfo)drivers.elementAt(i);
if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
println(" skipping: " + di); continue;
}
try {
println(" trying " + di);
Connection result = di.driver.connect(url, info);//这个方法的实现在NonRegisteringDriver
if (result != null) {
// Success!
println("getConnection returning " + di);
return (result);
}
} catch (SQLException ex) {
if (reason == null) { reason = ex; }
}
}
// 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");
}
}
public class NonRegisteringDriver implements java.sql.Driver {
public java.sql.Connection connect(String url, Properties info)
throws SQLException {
//省略一些前缀匹配,实现不同的url类型,分别处理
Properties props = null;
if ((props = parseURL(url, info)) == null) {
return null;
}
try {
Connection newConn = new com.mysql.jdbc.Connection(host(props),
port(props), props, database(props), url);
return newConn;
} catch (SQLException sqlEx) { throw sqlEx;
} catch (Exception ex) { throw SQLError.createSQLException("..."); }
}
}
public class Connection extends ConnectionProperties implements
java.sql.Connection {
Connection(String hostToConnectTo, int portToConnectTo, Properties info,
String databaseToConnectTo, String url) throws SQLException {
//这个方法代码很多,挑了两句重点的理解一下
//初始化驱动的属性信息....
initializeDriverProperties(info);
createNewIO(false);//创建新的连接服务器的IO channel
}
}
//私有的getConnection方法中有synchronized(DriverManager.class){}有什么作用?
protected void createNewIO(boolean isForReconnect)
throws SQLException {
// 创建一个MysqlIO对象,建立与Mysql服务器的Socket连接
this.io = new MysqlIO(newHost, newPort, mergedProps,
getSocketFactoryClassName(), this,
getSocketTimeout(),
this.largeRowSizeThreshold.getValueAsInt());
// 登录校验MySql Server, 发送用户名、密码、当前数据库连接默认选择的数据库名
this.io.doHandshake(this.user, this.password,
this.database);
// 获取MySql数据库连接的连接ID
this.connectionId = this.io.getThreadId();
this.isClosed = false;
}
在集合中,找到注册的驱动,在createNewIO方法中去执行连接数据库的步骤。
6.总结
Connection是服务接口,driver是服务提供者接口,DriverManager.registerDriver是提供者注册API,DiverManager.getConnection是服务访问API。emmmmm.