1、传统方式解决手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
UML 类图
问题分析
- 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
- 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
- 解决方案——使用桥接模式
2、桥接模式基本介绍
- 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
- 它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
举个例子说明下:
对于手机来说,我们可以根据手机品牌分类,也可以根据手机类型来分类。所以手机这个系统可以在这两个角度独立的变化,手机品牌的变化不影响手机类型的变化。桥接模式其实就是通过合成/聚合代替继承,实现了松耦合的、在各个不同角度的”独立地变化“。
原理类图
原理类图说明
- Client:桥接模式的调用者
- Abstraction:Abstraction 充当桥接类,维护了 Implementor,即 ConcreteImplementorA / ConcreteImplementorB
- RefinedAbstraction:Abstraction 抽象类的子类
- Implementor:行为实现类的接口
- ConcreteImplementorA / ConcreteImplementorB:行为的具体实现类
- 这里的抽象类和接口是聚合的关系,也是调用者和被调用者的关系
3、桥接模式解决手机操作问题
UML 类图
核心代码
// 行为接口——品牌接口
public interface Branch {
void open();
void call();
void close();
}
// 行为实现类——华为品牌
public class Huawei implements Branch {
@Override
public void open() {
System.out.println("华为手机开机");
}
@Override
public void call() {
System.out.println("华为手机打电话");
}
@Override
public void close() {
System.out.println("华为手机关机");
}
}
// 行为实现类——小米品牌
public class Xiaomi implements Branch {
@Override
public void open() {
System.out.println("小米手机开机");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
@Override
public void close() {
System.out.println("小米手机关机");
}
}
// 行为实现类——苹果品牌
public class iPhone implements Branch {
@Override
public void open() {
System.out.println("苹果手机开机");
}
@Override
public void call() {
System.out.println("苹果手机打电话");
}
@Override
public void close() {
System.out.println("苹果手机关机");
}
}
// 桥接类——手机抽象类
public abstract class Phone {
private Branch branch;
public Phone(Branch branch) {
this.branch = branch;
}
public void open() {
branch.open();
}
public void call() {
branch.call();
}
public void close() {
branch.close();
}
}
// 桥接子类——翻盖式手机
public class FlipPhone extends Phone {
public FlipPhone(Branch branch) {
super(branch);
System.out.println("翻盖式手机");
}
@Override
public void open() {
super.open();
}
@Override
public void call() {
super.call();
}
@Override
public void close() {
super.close();
}
}
// 桥接子类——滑盖式手机
public class SlidePhone extends Phone {
public SlidePhone(Branch branch) {
super(branch);
System.out.println("滑盖式手机");
}
@Override
public void open() {
super.open();
}
@Override
public void call() {
super.call();
}
@Override
public void close() {
super.close();
}
}
// 桥接子类——直立式手机
public class UprightPhone extends Phone {
public UprightPhone(Branch branch) {
super(branch);
System.out.println("直立式手机");
}
@Override
public void open() {
super.open();
}
@Override
public void call() {
super.call();
}
@Override
public void close() {
super.close();
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new FlipPhone(new Huawei());
phone.open();
phone.call();
phone.close();
System.out.println("======================");
phone = new FlipPhone(new Xiaomi());
phone.open();
phone.call();
phone.close();
System.out.println("======================");
phone = new UprightPhone(new iPhone());
phone.open();
phone.call();
phone.close();
}
}
4、JDBC 源码分析
说起jdbc,我相信很多人都不陌生,在最开始的web项目中,我们常常用它来连接数据库执行sql语句,下面是一个连接mysql的例子:
/*** 定义数据库连接辅助类*/
public class DBhelper {
private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://127.0.0.1:3306/test";
private static final String USER = "root";
private static final String PASSWORD = "123456";
private Connection conn = null;
private Statement st = null;
private PreparedStatement ppst = null;
private ResultSet rs = null; /*加载驱动*/
static {
try {
Class.forName(DRIVERNAME).newInstance();
} catch (Exception e) {
e.printStackTrace();
System.out.println("加载驱动失败");
}
} /*获取数据库连接*/
public Connection getConn() {
try {
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (SQLException throwables) {
throwables.printStackTrace();
System.out.println("获取数据库连接失败");
}
return conn;
} /*释放数据库连接*/
public void releaseConn() {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
if (ppst != null) {
try {
ppst.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
测试一下:
public class TestJdbc {
private Connection conn = null;
private Statement st = null;
private PreparedStatement ppst = null;
private ResultSet rs = null;
private List<Object> selectUser(User user) {
List<Object> list = new ArrayList<>();
DBhelper dBhelper = new DBhelper();
conn = dBhelper.getConn();
String sql = "select * from blog_user where login_num=" + user.getLoginNum() + " and password =" + user.getPassword();
try {
st = conn.createStatement();
rs = st.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
while (rs.next()) {
Map map = new HashMap();
for (int i = 1; i <= columnCount; i++) {
map.put(rsmd.getColumnLabel(i), rs.getObject(i));
}
list.add(map);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
dBhelper.releaseConn();
}
return list;
}
public static void main(String[] args) {
TestJdbc testJdbc = new TestJdbc();
User user = new User();
user.setLoginNum(123456);
user.setPassword("123456");
List list = testJdbc.selectUser(user);
}
}
debug一下:
然而在实际开发中,我们常常用到的框架是mybatis,其实mybatis就是对jdbc的封装,在我们以后的开发中我们可能会遇到关于持久层的各种问题,我们理解了jdbc的原理,那么mybatis又有何难?
依然以mysql为例,首先,我们来看数据库驱动Driver的加载过程:
在上面的贴出的代码中,我们可以看到一个静态代码块,利用class.forName()加载这个驱动,如下图:
private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";
/*加载驱动*/
static {
try {
Class.forName(DRIVERNAME).newInstance();
} catch (Exception e) {
e.printStackTrace();
System.out.println("加载驱动失败");
}
}
我们点进这个驱动,发现它继承了一个父类,实现了一个接口,里面有一个方法----注册驱动, 如下图:
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,如下图:
这是一个顶层接口(由于太长,注释删掉一部分),简单理解一下注释,我们就会明白,这是一个所有的驱动必须实现的方法,也就是说,这是一个java连接数据库的一个接口,一个规范,数据库有很多种,因此数据库驱动也分很多种,但是不管你是那种数据库驱动,必须都要实现这个接口,遵循这个规范,才能连接数据库,无疑给我们带来了很大的方便,更换数据库就代表着更换驱动,而所有驱动都实现了这个接口,那我们只需要在利用反射加载驱动的class.forName()方法中注明需要加载的驱动就ok了,这样就可以适配所有的数据库.
/**
* 每个驱动程序类必须实现的接口。
* <P>Java SQL 框架允许多个数据库驱动程序。 <P>
* 每个驱动程序都应提供一个实现 * 驱动程序接口的类。
* <P>DriverManager 将尝试加载尽可能多的驱动程序,然后对于任何给定的连接请求,
* 它将依次要求每个 * 驱动程序尝试连接到目标 URL。 <P>
* 强烈建议每个 Driver 类都应该是 * 小且独立的,以便 Driver 类可以加载和 * 查询,而无需引入大量支持代码。
* <P>加载 Driver 类时,它应该创建 * 本身的实例并将其注册到 DriverManager。
* 这意味着 * 用户可以通过调用以下命令来加载和注册驱动程序:
* <p> * {@code Class.forName(“foo.bah.Driver”)}
*/
public interface Driver {
/**
* 尝试与给定 URL 建立数据库连接。 *
* 如果驱动程序意识到连接到给定 URL 的驱动程序类型错误,则应返回“null”。
* 这很常见,因为当 * 要求 JDBC 驱动程序管理器连接到给定的 URL 时,
* 它会依次将 * URL 传递给每个加载的驱动程序。 *
*/
Connection connect(String url, java.util.Properties info) throws SQLException;
/**
* 检索驱动程序是否认为它可以打开与给定 URL 的连接 *。
* 通常,如果驱动程序 * 理解 URL 中指定的子协议,则返回 <code>true</code>,
* 如果 * 不理解,则返回 <code>false</code>。
*/
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
再看NonRegisteringDriver
类
它实现了Driver,实际上,它是mysql驱动的一部分,里面的一些方法是关于连接mysql数据库的一些配置细节,根据一些连接属性创建一个真正连接数据库的网络通道
public class NonRegisteringDriver implements Driver {
public static String getOSName() {
return Constants.OS_NAME;
}
public static String getPlatform() {
return Constants.OS_ARCH;
}
static int getMajorVersionInternal() {
return StringUtils.safeIntParse("8");
}
static int getMinorVersionInternal() {
return StringUtils.safeIntParse("0");
}
public NonRegisteringDriver() throws SQLException {
} //接收url,验证url的合法性
public boolean acceptsURL(String url) throws SQLException {
try {
return ConnectionUrl.acceptsUrl(url);
} catch (CJException var3) {
throw SQLExceptionsMapping.translateException(var3);
}
}
/**
* 根据给定的URL和属性信息建立数据库连接。
*
* @param url 数据库连接URL,用于指定连接的数据库类型和位置。
* @param info 属性信息,包含登录数据库所需的用户名和密码等信息。
* @return 返回与数据库建立的连接对象,如果无法建立连接则返回null。
* @throws SQLException 如果建立连接过程中发生错误,则抛出SQLException。
*/
public Connection connect(String url, Properties info) throws SQLException {
try {
try {
// 检查URL是否被当前驱动接受,如果不接受则直接返回null。
if (!ConnectionUrl.acceptsUrl(url)) {
return null;
} else {
// 根据URL和属性信息获取ConnectionUrl实例,用于解析连接URL并确定连接类型。
//负载均衡式访问
ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
// 根据连接类型创建并返回相应的连接对象。
switch (conStr.getType()) {
case SINGLE_CONNECTION:
return ConnectionImpl.getInstance(conStr.getMainHost());
case FAILOVER_CONNECTION:
case FAILOVER_DNS_SRV_CONNECTION:
return FailoverConnectionProxy.createProxyInstance(conStr);
case LOADBALANCE_CONNECTION:
case LOADBALANCE_DNS_SRV_CONNECTION:
return LoadBalancedConnectionProxy.createProxyInstance(conStr);
case REPLICATION_CONNECTION:
case REPLICATION_DNS_SRV_CONNECTION:
return ReplicationConnectionProxy.createProxyInstance(conStr);
default:
return null;
}
}
} catch (UnsupportedConnectionStringException var5) {
// 如果连接字符串不被支持,则返回null。
return null;
} catch (CJException var6) {
// 如果在连接过程中发生CJException,则将其转换为SQLException并抛出。
throw (UnableToConnectException) ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);
}
} catch (CJException var7) {
// 如果发生CJException,则将其转换为SQLException并抛出。
throw SQLExceptionsMapping.translateException(var7);
}
}
public int getMajorVersion() {
return getMajorVersionInternal();
}
public int getMinorVersion() {
return getMinorVersionInternal();
}
/**
* 获取驱动程序属性信息。
* 该方法通过分析给定的URL和属性,构造驱动程序需要的属性信息。它主要用于配置连接到数据库所需的属性。
*
* @param url 数据库连接URL,用于解析数据库类型、主机、端口、数据库名称等信息。
* @param info 已经存在的属性信息,可能包含主机、端口、数据库名称、用户和密码等信息。
* @return DriverPropertyInfo数组,包含所有必要的驱动程序属性信息。
* @throws SQLException 如果解析URL或获取属性信息过程中出现错误,则抛出SQLException。
*/
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
try {
// 初始化数据库连接所需的各个属性
String host = "";
String port = "";
String database = "";
String user = "";
String password = "";
// 如果URL不为空,则尝试解析URL以获取更多配置信息
if (!StringUtils.isNullOrEmpty(url)) {
ConnectionUrl connStr = ConnectionUrl.getConnectionUrlInstance(url, info);
// 如果是单个连接配置,提取主机信息
if (connStr.getType() == Type.SINGLE_CONNECTION) {
HostInfo hostInfo = connStr.getMainHost();
// 将主机信息转换为Properties对象
info = hostInfo.exposeAsProperties();
}
}
// 从info对象中提取出各个属性值
if (info != null) {
host = info.getProperty(PropertyKey.HOST.getKeyName());
port = info.getProperty(PropertyKey.PORT.getKeyName());
database = info.getProperty(PropertyKey.DBNAME.getKeyName());
user = info.getProperty(PropertyKey.USER.getKeyName());
password = info.getProperty(PropertyKey.PASSWORD.getKeyName());
}
// 创建驱动程序属性信息对象,并设置相应的属性和描述
DriverPropertyInfo hostProp = new DriverPropertyInfo(PropertyKey.HOST.getKeyName(), host);
hostProp.required = true;
hostProp.description = Messages.getString("NonRegisteringDriver.3");
DriverPropertyInfo portProp = new DriverPropertyInfo(PropertyKey.PORT.getKeyName(), port);
portProp.required = false;
portProp.description = Messages.getString("NonRegisteringDriver.7");
DriverPropertyInfo dbProp = new DriverPropertyInfo(PropertyKey.DBNAME.getKeyName(), database);
dbProp.required = false;
dbProp.description = Messages.getString("NonRegisteringDriver.10");
DriverPropertyInfo userProp = new DriverPropertyInfo(PropertyKey.USER.getKeyName(), user);
userProp.required = true;
userProp.description = Messages.getString("NonRegisteringDriver.13");
DriverPropertyInfo passwordProp = new DriverPropertyInfo(PropertyKey.PASSWORD.getKeyName(), password);
passwordProp.required = true;
passwordProp.description = Messages.getString("NonRegisteringDriver.16");
// 初始化JDBC属性集,并根据当前配置初始化属性
JdbcPropertySet propSet = new JdbcPropertySetImpl();
propSet.initializeProperties(info);
// 将JDBC属性集暴露为驱动程序属性信息
List<DriverPropertyInfo> driverPropInfo = propSet.exposeAsDriverPropertyInfo();
// 创建一个包含所有属性信息的数组
DriverPropertyInfo[] dpi = new DriverPropertyInfo[5 + driverPropInfo.size()];
// 将基本属性和额外的驱动程序属性信息添加到数组中
dpi[0] = hostProp;
dpi[1] = portProp;
dpi[2] = dbProp;
dpi[3] = userProp;
dpi[4] = passwordProp;
System.arraycopy(driverPropInfo.toArray(new DriverPropertyInfo[0]), 0, dpi, 5, driverPropInfo.size());
// 返回包含所有属性信息的数组
return dpi;
} catch (CJException var17) {
// 将内部CJException转换为SQLException并抛出
throw SQLExceptionsMapping.translateException(var17);
}
}
public boolean jdbcCompliant() {
return false;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
static {
try {
Class.forName(AbandonedConnectionCleanupThread.class.getName());
} catch (ClassNotFoundException var1) {
}
}
}
然后就是 DriverManager.registerDriver( new Driver() )
这个注册驱动的方法,它是将自己传给DriverManager
,我们点开这个方法:
这里将Driver
封装进DriverInfo
类中,添加在DriverManager
的静态List
中,便于DriverManager
管理驱动
/**
* class DriverManager
* 这段代码定义了一个私有的、静态的、常量registeredDrivers,其类型为CopyOnWriteArrayList<DriverInfo>。这个列表用于存储DriverInfo类型的元素。
* CopyOnWriteArrayList是Java并发编程中的一种线程安全的列表实现。它通过使用“写时复制”(Copy-on-Write)的策略来实现并发访问和修改。当有线程尝试修改列表时,会创建该列表的一个副本,并在副本上进行修改操作,而原列表则保持不变。这样可以确保在并发环境下,读操作的高效性和线程安全性。
* 在该代码中,registeredDrivers列表用于存储DriverInfo类型的元素,可以进行元素的添加、删除和查询等操作。由于使用了CopyOnWriteArrayList,因此在并发环境下对列表的操作是线程安全的。
*/
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
registerDriver(driver, null);
}
/**
* 注册驱动程序
* 该函数用于向DriverManager注册给定的 JDBC 驱动程序。
* 如果驱动程序已经注册,则不会采取任何行动。该方法接受两个参数:
* driver是要注册的 JDBC 驱动程序,
* da是当调用DriverManager#deregisterDriver时使用的DriverAction实现。
* 如果driver为null,则会抛出NullPointerException。
* 如果注册成功,则会在控制台打印registerDriver:加上驱动程序的信息。
* @param driver
* @param da
* @throws SQLException
*/
public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
/* 如果驱动程序尚未添加到我们的列表中,请注册它 */
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
//这是为了与原始 DriverManager 兼容
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
再点开: registeredDrivers.addIfAbsent(new DriverInfo(driver, da))
方法名大概是说 如果缺席(没有)就添加类里面有一个array,看注释,这是一个放置驱动类的临时数组,只能通过getArray
和setArray
获取和设置.
而 indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :addIfAbsent(e, snapshot)
是返回参数e
在数组snapshot
的下标,这里的 e
就是 上面的new DriverInfo(driver, da)
, snapshot
为上面提到的 array
当e为null,返回snapshot
中null的下标.如果snapshot
中没有e
,则返回 -1,也就是说其实是判断snapshot
中有没有e
,没有的话,就调用方法添加.
再看下面的添加方法,就是把 Driver
放进 array 中,相当于把驱动注册进DriverManager
中,至于这里为什么是一个数组?假如我们一个系统同时连接两种或者多种数据库,那我们就需要多个驱动,因此这里是一个数组,当我们需要连接哪种数据库的时候,就可以从这里取出对应的驱动去获取连接
private transient volatile Object[] array;
/**
* 查找元素在数组中的索引。
*
* @param o 要查找的元素,可以为null。
* @param elements 目标数组,可能包含null元素。
* @param index 搜索的起始索引。
* @param fence 搜索的结束界限,但不包括该索引本身。
* @return 如果找到元素,返回其索引;如果未找到,返回-1。
*
* 方法首先检查要查找的元素是否为null,然后遍历数组从指定索引开始直到指定的界限。
* 如果元素为null,则查找数组中的null元素;如果元素不为null,则使用equals方法进行匹配查找。
* 这种方法允许在数组中高效地查找特定元素,无论元素是否为null。
*/
private static int indexOf(Object o, Object[] elements, int index, int fence) {
if (o == null) {
// 如果要查找的元素为null,则在数组中查找null元素。
for (int i = index; i < fence; i++) if (elements[i] == null) return i;
} else {
// 如果要查找的元素不为null,则使用equals方法进行匹配查找。
for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i;
}
// 如果未找到匹配的元素,返回-1。
return -1;
}
/**
* 在集合中添加元素e,仅当e不存在于集合中时添加。
*
* @param e 要添加到集合中的元素。
* @return 如果元素已存在,则返回false;如果元素成功添加,则返回true。
*/
public boolean addIfAbsent(E e) {
// 获取当前集合的元素数组快照,用于后续判断元素是否已存在
Object[] snapshot = getArray();
// 判断元素e是否已存在于集合中
if (indexOf(e, snapshot, 0, snapshot.length) >= 0) {
return false; // 元素已存在,不添加,返回false
} else {
// 元素不存在,调用addIfAbsent方法实际添加元素
return addIfAbsent(e, snapshot);
}
}
/**
* 该函数在同步锁定的情况下,检查给定的元素e是否存在于数组中。
* 如果不存在,则将e添加到数组中,并返回true;
* 如果存在,则返回false。函数首先比较给定的快照数组snapshot和当前数组是否相等,
* 如果不相等,则遍历两个数组中长度较小的部分,
* 检查e是否已经存在。如果e不存在,则继续查找剩余部分。
* 如果e仍然不存在,则创建一个新数组,将e添加到新数组末尾,
* 然后将新数组设置为当前数组。最后返回true表示成功添加元素。
*
* @param e 要添加到集合的元素。
* @param snapshot 快照数组,用于比较以确定元素是否已经存在。
* @return 如果元素成功添加到集合中,则返回true;如果元素已经存在,则返回false。
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取锁以确保线程安全
try {
Object[] current = getArray(); // 获取当前的元素数组
int len = current.length; // 获取当前数组的长度
if (snapshot != current) { // 检查快照数组是否是当前的元素数组
// 针对另一个 addXXX 操作的失败竞争进行了优化
int common = Math.min(snapshot.length, len); // 计算快照数组和当前数组的长度的较小值
for (int i = 0; i < common; i++) {
if (current[i] != snapshot[i] && eq(e, current[i])) {
return false; // 如果在相同位置上元素不相同且新元素已存在,则返回false
}
}
if (indexOf(e, current, common, len) >= 0) {
return false; // 如果新元素在当前数组的剩余部分中存在,则返回false
}
}
Object[] newElements = Arrays.copyOf(current, len + 1); // 创建一个新数组,长度为当前数组长度加一
newElements[len] = e; // 将新元素添加到新数组的末尾
setArray(newElements); // 设置新数组为当前的元素数组
return true; // 返回true,表示新元素已成功添加
} finally {
lock.unlock(); // 释放锁
}
}
我们再看如何获取连接Connection
, DriverManager
遍历其中的所有的驱动,然后获取该驱动的连接,这种方法或许有点笨,但是可以兼容所有的数据库驱动,而这里真正连接数据库的操作Connection con = aDriver.driver.connect(url, info)
;调用的是NonRegisteringDriver
中的connect()
方法,返回一个Connection
实例
/**
* 根据给定的URL、属性和调用者类加载器获取一个数据库连接。
* 此方法是getConnection方法的实现,它尝试通过已注册的驱动程序来建立连接。
*
* @param url 数据库连接URL,不能为null。
* @param info 连接属性,可以为null。
* @param caller 调用者的类,用于获取类加载器,可以为null。
* @return 数据库连接对象。
* @throws SQLException 如果无法建立连接或URL为null。
*/
// Worker method called by the public getConnection() methods.
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
// 根据调用者类加载器获取合适的类加载器
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
// 确保线程安全地处理类加载器
synchronized (DriverManager.class) {
// 同步加载正确的类加载器。
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
// 检查URL是否为null
if (url == null) {
throw new SQLException("The url cannot be null", "08001");
}
// 打印获取连接的日志信息
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 尝试通过每个已注册的驱动程序建立连接
for (DriverInfo aDriver : registeredDrivers) {
// 检查驱动程序是否允许被调用者使用
if (isDriverAllowed(aDriver.driver, callerCL)) {
try {
// 尝试连接到数据库
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// 连接成功,返回连接对象
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
// 记录第一个发生的SQLException
if (reason == null) {
reason = ex;
}
}
} else {
// 跳过不被允许的驱动程序
println(" skipping: " + aDriver.getClass().getName());
}
}
// 如果有SQLException发生,抛出该异常
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
// 如果没有找到合适的驱动程序,抛出SQLException
println("getConnection: no suitable driver found for " + url);
throw new SQLException("No suitable driver found for " + url, "08001");
}
再来看返回的连接 Connection,这也是一个接口,定义了一些数据库连接都要有的方法会用到的方法,我们看一下它的继承类图,如下:
package java.sql;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* <P>与特定 * 数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果 *。*
* <pre> * java.util.Map map = con.getTypeMap();
* * map.put(“mySchemaName.ATHLETES”, Class.forName(“运动员”));
* * con.setTypeMap(地图); * </pre>
* * * @see DriverManager#getConnection
* * @see语句
* * @see ResultSet * @see DatabaseMetaData
*/
public interface Connection extends Wrapper, AutoCloseable {
//所有的数据库连接都要有的方法
Statement createStatement() throws SQLException;
PreparedStatement prepareStatement(String sql) throws SQLException;
CallableStatement prepareCall(String sql) throws SQLException;
String nativeSQL(String sql) throws SQLException;
void setAutoCommit(boolean autoCommit) throws SQLException;
boolean getAutoCommit() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
boolean isClosed() throws SQLException;
}
public interface MysqlConnection { //略
// 关于mysql连接的一些方法
}
public interface JdbcConnection extends Connection, MysqlConnection, TransactionEventHandler {
//略
}
public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {
//略
}
根据上面的分析,如图所示:
JDBC 的 Driver 接口:如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver、Oracle 的 Driver,这些就可以当做实现接口类
Connection 继承体系
Driver源码
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 {
// Required for Class.forName().newInstance()
}
}
DriverManager 结构
说明
- MySQL 有自己的 Connectionlmpl 类,同样 Oracle 也有对应的实现类
- Driver 和 Connection 之间是通过 DriverManager 类进行桥连接的
5、注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景
6、桥接模式其他应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
常见的应用场景
- JDBC 驱动程序
- 银行转账系统
- 转账分类:网上转账、柜台转账、AMT 转账
- 转账用户类型:普通用户、银卡用户、金卡用户
- 消息管理
- 消息类型:即时消息、延时消息
- 消息分类:手机短信、邮件消息、QQ消息…