设计模式与连接池:享元模式共享连接、工厂模式创建连接、代理模式管理连接

连接池这个东西,做后端开发的兄弟应该都不陌生。说白了,它就是一组预先创建好的数据库连接,放在一个池子里等着用。为什么要这么干?因为每次请求都去创建连接、销毁连接,开销太大了。我早年在一个电商项目里就吃过这个亏——并发一上来,数据库连接直接被打满,系统瞬间瘫痪。

那连接池跟设计模式有什么关系?关系大了去了。你仔细想想,连接池的核心逻辑,其实就是三个设计模式的组合拳:享元模式负责共享连接,工厂模式负责创建连接,代理模式负责管理连接。今天我就带你把这套组合拳拆开揉碎了讲清楚。

1. 享元模式:连接复用,避免重复创建

享元模式的核心思想就一句话:共享细粒度对象,减少内存占用。连接池里的连接,本质上就是享元对象。每个连接都是重量级的,创建一次不容易,能复用就复用。

我习惯把连接池里的连接分为两种状态:空闲态忙碌态。空闲的连接放在一个队列里,谁要用就拿走,用完再还回来。这就是典型的享元模式——内部状态(连接本身)不变,外部状态(当前谁在用)动态变化。

核心要点:享元模式在连接池中的体现,就是连接复用。你不需要每次请求都 new 一个 Connection,而是从池子里拿一个现成的。

来看一段简化的代码,感受一下:

// 享元对象:数据库连接
public class DatabaseConnection {
    private String url;
    private String username;
    private String password;
    private boolean inUse; // 外部状态:是否被占用

    public DatabaseConnection(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.inUse = false;
    }

    public void connect() {
        // 实际建立连接
        System.out.println("连接到数据库: " + url);
    }

    public void setInUse(boolean inUse) {
        this.inUse = inUse;
    }

    public boolean isInUse() {
        return inUse;
    }
}

嗯,这里要注意:享元对象本身不负责管理自己的状态。谁在用、用完了没有,这些信息是由连接池来维护的。这就是后面代理模式要干的事。

2. 工厂模式:统一创建,隐藏细节

连接不能凭空产生,得有个地方负责创建。工厂模式就是干这个的。我个人习惯把连接工厂设计成抽象工厂,这样不同的数据库(MySQL、PostgreSQL、Oracle)都可以通过同一个接口来创建连接。

你想想看,如果每个地方都直接 new Connection(),那换数据库的时候得改多少代码?用工厂模式,只需要改工厂的实现类就行了。我在一个老项目里就见过这种惨状——代码里到处都是 new OracleConnection(),后来要换成 MySQL,改了一个通宵。

// 抽象工厂
public interface ConnectionFactory {
    DatabaseConnection createConnection();
}

// MySQL 工厂
public class MySQLConnectionFactory implements ConnectionFactory {
    @Override
    public DatabaseConnection createConnection() {
        return new DatabaseConnection("jdbc:mysql://localhost:3306/mydb", "root", "password");
    }
}

// PostgreSQL 工厂
public class PostgreSQLConnectionFactory implements ConnectionFactory {
    @Override
    public DatabaseConnection createConnection() {
        return new DatabaseConnection("jdbc:postgresql://localhost:5432/mydb", "postgres", "password");
    }
}
我的建议:工厂模式不要只创建一种连接。把连接配置(URL、用户名、密码)也抽象出来,用 Builder 模式或者配置文件来管理。这样工厂只负责创建,配置由外部注入,职责更清晰。

3. 代理模式:管理连接,控制访问

连接从池子里拿出来了,用完了得还回去。但问题是,调用方可能直接调用 connection.close(),把连接给关了。这就麻烦了——连接一旦关闭,就不能再复用了。

代理模式就是来解决这个问题的。我给每个连接包一层代理,把 close() 方法拦截掉。调用方以为自己在关闭连接,实际上代理只是把连接标记为「空闲」,然后放回池子里。

