大白话说Java设计模式-23-桥接模式(源码剖析篇)

📌 PDF :AI人工智能 --- AI Agent智能体实战项目

大白话说Java设计模式-23-桥接模式(源码剖析篇):JDK / AWT / Logging 中的桥接实现

📌 一句话本质:桥接在源码里不是"两个维度"的形象化,而是"抽象和实现彻底分离"的工业级架构。

🏷️ 标签:桥接模式 / JDBC DriverManager / AWT Component Peer / Java Logging 🎯 适合:中高级后端 / 想读懂主流框架源码的工程师


目录

  • 一、为什么读桥接源码?一张地图先看清
  • [二、JDK 源码剖析:JDBC DriverManager](#二、JDK 源码剖析:JDBC DriverManager)
  • [三、JDK 源码剖析:AWT Component / Peer](#三、JDK 源码剖析:AWT Component / Peer)
  • [四、JDK 源码剖析:Java Logging Logger / Handler](#四、JDK 源码剖析:Java Logging Logger / Handler)
  • 五、为什么框架作者这么设计?
  • [六、借鉴到我们项目:3 个"抄作业"实践](#六、借鉴到我们项目:3 个"抄作业"实践)
  • [七、本篇小结 + 下一模式预告](#七、本篇小结 + 下一模式预告)

一、为什么读桥接源码?一张地图先看清

桥接模式在企业级框架里出现得极其关键------它解决了"多维度变化"的经典难题。

源码地图:

类别 框架 / 中间件 抽象 实现
JDK JDBC DriverManager Driver
JDK AWT Component ComponentPeer
JDK Swing JComponent LookAndFeel / UIResource
JDK Java Logging Logger Handler
JDK JNDI Context InitialContextFactory
JDK Preferences Preferences PreferencesFactory
Spring Spring AOP AopProxy AopProxyFactory
MyBatis MyBatis SqlSessionFactory SqlSessionFactoryBuilder
Hibernate Hibernate SessionFactory ConnectionProvider

我们按"JDBC → AWT → Logging"的顺序来剖析。


二、JDK 源码剖析:JDBC DriverManager

2.1 JDBC 的桥接结构

JDBC 是桥接模式的"教科书级"实现:

复制代码
DriverManager(抽象 / 客户端 API)
  ├── Driver driver  ← 桥(持有驱动引用)
  ├── MySQL Driver
  ├── PostgreSQL Driver
  ├── Oracle Driver
  └── SQL Server Driver

关键点:

  • 抽象 :DriverManager.getConnection()(客户端 API)
  • 实现 :Driver 接口(具体驱动)
  • 桥 :DriverManager 持有 Driver 引用

2.2 DriverManager 核心源码

源码位置:

  • OpenJDK 17
  • src/java.sql/share/classes/java/sql/DriverManager.java

核心源码(简化版):

java 复制代码
/**
 * ✅ 抽象:DriverManager
 * <p>
 * 管理一组 JDBC 驱动,提供统一的连接获取 API
 */
public class DriverManager {

    /**
     * ✅ 已注册的驱动列表(实现类)
     */
    private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    /**
     * ✅ 桥:客户端只调 getConnection()
     */
    @CallerSensitive
    public static Connection getConnection(String url, String user, String password) throws SQLException {
        // 1️⃣ 找到合适的 Driver
        Driver driver = getDriver(url);

        // 2️⃣ 调 Driver 的 connect
        return driver.connect(url, new Properties());
    }

    /**
     * ✅ 找到合适的 Driver
     */
    private static Driver getDriver(String url) throws SQLException {
        for (DriverInfo aDriver : registeredDrivers) {
            // 如果是当前调用方的 ClassLoader,跳过
            if (isDriverAllowed(aDriver.driver, Reflection.getCallerClass())) {
                try {
                    // ✅ 调用 Driver.acceptsURL()
                    if (aDriver.driver.acceptsURL(url)) {
                        return aDriver.driver;
                    }
                } catch (SQLException e) {
                    // ...
                }
            }
        }
        throw new SQLException("No suitable driver found for " + url);
    }

    /**
     * ✅ 注册驱动
     */
    public static synchronized void registerDriver(Driver driver) throws SQLException {
        registeredDrivers.addIfAbsent(new DriverInfo(driver));
    }
}

关键点:

关键点 解释
registeredDrivers 实现类列表(多种数据库驱动)
getConnection() 客户端统一 API
Driver.acceptsURL() 桥(选择实现)

2.3 Driver 接口(实现)

核心源码:

java 复制代码
/**
 * ✅ 实现接口:Driver
 * <p>
 * 每个数据库驱动实现这个接口
 */
public interface Driver {

    /**
     * 接受 URL(判断是否能处理)
     */
    boolean acceptsURL(String url) throws SQLException;

    /**
     * 连接数据库
     */
    Connection connect(String url, Properties info) throws SQLException;

    /**
     * 获取属性
     */
    DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException;

    /**
     * 主版本
     */
    int getMajorVersion();

    /**
     * 次版本
     */
    int getMinorVersion();
}

2.4 MySQL 驱动实现

核心源码(简化版):

java 复制代码
/**
 * ✅ 具体实现:MySQL 驱动
 */
public class com.mysql.cj.jdbc.Driver implements Driver {

    static {
        try {
            // ✅ 自动注册
            DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        } catch (SQLException e) {
            throw new RuntimeException("Can't register MySQL driver!");
        }
    }

    @Override
    public boolean acceptsURL(String url) throws SQLException {
        return url != null && url.startsWith("jdbc:mysql:");
    }

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        // 实际建立 MySQL 连接
        return new com.mysql.cj.jdbc.ConnectionImpl(url, info);
    }
}

2.5 完整 JDBC 桥接调用

复制代码
应用代码
  ↓
DriverManager.getConnection("jdbc:mysql://localhost:3306/db")
  ↓ 桥:找到 MySQL Driver
com.mysql.cj.jdbc.Driver.acceptsURL() → true
  ↓ 桥:调 connect
com.mysql.cj.jdbc.Driver.connect()
  ↓
Connection(MySQL 连接)

关键点:

关键点 解释
客户端只调 DriverManager 不知道底层是 MySQL 还是 Oracle
切换数据库 改 URL,不改代码
加新数据库 加新 Driver,DriverManager 不动

2.6 JDBC 桥接的"作者选择"

设计点 选择 原因
Driver 接口 统一规范 数据库厂商实现
DriverManager 注册 桥接点 运行时发现
SPI 机制 ServiceLoader 自动加载
acceptsURL 选择实现 灵活

三、JDK 源码剖析:AWT Component / Peer

3.1 AWT 的桥接结构

AWT 是经典的桥接模式应用:

复制代码
Component(抽象:界面)
  ├── ComponentPeer peer  ← 桥(持有平台相关实现)
  ├── Button → ButtonPeer(Windows / Linux / Mac 不同实现)
  ├── TextField → TextFieldPeer
  └── Container → ContainerPeer

关键点:

  • 抽象 :Component(跨平台 Java 接口)
  • 实现 :ComponentPeer(平台相关原生组件)
  • 桥 :Component 持有 ComponentPeer

3.2 Component 抽象类

源码位置:

  • OpenJDK 17
  • src/java.desktop/share/classes/java/awt/Component.java

核心源码(简化版):

java 复制代码
/**
 * ✅ 抽象:AWT Component
 * <p>
 * 跨平台的 Java 组件接口
 */
public abstract class Component implements ImageObserver, MenuContainer, Serializable {

    /**
     * ✅ 桥:持有平台相关的 Peer
     */
    transient volatile ComponentPeer peer;

    /**
     * 通用操作
     */
    public void setLocation(int x, int y) {
        // 1. Java 层处理
        this.x = x;
        this.y = y;

        // 2. ✅ 桥:调 peer
        if (peer != null) {
            peer.setLocation(x, y);
        }
    }

    public void setSize(int width, int height) {
        this.width = width;
        this.height = height;
        // ✅ 桥:调 peer
        if (peer != null) {
            peer.setSize(width, height);
        }
    }
}

关键点:

关键点 解释
peer 桥(持有平台相关实现)
Java 层 跨平台(位置/大小)
Peer 层 平台相关(Windows / Linux / Mac)

3.3 ComponentPeer 接口(实现)

核心源码:

java 复制代码
/**
 * ✅ 实现接口:ComponentPeer
 * <p>
 * 平台相关的原生组件接口
 */
public interface ComponentPeer {

    void setLocation(int x, int y);

    void setSize(int width, int height);

    void setVisible(boolean v);

    void dispose();

    // ... 其他平台相关方法
}

3.4 平台相关实现

Windows 实现(WComponentPeer):

java 复制代码
/**
 * ✅ 具体实现:Windows 平台
 */
public class WComponentPeer implements ComponentPeer {

    @Override
    public void setLocation(int x, int y) {
        // Windows API:SetWindowPos
        // 实际调用 Windows 底层
    }
}

/**
 * Linux 实现(`XComponentPeer`)
 */
public class XComponentPeer implements ComponentPeer {

    @Override
    public void setLocation(int x, int y) {
        // X11 API:XMoveWindow
        // 实际调用 X11 底层
    }
}

3.5 AWT 桥接的"作者选择"

设计点 选择 原因
Component 抽象 跨平台 Java 接口 一套代码
ComponentPeer 实现 平台相关原生 性能
Peer 桥 持有平台实现 灵活切换
Toolkit 创建 Peer 工厂

四、JDK 源码剖析:Java Logging Logger / Handler

4.1 Java Logging 的桥接结构

Java Logging 也是桥接模式:

复制代码
Logger(抽象:日志记录器)
  ├── Handler handler  ← 桥(持有日志输出器)
  ├── ConsoleHandler
  ├── FileHandler
  └── SocketHandler

关键点:

  • 抽象 :Logger(日志记录 API)
  • 实现 :Handler 接口(具体输出位置)
  • 桥 :Logger 持有 Handler 列表

4.2 Logger 抽象类

源码位置:

  • OpenJDK 17
  • src/java.logging/share/classes/java/util/logging/Logger.java

核心源码(简化版):

java 复制代码
/**
 * ✅ 抽象:Logger
 * <p>
 * 提供日志记录 API
 */
public class Logger {

    /**
     * ✅ 桥:日志处理器列表
     */
    private final CopyOnWriteArrayList<Handler> handlers = new CopyOnWriteArrayList<>();

    /**
     * 记录日志
     */
    public void log(LogRecord record) {
        // 1️⃣ 过滤
        if (!isLoggable(record.getLevel())) {
            return;
        }

        // 2️⃣ 调过滤器
        // ...

        // 3️⃣ ✅ 桥:调所有 Handler
        for (Handler handler : handlers) {
            handler.publish(record);
        }
    }

    /**
     * 添加 Handler
     */
    public void addHandler(Handler handler) {
        handlers.add(handler);
    }
}

4.3 Handler 接口(实现)

核心源码:

java 复制代码
/**
 * ✅ 实现接口:Handler
 */
public abstract class Handler {

    /**
     * 输出日志
     */
    public abstract void publish(LogRecord record);

    /**
     * 刷新
     */
    public abstract void flush();

    /**
     * 关闭
     */
    public abstract void close();
}

4.4 具体实现

java 复制代码
/**
 * ✅ 具体实现:控制台 Handler
 */
public class ConsoleHandler extends Handler {

    @Override
    public void publish(LogRecord record) {
        System.err.println(record.getMessage());
    }

    @Override
    public void flush() { System.err.flush(); }

    @Override
    public void close() { }
}

/**
 * ✅ 具体实现:文件 Handler
 */
public class FileHandler extends Handler {

    private final String filePath;

    public FileHandler(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void publish(LogRecord record) {
        // 写入文件
    }
}

4.5 Logging 桥接的"作者选择"

设计点 选择 原因
Logger 抽象 客户端 API 统一记录
Handler 实现 多种输出 灵活
多 Handler 列表 多个输出
Filter / Formatter 装饰 增强

五、为什么框架作者这么设计?

读源码看的是"实现",悟的是"设计哲学"。

5.1 框架作者选桥接的 4 个核心考虑

序号 考虑 举例
① 两个独立维度变化 JDBC 类型 × 数据库
② 避免多继承 AWT 界面 × 平台
③ 运行时动态组合 Logging Logger × Handler
④ 保护抽象不变 抽象稳定,实现多变

5.2 桥接 vs 继承:源码里怎么选?

场景 选哪个 原因
多维度变化 桥接 组合
单维度 继承 简单
运行时切换 桥接 灵活
编译期确定 继承 稳定

5.3 框架作者不选桥接的 3 种情况

序号 场景 原因
① 只有一个维度 桥接过度设计
② 维度不变化 用枚举
③ 业务简单 普通类

5.4 桥接在企业级应用的 3 种典型用法

复制代码
用法 1:JDBC 数据库切换
       DriverManager × Driver
       → 切换数据库不改业务

用法 2:AWT 跨平台
       Component × ComponentPeer
       → 一套代码跑遍所有平台

用法 3:日志多输出
       Logger × Handler
       → 同时输出控制台和文件

六、借鉴到我们项目:3 个"抄作业"实践

6.1 实践 1:多支付渠道桥接

场景:大白商城多支付渠道(一次性/分期 × 支付宝/微信)。

完整实现:

java 复制代码
/**
 * 借鉴 JDBC DriverManager:支付桥接
 */
public interface PaymentDriver {
    boolean accepts(String channel);
    void pay(BigDecimal amount);
}

/**
 * 支付宝驱动
 */
public class AlipayDriver implements PaymentDriver {
    @Override
    public boolean accepts(String channel) {
        return "alipay".equals(channel);
    }
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("支付宝支付: " + amount);
    }
}

/**
 * 微信驱动
 */
public class WechatDriver implements PaymentDriver {
    @Override
    public boolean accepts(String channel) {
        return "wechat".equals(channel);
    }
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("微信支付: " + amount);
    }
}

/**
 * 支付客户端(桥)
 */
public class PaymentClient {

    private final List<PaymentDriver> drivers = new ArrayList<>();

    public PaymentClient(List<PaymentDriver> driverList) {
        this.drivers.addAll(driverList);
    }

    public void pay(String channel, BigDecimal amount) {
        // 找驱动
        PaymentDriver driver = drivers.stream()
                .filter(d -> d.accepts(channel))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("不支持: " + channel));

        // 桥:调驱动
        driver.pay(amount);
    }
}

// 使用
PaymentClient client = new PaymentClient(List.of(
        new AlipayDriver(),
        new WechatDriver()
));
client.pay("alipay", new BigDecimal("100"));

6.2 实践 2:多输出日志桥接

场景:大白商城日志同时输出控制台、文件、远程。

完整实现:

java 复制代码
/**
 * 借鉴 Java Logging:日志桥接
 */
public interface LogHandler {
    void publish(String message);
}

/**
 * 控制台输出
 */
public class ConsoleHandler implements LogHandler {
    @Override
    public void publish(String message) {
        System.out.println("[CONSOLE] " + message);
    }
}

/**
 * 文件输出
 */
public class FileHandler implements LogHandler {

    private final String filePath;

    public FileHandler(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void publish(String message) {
        // 写入文件
    }
}

/**
 * 远程输出
 */
public class RemoteHandler implements LogHandler {
    @Override
    public void publish(String message) {
        // HTTP 上报
    }
}

/**
 * 日志器(桥)
 */
public class Logger {

    private final List<LogHandler> handlers = new ArrayList<>();

    public void addHandler(LogHandler handler) {
        handlers.add(handler);
    }

    public void log(String message) {
        // 桥:调所有 handler
        for (LogHandler handler : handlers) {
            handler.publish(message);
        }
    }
}

// 使用
Logger logger = new Logger();
logger.addHandler(new ConsoleHandler());
logger.addHandler(new FileHandler("/var/log/app.log"));
logger.addHandler(new RemoteHandler());
logger.log("Hello");
// 同时输出:控制台 + 文件 + 远程

6.3 实践 3:跨平台 UI 桥接

场景:大白商城客户端需要同时支持 Web / 移动端 / 桌面。

完整实现:

java 复制代码
/**
 * 借鉴 AWT:跨平台 UI 桥接
 */
public interface UIPeer {
    void render(String content);
    void onClick(String event);
}

/**
 * Web 实现
 */
public class WebUIPeer implements UIPeer {
    @Override
    public void render(String content) {
        // 渲染 HTML
    }
    @Override
    public void onClick(String event) {
        // 浏览器事件
    }
}

/**
 * 移动端实现
 */
public class MobileUIPeer implements UIPeer {
    @Override
    public void render(String content) {
        // 渲染原生界面
    }
    @Override
    public void onClick(String event) {
        // 移动端事件
    }
}

/**
 * UI 组件(桥)
 */
public class UIComponent {

    private UIPeer peer;

    public void setPeer(UIPeer peer) {
        this.peer = peer;
    }

    public void show(String content) {
        peer.render(content);
    }
}

// 使用
UIComponent component = new UIComponent();
component.setPeer(new WebUIPeer());
component.show("订单详情");

收益:

优化点 效果
多支付统一 任意组合支付
多输出日志 灵活配置
跨平台 UI 业务代码不变

七、本篇小结 + 下一模式预告

7.1 本篇小结(5 个核心要点)

  1. JDBC DriverManager :经典桥接,抽象和数据库驱动独立。
  2. AWT Component / Peer :跨平台桥接,Java 接口和原生实现分离。
  3. Java Logging Logger / Handler :日志桥接,Logger 和输出位置独立。
  4. 设计哲学 :抽象稳定 + 实现多变 + 桥作为连接点。
  5. 借鉴价值 :支付/日志/UI,任何多维度组合都用桥接。

7.2 一句话总结

桥接在源码里不是"两个维度"的形象化,而是"抽象和实现彻底分离"的工业级架构 。JDBC 让数据库可换,AWT 让平台可换,Logging 让输出可换。学桥接,先学 JDBC。

7.3 知识脑图

复制代码
桥接源码剖析
├── JDBC
│   ├── DriverManager(抽象)
│   ├── Driver(实现接口)
│   └── MySQLDriver(具体实现)
├── AWT
│   ├── Component(抽象)
│   ├── ComponentPeer(实现接口)
│   ├── WComponentPeer(Windows)
│   └── XComponentPeer(Linux)
├── Java Logging
│   ├── Logger(抽象)
│   ├── Handler(实现接口)
│   ├── ConsoleHandler
│   └── FileHandler
└── 借鉴价值
    ├── 多支付渠道桥接
    ├── 多输出日志桥接
    └── 跨平台 UI 桥接

7.4 下一模式预告

第 24 篇【享元模式 - 业务实战篇】:大白商城商品 SKU 共享的"内存减肥药"

下一篇我们会实战享元模式,回答 4 个问题:

  1. 商品标签 / 库存 SKU 怎么共享减少内存?
  2. 享元 vs 单例 / 缓存 / 对象池怎么区分?
  3. Java 的 Integer.valueOf 怎么用享元?
  4. 享元 vs 原型怎么选?

并附完整的商品享元实战代码(含内部状态 + 外部状态 + 单元测试),可直接复制到 IDEA 跑。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
IT枫斗者枫哥1 天前
MyBatis一对多分页:LIMIT 20,为什么凑不齐20个订单?
java·数据库
代码方舟1 天前
Java数据工程:利用天远全网运营商三要素优化线上实名认证合规体验
java·人工智能
BBmmo1 天前
我的 Java 学习笔记 · 第 7 篇:泛型与通配符
java
独泪了无痕1 天前
Hutool之RandomUtil:随机数生成的终极利器
java·后端
花开路口1 天前
深入理解 Java/Kotlin 协变与逆变
java·kotlin
运行时异常1 天前
【WMS 仓储系统集成 AI Agent 实战】第 7 讲:Vue3 前端工程化——Token 刷新锁、Markdown 渲染踩坑与权限体系
java
她的男孩1 天前
开放接口限流从 20 改到 200 还是每分钟 20 次:拆完防重放+幂等+限流,我找到 5 个静默失效的坑
java·后端·架构
天天被压力1 天前
【Python 量化取数指南 #13】Python 把行情落库:sqlite 一键存,回测随用随取
java·人工智能·python
天天被压力1 天前
【Python 量化取数指南 #14】Python 清洗行情数据:复权停牌对齐,回测不翻车
java·人工智能·python
独泪了无痕1 天前
开发利器Hutool之MapUtil的使用
java·后端