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


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

相关推荐
维天说7 小时前
Agent会听人话,反而更难管
java·开发语言·人工智能
SamDeepThinking7 小时前
if嵌套最好控制在3层以内
java·后端·程序员
边境悍匪7 小时前
蜗牛学苑 Java 智能体学习 Day36|阿里云 OSS 文件上传、Spring 事务、全局异常思维导图复盘
java·学习·阿里云
苏渡苇7 小时前
Spring Insight 里上报 Span 为什么用 JDK HttpClient 而不是 RestTemplate
java·后端·spring·spring cloud·springboot·性能监控
裕晟资质规划7 小时前
军工保密资质二级申报的四个可量化硬条件:条文位置、数值口径与西安配套企业实务要点
java·服务器·网络·数据库·算法
初願致夕霞7 小时前
C/C++传统程序内存分布(代码实测)
java·c语言·c++
掉鱼的猫7 小时前
不只是 Token 流:Solon AI 4.1 的语义化聊天事件
java·openai
她的男孩7 小时前
同一个接口,为什么销售只看到自己的订单?我拆了 681 行数据权限拦截器的源码
java·后端·架构
卓怡学长7 小时前
w157基于springboot北部湾地区助农平台
java·spring boot·spring·maven·intellij-idea
小白羊丨7 小时前
问数 Agent 整体架构、意图识别、历史上下文与 NL2SQL
java·面试·架构