我曾经在一个金融项目里遇到过这种坑:开发人员忘记归还连接,导致连接池被耗尽。后来我加了一层代理,在代理里做了超时检测——如果某个连接被占用超过 30 秒,自动回收并报警。效果立竿见影。

// 代理类:接管连接的生命周期
public class ConnectionProxy {
    private DatabaseConnection realConnection;
    private ConnectionPool pool;

    public ConnectionProxy(DatabaseConnection realConnection, ConnectionPool pool) {
        this.realConnection = realConnection;
        this.pool = pool;
    }

    // 代理 close 方法
    public void close() {
        // 不真正关闭,而是归还到池中
        realConnection.setInUse(false);
        pool.releaseConnection(realConnection);
        System.out.println("连接已归还到池中");
    }

    // 其他方法直接委托给真实连接
    public void executeQuery(String sql) {
        if (!realConnection.isInUse()) {
            throw new RuntimeException("连接已被释放");
        }
        System.out.println("执行查询: " + sql);
    }
}
注意:代理模式不是万能的。如果调用方通过反射直接调用真实连接的方法,代理就失效了。所以我在项目中一般会限制真实连接的访问权限,只暴露代理给外部使用。

4. 三种模式的协作流程

这三种模式不是各自为战,而是紧密协作的。我画了一张图,帮你理清它们之间的关系:

连接池中三种设计模式的协作流程 客户端 代理模式 管理连接生命周期 享元模式 共享连接对象 工厂模式 创建连接对象 连接池 空闲连接队列 忙碌连接集合 最大连接数 超时时间 等待队列 请求连接 获取/归还 创建新连接 管理状态 存储连接 放入池中

流程其实很简单:客户端请求连接 -> 代理模式拦截请求 -> 从享元模式的池子里拿空闲连接 -> 如果没有空闲,工厂模式创建新连接 -> 代理把连接包装后返回给客户端 -> 客户端用完调用 close() -> 代理拦截 close(),把连接归还到池中。

5. 完整代码示例

最后,我把三个模式整合到一起,给你一个完整的连接池实现。这个代码我在多个项目里都用过,核心逻辑基本不变。

// 连接池:整合三种设计模式
public class ConnectionPool {
    private List<DatabaseConnection> idleConnections = new ArrayList<>();
    private List<DatabaseConnection> busyConnections = new ArrayList<>();
    private ConnectionFactory factory;
    private int maxSize = 10;

    public ConnectionPool(ConnectionFactory factory) {
        this.factory = factory;
        // 初始化时创建 3 个连接
        for (int i = 0; i < 3; i++) {
            DatabaseConnection conn = factory.createConnection();
            conn.connect();
            idleConnections.add(conn);
        }
    }

    // 获取连接(代理模式)
    public ConnectionProxy getConnection() {
        DatabaseConnection conn = null;
        if (!idleConnections.isEmpty()) {
            conn = idleConnections.remove(0);
        } else if (busyConnections.size() < maxSize) {
            // 享元模式:没有空闲就创建新的
            conn = factory.createConnection();
            conn.connect();
        } else {
            throw new RuntimeException("连接池已满,无法获取连接");
        }
        conn.setInUse(true);
        busyConnections.add(conn);
        return new ConnectionProxy(conn, this);
    }

    // 归还连接(享元模式)
    public void releaseConnection(DatabaseConnection conn) {
        busyConnections.remove(conn);
        idleConnections.add(conn);
    }
}
避坑指南:我曾经在连接池里忘记做「最大连接数」的限制,结果生产环境被瞬间打爆。后来我加了一个等待队列——当连接池满时,请求进入等待队列,等有连接归还时再唤醒。这个机制在并发高的场景下特别重要。

好了,三种设计模式在连接池中的应用,我就讲到这里。说白了,享元模式管复用,工厂模式管创建,代理模式管生命周期。这三者配合好了,连接池就能稳定、高效地运行。你在实际项目中遇到连接池相关的问题,不妨从这三个角度去分析,往往能找到症结所在。


公众号:蓝海资料掘金营,微信deep3321