大白话说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 跑


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

相关推荐
long3161 小时前
枚举(Enums)
java·开发语言·数据库
墨雨晨曦881 小时前
2026/08/15 spring AI学习总结
java·tomcat
wno7042 小时前
Spring Boot JdbcTemplate配置Druid多数据源
java·spring boot·后端
聊浮游2 小时前
JAVA2026最新全套学习资料、学习路线
java·开发语言·jvm·mysql·spring·maven·idea
MacroZheng2 小时前
完美替代 Navicat!这款内置 AI 的数据库工具,太香了!
java·后端·mysql
月华路3 小时前
Guava 图数据结构 graph 从基础概念到原理分析
java·数据结构·guava·图搜索
SamDeepThinking3 小时前
从REST到gRPC,一个API选型的思考框架
java·后端·程序员
Zane19943 小时前
接口都能写默认实现了,为什么还需要抽象类
java·后端
未秃头的程序猿3 小时前
读Spring AI源码的方法——不是硬啃,是带着问题去翻
java·后端·spring