一:为什么需要工厂类
核心:解耦对象的创建与使用,让代码更易扩展、更易维护
1.先来理解一下什么是代码耦合与解耦
(1)、核心概念
| 术语 | 定义 |
|---|---|
| 耦合 | 模块之间相互依赖的程度 |
| 解耦 | 降低模块之间的依赖,让彼此更独立 |
简单来说:模块A内部使用了模块B,此时模块A就依赖了模块B,如果模块B内部的某个方法名字改变了,那么模块A如果调用了模块B的这个方法,那么模块A也要同步修改方法名,不然无法调用,这个就是耦合。按照博主来看,耦合限制了代码的可维护性和扩展性
(2)耦合的讲解
1. 耦合对可维护性的影响:修改风险高、理解成本大
可维护性指的是修改代码的难易程度。耦合高时,维护工作会变得非常困难:
- 修改的连锁反应:如果你想修改一个模块,必须连带检查和修改所有与之耦合的模块,比如依赖关系A->B->C,修改C要同步修改B,修改B又要同步修改A,因为一个模块的内部变化,可能会破坏依赖于它的其他模块。
- 极高的理解成本:由于模块间依赖复杂,要修改一个地方,必须先理清背后一连串的关系,就像解开一团乱麻。这种心智负担会严重拖慢开发效率。
- 难以安全测试:高耦合的代码很难单独测试。因为模块A依赖于模块B,要测试A就必须先把B搭建起来。如果B还依赖于C、D,测试环境会变得极其复杂,导致问题难以及时发现。
2. 耦合对可扩展性的影响:难以复用、难以替换
可扩展性指的是在不影响现有系统的情况下,添加新功能或修改旧功能的难易程度。耦合同样会在这方面造成阻碍:
- 代码难以复用:如果你想复用某个模块,会发现它“拖家带口”。因为它与太多外部模块捆绑在一起,导致无法独立地应用到新项目中。
- 功能难以替换:高耦合意味着具体实现被硬编码。例如,一个支付模块直接使用了
new AliPay(),而不是一个通用的Pay接口。当你想换成微信支付时,就必须修改这个模块的内部代码,重新new WeiXinPay(),如果还要更换支付方式就还要去支付模块硬编码的去重新new,无法做到简单地“即插即用”。 - 开发互相阻塞:在团队协作中,如果模块高度耦合,任务就无法并行。例如,张三在写订单模块,李四在写物流模块,但两者代码写死了互相调用,那双方就必须等对方写完才能联调,造成时间浪费。
举个例子:高耦合
假设有一个OrderService(订单服务),需要记录日志。
高耦合的设计
class OrderService {
// 直接依赖了具体的文件日志类
private FileLogger logger = new FileLogger();
public void createOrder() {
// ... 业务逻辑
logger.write("订单创建成功"); // 硬编码调用
}
}
class FileLogger {
public void write(String message) {
// 将日志写入文件
}
}
- 问题:依赖关系OrderService->FileLogger,
OrderService直接创建了FileLogger。如果想改成数据库日志,就必须修改OrderService的代码,破坏了“开闭原则”。
(3)解耦的讲解
核心概念:解耦 = 降低模块之间的相互依赖,让彼此独立变化
| 高耦合的问题 | 后果 |
|---|---|
| 一改A模块,B/C/D都要跟着改 | 维护成本高 |
| 无法单独测试A模块 | 必须搭全套环境 |
| 复用困难 | 拆出来带着一堆依赖 |
| 协作冲突 | 多人改同一块代码 |
解耦的实现方式
1. 面向接口编程(Interface-based Programming)
最根本的手段 模块 A 不直接调用模块 B 的具体类,而是调用一个接口(Interface)。 B 只需实现该接口,A 完全不知道 B 的内部细节。
// 1. 定义一个通知接口(抽象)
interface NotificationChannel {
void send(String message);
}
// 2. 实现具体渠道
class SmsChannel implements NotificationChannel {
@Override
public void send(String message) {
System.out.println("发送短信:" + message);
}
}
class EmailChannel implements NotificationChannel {
@Override
public void send(String message) {
System.out.println("发送邮件:" + message);
}
}
// 3. 订单服务现在依赖接口,而不是具体类
public class OrderService {
// 虽然这里是接口,但我们还需要处理具体传进来的对象
private NotificationChannel channel;
// 通过构造器把具体的实现传进来(这就是依赖注入的雏形)
public OrderService(NotificationChannel channel) {
this.channel = channel;
}
public void createOrder(String orderId) {
System.out.println("订单创建成功:" + orderId);
// 调用接口方法,不再关心具体是什么渠道
channel.send("您的订单 " + orderId + " 已创建。");
}
}
// 客户端调用
public class Main {
public static void main(String[] args) {
// 想用短信,就传入短信对象
NotificationChannel channel = new SmsChannel();
OrderService orderService = new OrderService(channel);
orderService.createOrder("123456");
// 想用邮件,只需换传入的对象,OrderService内部不用改
// NotificationChannel channel2 = new EmailChannel();
// OrderService orderService2 = new OrderService(channel2);
}
}
需要注意的是:
public OrderService(NotificationChannel channel)接受的是接口类型的参数,只要是接口及接口的实现类,都可以接受
channel.send("您的订单 " + orderId + " 已创建。");这里调用接口的方法,本质是调用实现类重写的send方法
举个例子,Sms和Email都是NotificationChannel接口的实现类,且重写了send方法。我们OrderService都可以接受他们两个类,如果我们传入Sms,那么我们调用createOrder()方法,本质就是去调用Sms重写后的send方法
或许可能会问:为什么还要单独设置OrderService这个类,你main函数中 NotificationChannel channel = new SmsChannel();这里不还是硬编码了吗,直接new SmsChannel()了,以后SmsChannel名字变了,你不还要再这里同步改名字吗?
这确实是个好问题,博主以前也是这么想的,为什么还要设计OrderService类。但是后来也明白了,我们这里实现的是OrderService类解耦了,但是main函数还没解耦。
- 现在我们的orderService接受的参数都是接口类,意味着他并不关心你到底传了哪个实现类
- 以前的OrderService要接受Sms类的参数就要在内部定义变量 private Sms sms,你main函数传递Sms对象给OrderService类,OrderService的sms字段接受你的Sms对象,用sms变量的send方法发送短信,你有没有发现OrderService就和Sms类耦合在一起了。你想让OrderService实现发送短信功能,就必须在内部定义Sms类型的字段接受Sms对象。如果Sms类名字改成NewSms这个新名字,OrderService字段是不是也要一起改?如果你还要添加QQ类型的发送形式,你也想让OrderService可以新增QQ发送形式,是不是还要新增一个QQ字段?这不就再次耦合在一起了吗,OrderService想实现多种发送形式就必须要提前知道到底有哪些发送形式
- 现在通过接口的接收形式,OrderService不关心你到底是Sms,QQ还是Weixin的发送方式,你只要是接口实现类,那我统统接受,然后调用接口的send方法(本质还是调用传入的接口实现重写后的send方法),实现你们各自的发送形式,即使你们修改名字了,对OrderService还是没有影响,因为你们不都还是接口实现类吗,这不就实现OrderService解耦了吗
然后就是解答为什么要设置OrderService,而不能在main函数中直接new的形式创建实现类,调用send方法
- 这里本质只是拿OrderService举个例子,真是项目中功能太多太多了。就比如我们上面的订单发送方式就有好几种,我们不可能说这里需要WeiXin发送就单独用WeiXin类,那里要QQ就直接调用QQ,这样代码就耦合在一块了。而是把功能抽取成一个模块,比如发送模块就包含上面几种发送方式。我们需要发送时,只需要调用模块统一的方法就能实现方式,这样项目才好管理,而不是这里放一块,那里放一块。上面的OrderService就可以理解为发送模块,他就代替我们进行发送服务了
2.spring的@Autowired自动注入机制
Spring @Autowired:控制反转(IoC)
// Spring 方式 - 依赖"送"上门
@Service
public class OrderService {
@Autowired
private UserService userService; // 只声明接口,不管谁实现
@Autowired
private EmailService emailService; // Spring 自动装配正确的bean
public void createOrder() {
userService.validateUser(); // 只管使用,不管创建
}
只要在Autowired注解下注明我们要注入的类,容器就会自动去查找这个类并且注入进来,前提是一定要将该类注入Bean对象,放进容器才行,容器只能对加入到容器的类进行管理
解耦的核心机制
依赖查找 → 依赖注入(控制反转)
传统方式: Spring方式:
┌─────────────┐ ┌─────────────┐
│ OrderService │ │ OrderService │
│ ↓ new │ │ ↑注入 │
│ UserService │ │ UserService │
└─────────────┘ └─────────────┘
主动创建 被动接收(IoC容器控制)
关键转变:对象不再控制依赖的创建,控制权交给 Spring 容器(IoC)。我们只需要申明要注入的对象,不用自己new,容器会帮我们创建并注入进来
这里需要讲解一下我们一般怎么用@Autowired注解进行解耦的,我们一般只声明要注入的接口类型
@Autowired
private UserService userService; // 只声明接口,不管谁实现,该接口的实现类就自动注入进来了
那么容器就会自动查找接口实现类并且注入,这样实现类的名字再怎么变,他都实现类该接口,我们都可以用声明接口的形式将该实现类注入进来,这就实现了解耦
或许还有人问:如果一个接口有多个实现类,那么我们只声明接口型,容器怎么区分哪些是我们要注入的实现类
下面是提供的四种解决方案
假设场景:
public interface MessageSender {
void send(String msg);
}
@Component // 默认 bean 名称为 "smsSender"
public class SmsSender implements MessageSender { ... }
@Component // 默认 bean 名称为 "emailSender"
public class EmailSender implements MessageSender { ... }
方案1:@Primary(首选方案)
@Component
@Primary // 标记为主要实现,默认注入这个
public class EmailSender implements MessageSender { ... }
// 使用处无需改动,默认注入 EmailSender
@Autowired
private MessageSender sender; // 拿到 EmailSender
方案2:@Qualifier(精确指定,注入时指定实现类名称)
@Autowired
@Qualifier("smsSender") // 指定 bean 名称(类名首字母小写)
private MessageSender smsSender;
@Autowired
@Qualifier("emailSender")
private MessageSender emailSender;
方案3:字段名/参数名匹配(Spring 4.3+)
@Autowired
private MessageSender smsSender; // 字段名 = bean 名称,自动匹配
@Autowired
private MessageSender emailSender; // 字段名 = bean 名称,自动匹配
原理:Spring 会先按类型找,找到多个时,再用字段名匹配 bean 名称。
方案4:注入集合/Map(全部获取)
// 获取所有实现类
@Autowired
private List<MessageSender> allSenders; // [SmsSender, EmailSender]
// 或按名称映射
@Autowired
private Map<String, MessageSender> senderMap;
// {"smsSender": SmsSender, "emailSender": EmailSender}
// 使用时动态选择
public void broadcast(String msg) {
allSenders.forEach(s -> s.send(msg)); // 全渠道发送
三、方案对比
表格
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| @Primary | 有默认实现,偶尔需要其他的 | 代码简洁 | 只能有一个默认 |
| @Qualifier | 明确知道要哪个 | 精确控制 | 硬编码 bean 名 |
| 字段名匹配 | 简单场景 | 少写注解 | 重构时易出错 |
| 集合注入 | 策略模式、广播场景 | 扩展性好 | 需自行遍历选择 |
注意,这里不推荐注解注入实现类的形式
@Autowired
private UserServiceImpl userService; // 具体实现类
注意:直接注入类会丧失解耦优势,但语法上是允许的。
2.再来理解一下为什么需要工厂类
一、传统 new 关键字的核心问题
直接用 new 创建对象,看似简单,但在复杂业务场景下会暴露以下痛点:
1. 耦合度极高
new 具体类() 会让调用方和具体的类强绑定。比如:
// 调用方直接new,和MySQLConnection强耦合
DatabaseConnection conn = new MySQLConnection("localhost", 3306, "user", "pass");
如果后续要替换成 OracleConnection,必须修改所有写了 new MySQLConnection() 的代码,同时需要修改数据库url等等,违反「开闭原则」(对扩展开放,对修改关闭)。
2. 对象创建逻辑复杂,代码冗余
如果对象创建需要:
- 初始化参数(如数据库连接的 URL、用户名、密码)
- 前置校验(如检查配置是否合法)
- 资源准备(如加载驱动、建立连接池)
- 异常处理(如连接失败重试)
这些逻辑会散落在所有 new 对象的地方,导致代码重复、维护困难。
3. 无法统一管理对象生命周期
调用方各自 new 对象,无法统一控制对象的创建、复用、销毁。比如需要实现「连接池复用连接」「单例对象」,直接 new 根本做不到。
4. 隐藏依赖,可读性差
调用方只需要「一个数据库连接」,但 new MySQLConnection() 要求调用方必须知道:
- MySQLConnection 的构造参数
- 依赖的驱动类
- 初始化顺序这些细节本该被封装,却暴露给了调用方。
1.就是说调用方只想创建一个数据库连接,但是他却需要传递完整的创建数据库连接所需要的一系列对象,太过冗余了,如果我这里需要使用一个数据库连接,就需要
new MySQLConnection("localhost", 3306, "user", "pass"),那里需要就在new一个,填写完整参数,是不是太麻烦了
2.如果我们有一个工厂类,我们将数据库连接的参数都设置给工厂,让他代替我们去创建数据库连接,我们哪里需要数据库连接,就在哪里调用工厂,由工厂统一创建,不再需要手动传递参数.同时如果我们想修改连接的数据库地址,只需要修改工厂参数,填入正确的数据库url,就行了.是不是就方便很多了
3.如果不好理解,我们就把工厂想象成一个方法,如下图的getMysqlConnection,我们在方法中定义了数据库连接的参数,这样我们哪里需要数据库连接就在哪里调用方法,需要修改数据库信息时在方法中修改,我们调用方只需要调用getMysqlConnection()方法即可,不用关注更多数据库细节,这就类似于工厂
private MySqlConnection getMysqlConnection()
{
return new MySQLConnection("localhost", 3306, "user", "pass")
}
二、工厂类如何解决这些问题
工厂类的核心思想是:将对象的创建逻辑封装到专门的类(工厂类)中,调用方只需要告诉工厂「我要什么」,不需要知道「怎么创建」。
1. 解耦:隔离调用方和具体实现
工厂类提供统一的接口,调用方只依赖接口 / 抽象类,不依赖具体实现类。
// 工厂类封装创建逻辑
public class ConnectionFactory {
// 调用方只传类型,不需要知道具体类
public static Connection createConnection(String dbType) {
if ("mysql".equals(dbType)) {
return new MySQLConnection();
} else if ("oracle".equals(dbType)) {
return new OracleConnection();
}
throw new IllegalArgumentException("不支持的数据库类型");
}
}
// 调用方使用(只依赖工厂和接口,和具体类解耦)
Connection conn = ConnectionFactory.createConnection("mysql");
后续替换数据库类型,只需要修改工厂类,调用方代码完全不用动。
2. 封装复杂创建逻辑,消除冗余
把对象创建的复杂逻辑(参数、校验、资源)都放到工厂里,调用方只需一行代码:
public class ConnectionFactory {
public static Connection createConnection(String dbType) {
// 1. 加载配置(复杂逻辑封装)
Properties prop = loadConfig("db.properties");
String url = prop.getProperty(dbType + ".url");
String user = prop.getProperty(dbType + ".user");
String pwd = prop.getProperty(dbType + ".pwd");
// 2. 校验参数
if (url == null || user == null) {
throw new RuntimeException("数据库配置缺失");
}
// 3. 创建对象
if ("mysql".equals(dbType)) {
return new MySQLConnection(url, user, pwd);
} else if ("oracle".equals(dbType)) {
return new OracleConnection(url, user, pwd);
}
throw new IllegalArgumentException("不支持的数据库类型");
}
// 私有方法:加载配置(调用方无需关心)
private static Properties loadConfig(String path) {
// 实现加载逻辑...
}
}
// 调用方极简:只传类型,无需关心配置/校验/创建细节
Connection conn = ConnectionFactory.createConnection("mysql");
3. 统一管理对象生命周期
工厂类可以控制对象的创建规则(单例、池化、缓存),而调用方无感知:
// 工厂类实现连接池(复用对象,避免频繁new)
public class ConnectionFactory {
// 连接池:缓存创建好的连接
private static Map<String, Connection> pool = new HashMap<>();
public static Connection createConnection(String dbType) {
// 先查连接池,有则复用,无则创建
if (pool.containsKey(dbType)) {
Connection conn = pool.get(dbType);
if (conn.isValid()) {
return conn;
}
}
// 创建新连接并放入池
Connection newConn = createNewConnection(dbType);
pool.put(dbType, newConn);
return newConn;
}
// 私有方法:真正创建新连接
private static Connection createNewConnection(String dbType) {
// 实现创建逻辑...
}
}
4. 隐藏依赖,提升可读性
调用方只需关注「获取对象」的目标,无需关注创建对象的依赖和细节,代码意图更清晰:
// 差:调用方要知道所有细节
Connection conn = new MySQLConnection("jdbc:mysql://localhost:3306/db", "root", "123456");
// 好:调用方只关注目标,细节由工厂封装
Connection conn = ConnectionFactory.createConnection("mysql");
三、工厂类的适用场景
不是所有场景都需要工厂类:
- 简单对象(如
new String()、new ArrayList()):直接new更简洁,无需工厂; - 复杂对象(创建需配置 / 校验 / 资源)、需要灵活替换实现、需要统一管理对象:工厂类价值极大。
四、工厂类的一些细节问题
问题:调用方不再直接关心数据库连接的逻辑了,但是调用方不就直接调用工厂了吗,这不就形成调用方与工厂的耦合了吗,同时如果我们要换数据库连接了不还是要修改工厂类吗
你提的这个问题非常关键,也是很多新手理解工厂模式时最容易产生的疑问 ——调用方确实和工厂类产生了耦合,但这是一种「更优的耦合」,且工厂模式本身也有进阶方案来解决「修改工厂逻辑」的问题。
我们一步步拆解这个问题:
1.先明确:耦合不可消除,只能「优化耦合」
软件开发中,完全无耦合的代码是不存在的(否则代码无法协作)。我们的目标不是消灭耦合,而是降低耦合:
- 让耦合发生在「低风险、易维护」的地方;
- 把「强耦合」(和具体实现类耦合)变成「弱耦合」(和抽象 / 工厂耦合);
- 把「多处耦合」收敛为「一处耦合」。
对比:传统 new vs 工厂类的耦合
| 维度 | 传统 new 具体类 | 工厂类 |
|---|---|---|
| 耦合对象 | 调用方 ↔ 具体实现类 | 调用方 ↔ 工厂类 |
| 耦合范围 | 所有调用 new 的地方 | 仅工厂类一处 |
| 变更成本 | 改 N 处代码 | 改 1 处工厂代码 |
| 对调用方的影响 | 调用方需要感知实现细节 | 调用方完全无感知 |
举个实际例子:
- 用
new MySQLConnection():100 个调用方都写了这句,换 Oracle 要改 100 处; - 用
ConnectionFactory.createConnection("mysql"):换 Oracle 只需改工厂类里的逻辑,100 个调用方一行代码都不用动。
核心:工厂类把「分散的耦合」收敛成了「集中的耦合」,这是质的提升 —— 修改一处,影响全局,而不是修改百处,容易遗漏。
2.如何解决「修改工厂逻辑」的问题?
你担心的「改工厂逻辑」确实存在(比如简单工厂模式的缺点),但可以通过「工厂方法模式」「抽象工厂模式」进一步解耦,让工厂类也符合「开闭原则」。
a. 简单工厂的问题(你质疑的点)
我们之前写的简单工厂,核心问题是:新增数据库类型(如 PostgreSQL)时,必须修改工厂类的 if/else 逻辑,需要修改工厂类代码,违反开闭原则。
// 简单工厂:新增类型必须改这里
public static Connection createConnection(String dbType) {
if ("mysql".equals(dbType)) {
return new MySQLConnection();
} else if ("oracle".equals(dbType)) { // 新增类型要加else if
return new OracleConnection();
}
// ...
}
b. 工厂方法模式:让工厂也「可扩展」
思路:
- 定义「抽象工厂接口」,每个具体实现类对应一个「具体工厂类」,每个工厂类只生产特定产品,如MySql工厂只生产Mysql连接,Redis工厂只生成Redis连接.如果需要增加工厂就必须实现抽象工厂接口;
- 新增类型时,只需要新增具体工厂类,无需修改原有代码。
代码示例:
// 1. 连接接口(产品接口)
public interface Connection {
void connect();
// 新增:获取连接的基础信息(用于后续校验)
String getUrl();
}
// 2. 具体产品:MySQLConnection(创建逻辑变复杂)
public class MySQLConnection implements Connection {
private String url;
private String username;
private String password;
// 构造方法:需要传入配置,且创建时要校验
public MySQLConnection(String url, String username, String password) {
if (url == null || !url.startsWith("jdbc:mysql://")) {
throw new IllegalArgumentException("MySQL URL格式错误");
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public void connect() {
System.out.println("连接MySQL:" + url);
}
@Override
public String getUrl() {
return url;
}
}
// 3. 抽象工厂接口(不变,但定义了「创建连接」的统一规范)
public interface ConnectionFactory {
Connection createConnection(); // 工厂方法:封装所有创建逻辑
}
// 4. 具体工厂:MySQLConnectionFactory(工厂方法的核心作用体现)
public class MySQLConnectionFactory implements ConnectionFactory {
// 工厂内部封装配置(调用方无需知道)
private static final String MYSQL_URL = "jdbc:mysql://localhost:3306/test";
private static final String MYSQL_USER = "root";
private static final String MYSQL_PWD = "123456";
@Override
public Connection createConnection() {
// 工厂方法封装「复杂创建逻辑」:
// 1. 加载配置(这里简化为常量,实际可从配置文件读)
// 2. 校验配置(比如检查密码是否为空)
if (MYSQL_PWD == null || MYSQL_PWD.isEmpty()) {
throw new RuntimeException("MySQL密码不能为空");
}
// 3. 创建具体产品(调用复杂的构造方法)
MySQLConnection conn = new MySQLConnection(MYSQL_URL, MYSQL_USER, MYSQL_PWD);
// 4. 额外逻辑:比如测试连接是否可用
testConnection(conn);
// 5. 返回产品
return conn;
}
// 工厂内部的私有方法:测试连接(调用方无需关心)
private void testConnection(Connection conn) {
System.out.println("测试连接:" + conn.getUrl() + " → 可用");
}
}
// 5. 调用方使用(核心:调用方完全不用关心创建逻辑)
public class Client {
public static void main(String[] args) {
// 调用方只依赖「抽象工厂接口」,且一行代码就能创建复杂对象
ConnectionFactory factory = new MySQLConnectionFactory();
Connection conn = factory.createConnection();
conn.connect();
// 换Oracle:只需替换工厂,调用逻辑完全不变
// factory = new OracleConnectionFactory();
// conn = factory.createConnection();
// conn.connect();
}
}
工厂方法到底「有用」在哪里?
从上面的示例能清晰看到,createConnection() 这个工厂方法的核心价值体现在 3 个层面:
- 封装「对象创建的复杂逻辑」,隔离调用方
-
调用方视角:只需要
factory.createConnection()一行代码,就能拿到可用的Connection对象,完全不用知道:- MySQL 连接需要什么 URL 格式;
- 密码不能为空的校验规则;
- 创建后还要测试连接是否可用;
- 配置存在哪里(常量 / 配置文件 / 数据库)。
-
工厂方法视角:把所有「创建对象的脏活累活」都包揽了,调用方只需「结果」,不用管「过程」。
- 定义「创建产品的统一规范」,解耦实现
- 抽象工厂接口
ConnectionFactory定义了createConnection()方法,这是所有具体工厂必须遵守的「创建规范」—— 不管是 MySQL、Oracle 还是 PostgreSQL 工厂,都必须实现这个方法,返回Connection类型。 - 调用方只依赖
ConnectionFactory接口,不依赖任何具体工厂 / 具体产品:哪怕后续新增PostgreSQLConnectionFactory,调用方的代码也不用改(只需替换工厂实例)。
- 隔离「产品创建的变更风险」
如果后续 MySQL 的创建逻辑变了(比如 URL 格式改了、需要加超时时间、密码要加密),只需要修改 MySQLConnectionFactory 的 createConnection() 方法,调用方和其他工厂(如 Oracle)完全不受影响。
核心:
1.我们可以进一步拆分工厂类,定义一个抽象工厂,让抽象工厂生产特定工厂,特定工厂只需生产一种产品即可,比如MysqlConnectionFactory只生产MysqlConnection。
2.我们又定义了一个Connection接口,提供了connect接口方法,它就像一个契约,不管是Redis,MySql数据库还是别的,都需要实现该接口,统一格式,重写connect方法,这样即使不同数据库的连接机制不同,我们只需要调用connect方法即可获得连接,MySqlConnection就实现了该接口
3.抽象工厂提供一个基础接口方法createConnection(),所有工厂类均要实现这个接口,具体建立连接的逻辑都要写在这个方法中,返回一个Connection对象,即已建立的连接。用户只需要调用该方法即可获取数据库连接,不用关心实现细节
c. 终极解耦:结合「配置 + 反射」(实战常用)
如果想让调用方连「具体工厂类」都不耦合,可以用配置文件 + 反射:
-
在配置文件(如
factory.properties)中指定要使用的工厂类:db.factory=com.example.MySQLConnectionFactory -
工厂类通过反射动态加载:
public class FactoryLoader { // 加载配置文件,反射创建工厂实例 public static ConnectionFactory loadFactory() { Properties prop = new Properties(); prop.load(FactoryLoader.class.getClassLoader().getResourceAsStream("factory.properties")); String factoryClass = prop.getProperty("db.factory"); // 反射创建工厂 return (ConnectionFactory) Class.forName(factoryClass).newInstance(); } } // 调用方:完全不耦合任何具体类,只依赖接口 public class Client { public static void main(String[] args) { ConnectionFactory factory = FactoryLoader.loadFactory(); Connection conn = factory.createConnection(); conn.connect(); // 换数据库:只需改配置文件,代码一行不用动 } }
3、核心认知:设计模式是「权衡」,不是「银弹」
- 工厂模式的目标不是「零修改」,而是「最小化修改」「最小化影响范围」;
- 简单工厂适合「实现类变化少」的场景(比如固定几种数据库),工厂方法适合「频繁新增实现类」的场景;
- 不要过度设计:如果你的业务中实现类几乎不会变,简单工厂甚至直接
new都是合理的 —— 设计模式是解决问题的,不是炫技的。
总结
- 调用方与工厂耦合是「可接受的最优解」:将分散在各处的耦合收敛到工厂类,把「改 N 处」变成「改 1 处」,是耦合的优化而非恶化;
- 工厂方法模式解决「修改工厂逻辑」的问题:通过抽象工厂 + 具体工厂,新增实现类时只需新增工厂类,无需修改原有工厂代码,符合开闭原则;
- 实战中可结合配置 + 反射:让调用方完全依赖接口,连具体工厂类都不耦合,实现「零代码修改」切换实现。
五.总结
- 传统
new的核心问题:调用方与具体类强耦合、创建逻辑冗余、无法统一管理对象、暴露不必要的细节; - 工厂类的解决思路:将对象创建逻辑封装到专门的工厂类中,调用方只依赖工厂的统一接口,不依赖具体实现;
- 工厂类的核心价值:解耦(符合开闭原则)、封装复杂逻辑、统一管理对象生命周期、简化调用方代码。
简单来说,工厂类把「怎么造对象」和「用对象」分离开,让代码更易维护、更灵活。
二:工厂类的分类
1. 简单工厂(Simple Factory)
也叫「静态工厂」,是最基础的工厂模式,核心是用一个工厂类封装所有产品的创建逻辑,类比上面的例子,就是说该工厂类不仅生产MySql连接,还生成Redis连接等等,即一个工厂生产多个产品。
核心特点
- 只有一个工厂类,通常用
static方法创建产品; - 调用方传「类型参数」(如字符串 / 枚举),工厂通过
if/else/switch返回具体产品; - 不属于 GoF 23 种设计模式,但实际开发中最常用(简单易上手)。
代码示例(数据库连接场景)
// 产品接口
public interface Connection {
void connect();
}
// 具体产品
class MySQLConnection implements Connection {
@Override
public void connect() { System.out.println("MySQL连接"); }
}
class OracleConnection implements Connection {
@Override
public void connect() { System.out.println("Oracle连接"); }
}
// 简单工厂类(核心)
public class SimpleConnectionFactory {
// 静态方法:根据类型创建产品
public static Connection createConnection(String dbType) {
switch (dbType) {
//用户传递mysql就生产mysql的连接
case "mysql": return new MySQLConnection();
case "oracle": return new OracleConnection();
default: throw new IllegalArgumentException("不支持的数据库类型");
}
}
}
// 调用方
public class Client {
public static void main(String[] args) {
Connection conn = SimpleConnectionFactory.createConnection("mysql");
conn.connect();
}
}
适用场景
- 产品类型少(比如固定 2-3 种),且几乎不会新增;
- 不想让调用方关心产品创建细节,只想快速获取对象。
优缺点
| 优点 | 缺点 |
|---|---|
| 简单易实现,调用方只需传参数 | 新增产品必须修改工厂类的 if/else,违反「开闭原则」 |
| 封装了产品创建逻辑,解耦调用方和具体产品 | 工厂类会随着产品增多变得臃肿(「上帝类」问题 |
2. 工厂方法(Factory Method)
工厂方法模式(Factory Method Pattern)是创建型设计模式的核心之一,也是对「简单工厂模式」的优化升级。它的核心思想是:定义一个创建对象的抽象接口(工厂接口),让具体的子类(具体工厂)决定实例化哪一个产品类—— 简单说就是「把对象的创建权,交给具体的工厂子类」。
(1)、先搞懂工厂方法模式的核心角色
为了方便理解,我们先明确模式中的 4 个核心角色(用数据库连接场景举例):
| 角色 | 作用 | 示例(数据库场景) |
|---|---|---|
| 产品接口(Product) | 定义所有产品的通用行为,是具体产品的抽象 | Connection 接口(定义 connect() 方法) |
| 具体产品(Concrete Product) | 实现产品接口的具体类,是最终要创建的对象 | MySQLConnection、OracleConnection |
| 工厂接口(Creator) | 定义创建产品的「工厂方法」,返回产品接口类型 | ConnectionFactory 接口(定义 createConnection() 方法) |
| 具体工厂(Concrete Creator) | 实现工厂接口,重写工厂方法,创建对应的具体产品 | MySQLConnectionFactory、OracleConnectionFactory |
(2)、工厂方法模式的核心逻辑(通俗解释)
你可以把工厂方法模式理解为:
- 你(调用方)需要一个「数据库连接」,但不想自己
new具体的连接类; - 你找到一个「连接工厂总店」(工厂接口),总店规定所有分店(具体工厂)都必须能「造连接」;
- 「MySQL 分店」(MySQL 工厂)只造 MySQL 连接,「Oracle 分店」(Oracle 工厂)只造 Oracle 连接;
- 你想要哪种连接,就找对应的分店要 —— 你只认「总店的规则」(工厂接口),不认具体分店,也不用管
(3)、完整代码示例
// 1. 产品接口:定义所有数据库连接的通用行为
public interface Connection {
void connect(); // 核心行为:连接数据库
}
// 2. 具体产品1:MySQL连接(实现产品接口)
public class MySQLConnection implements Connection {
@Override
public void connect() {
System.out.println("创建MySQL数据库连接");
}
}
// 3. 具体产品2:Oracle连接(实现产品接口)
public class OracleConnection implements Connection {
@Override
public void connect() {
System.out.println("创建Oracle数据库连接");
}
}
// 4. 工厂接口:定义创建产品的规范(工厂方法)
public interface ConnectionFactory {
// 工厂方法:返回产品接口类型,具体造什么由子类决定
Connection createConnection();
}
// 5. 具体工厂1:MySQL工厂(只造MySQL连接)
public class MySQLConnectionFactory implements ConnectionFactory {
@Override
public Connection createConnection() {
// 这里可以封装复杂的创建逻辑(比如加载驱动、校验参数)
return new MySQLConnection();
}
}
// 6. 具体工厂2:Oracle工厂(只造Oracle连接)
public class OracleConnectionFactory implements ConnectionFactory {
@Override
public Connection createConnection() {
return new OracleConnection();
}
}
// 7. 调用方使用(核心:只依赖接口,不依赖具体实现)
public class Client {
public static void main(String[] args) {
// 步骤1:选择具体工厂(想要MySQL连接,就用MySQL工厂)
ConnectionFactory factory = new MySQLConnectionFactory();
// 步骤2:通过工厂方法创建产品(拿到的是接口类型)
Connection conn = factory.createConnection();
// 步骤3:使用产品(无需关心具体是哪种连接)
conn.connect();
// 切换成Oracle连接:只需换工厂,调用逻辑完全不变
factory = new OracleConnectionFactory();
conn = factory.createConnection();
conn.connect();
}
}
(4)、工厂方法模式的核心优势(解决了什么问题)
- **严格遵守「开闭原则」**新增一种数据库连接(比如 PostgreSQL),只需新增
PostgreSQLConnection(具体产品) +PostgreSQLConnectionFactory(具体工厂),原有代码一行都不用改—— 而简单工厂需要修改工厂类的if/else,违反开闭原则。 - 单一职责原则每个具体工厂只负责创建一种产品:MySQL 工厂只造 MySQL 连接,Oracle 工厂只造 Oracle 连接,工厂类的逻辑不会随着产品增多而臃肿(避免了简单工厂的「上帝类」问题)。
- 解耦程度更高调用方只依赖「工厂接口」和「产品接口」,完全不依赖具体的工厂 / 产品实现类。哪怕底层换了数据库连接的实现逻辑,调用方代码也不受影响。
- 灵活的扩展性可以为不同产品定制不同的创建逻辑:比如 MySQL 连接需要加载特定驱动,Oracle 连接需要校验特殊参数,这些逻辑都封装在各自的工厂里,互不干扰。
(5)、工厂方法模式的适用场景
- 当你不知道(或不想固定)要创建哪种具体产品时;
- 当产品类型较多,且需要频繁新增产品时;
- 当希望把对象创建逻辑和业务逻辑分离时;
- 当需要让子类决定创建哪种对象时(比如框架开发中,让用户自定义扩展)。
(6)、和简单工厂的核心区别(一张表看懂)
| 维度 | 简单工厂 | 工厂方法 |
|---|---|---|
| 工厂数量 | 只有 1 个工厂类 | 一个产品对应一个具体工厂类 |
| 开闭原则 | 新增产品需修改工厂类(违反) | 新增产品只需新增工厂类(符合) |
| 代码复杂度 | 低(类少) | 高(类数量翻倍) |
| 适用场景 | 产品少、几乎不新增 | 产品多、频繁新增 |
(7)、总结
- 核心定义:工厂方法模式通过「抽象工厂接口 + 具体工厂子类」,把对象创建的逻辑分散到各个具体工厂,让调用方只依赖抽象接口;
- 核心价值:符合开闭原则,新增产品无需修改原有代码,解耦调用方与具体产品;
- 核心思想:「把创建权交给子类」,让每个具体工厂专注创建自己的产品,做到职责单一。
简单来说,工厂方法模式就是「为每个产品配一个专属工厂」,既保证了扩展性,又让代码逻辑更清晰。
3.抽象工厂
抽象工厂模式(Abstract Factory Pattern)是创建型设计模式中更高级的工厂模式,是工厂方法模式的「升级版」。它的核心思想是:定义一个创建「产品族」的抽象接口,让具体的工厂子类负责创建某一整个产品族的所有产品—— 简单说就是「工厂方法造单个产品,抽象工厂造一套产品」。
(1)、先搞懂抽象工厂模式的核心概念
在理解抽象工厂前,必须先分清两个关键概念(用「电子产品」举例更易理解):
表格
| 概念 | 定义 | 示例(数据库场景) |
|---|---|---|
| 产品等级结构 | 同一类产品的不同实现(比如「手机」下的华为手机、苹果手机) | Connection 接口下的 MySQLConnection、OracleConnection;DataSource 接口下的 MySQLDataSource、OracleDataSource |
| 产品族 | 同一品牌 / 场景下的多个不同等级的产品(比如「华为全家桶」:华为手机 + 华为平板 + 华为手表) | MySQL 产品族:MySQLConnection(连接) + MySQLDataSource(数据源) + MySQLTransaction(事务);Oracle 产品族:OracleConnection + OracleDataSource + OracleTransaction |
抽象工厂的核心就是「造产品族」,而工厂方法只是「造单个产品等级结构里的产品」
(2)、抽象工厂模式的核心角色
延续数据库场景,抽象工厂包含 5 个核心角色(比工厂方法多了「多产品等级结构」):
表格
| 角色 | 作用 | 示例(数据库场景) |
|---|---|---|
| 抽象产品 A(Product A) | 第一个产品等级结构的接口 | Connection 接口(定义 connect()) |
| 具体产品 A1(Concrete Product A1) | 产品 A 的具体实现(属于产品族 1) | MySQLConnection |
| 具体产品 A2(Concrete Product A2) | 产品 A 的具体实现(属于产品族 2) | OracleConnection |
| 抽象产品 B(Product B) | 第二个产品等级结构的接口 | DataSource 接口(定义 getDataSource()) |
| 具体产品 B1(Concrete Product B1) | 产品 B 的具体实现(属于产品族 1) | MySQLDataSource |
| 具体产品 B2(Concrete Product B2) | 产品 B 的具体实现(属于产品族 2) | OracleDataSource |
| 抽象工厂(Abstract Factory) | 定义创建所有产品等级结构的「工厂方法」(造产品族的规范) | DBFactory 接口(定义 createConnection() + createDataSource()) |
| 具体工厂 1(Concrete Factory 1) | 实现抽象工厂,创建产品族 1 的所有产品 | MySQLFactory(造 MySQLConnection + MySQLDataSource) |
| 具体工厂 2(Concrete Factory 2) | 实现抽象工厂,创建产品族 2 的所有产品 | OracleFactory(造 OracleConnection + OracleDataSource) |
工厂方法模式:
ConnectionFactory(工厂接口)
├─ createConnection() 接口方法1
├─ MySqlConnectionFactory 具体工厂实现类A
└─ createConnection()实现接口方法1
└─connect()实现产品接口
└─MySqlConnection创建产品A1
├─ RedisConnectionFactory 具体工厂实现类B
└─ createConnection()实现接口方法1
└─connect()实现产品接口
└─RedisConnection创建产品B1
抽象工厂:
DBFactory(工厂接口)
├─ createConnection() 接口方法1
│─ createDataSource() 接口方法2
├─ MySqlFactory 具体工厂实现类A
└─ createConnection() 实现工厂接口方法1
└─connect() 实现产品接口1
└─MySqlConnection 创建产品A1
└─ createDataSOurce() 实现工厂接口方法2
└─dataSource() 实现产品接口2
└─MySqlDataSource 创建产品A2
├─ RedisFactory 具体工厂实现类B
└─ createConnection() 实现工厂接口方法1
└─connect() 实现产品接口1
└─RedisConnection 创建产品B1
└─ createDataSOurce() 实现工厂接口方法2
└─dataSource() 实现产品接口2
└─RedisDataSource 创建产品B2
可以看见,相比于工厂模式中,不同工厂只能创建产品:数据库连接connection,抽象工厂的不同工厂可以创建多种产品,如数据库连接,数据源等等,这些多个产品的集合就是一个产品族,每个工厂可以生产自己的产品族
(3)、抽象工厂模式的核心逻辑(通俗解释)
你可以把抽象工厂理解为:
- 你(调用方)需要一套「数据库解决方案」(不是单个连接,而是连接 + 数据源 + 事务);
- 你找到「数据库解决方案总店」(抽象工厂接口),总店规定所有分店必须能造「连接 + 数据源 + 事务」这一套产品;
- 「MySQL 分店」(具体工厂 1)只造 MySQL 全套产品(MySQL 连接 + MySQL 数据源 + MySQL 事务);
- 「Oracle 分店」(具体工厂 2)只造 Oracle 全套产品(Oracle 连接 + Oracle 数据源 + Oracle 事务);
- 你想要哪套解决方案,就找对应的分店 —— 你只认「总店的规则」,不用管每套产品是怎么造的,也不用担心「MySQL 连接配了 Oracle 数据源」这种不兼容的情况。
(4)、完整代码示例
// ========== 第一套产品等级结构:Connection(连接) ==========
// 抽象产品A
public interface Connection {
void connect(); // 核心行为:连接数据库
}
// 具体产品A1(MySQL产品族)
public class MySQLConnection implements Connection {
@Override
public void connect() {
System.out.println("创建MySQL数据库连接");
}
}
// 具体产品A2(Oracle产品族)
public class OracleConnection implements Connection {
@Override
public void connect() {
System.out.println("创建Oracle数据库连接");
}
}
// ========== 第二套产品等级结构:DataSource(数据源) ==========
// 抽象产品B
public interface DataSource {
void getDataSource(); // 核心行为:获取数据源
}
// 具体产品B1(MySQL产品族)
public class MySQLDataSource implements DataSource {
@Override
public void getDataSource() {
System.out.println("获取MySQL数据源");
}
}
// 具体产品B2(Oracle产品族)
public class OracleDataSource implements DataSource {
@Override
public void getDataSource() {
System.out.println("获取Oracle数据源");
}
}
// ========== 抽象工厂:定义造产品族的规范 ==========
public interface DBFactory {
// 造第一套产品(Connection)
Connection createConnection();
// 造第二套产品(DataSource)
DataSource createDataSource();
}
// ========== 具体工厂1:造MySQL产品族 ==========
public class MySQLFactory implements DBFactory {
@Override
public Connection createConnection() {
return new MySQLConnection(); // 造MySQL连接
}
@Override
public DataSource createDataSource() {
return new MySQLDataSource(); // 造MySQL数据源
}
}
// ========== 具体工厂2:造Oracle产品族 ==========
public class OracleFactory implements DBFactory {
@Override
public Connection createConnection() {
return new OracleConnection(); // 造Oracle连接
}
@Override
public DataSource createDataSource() {
return new OracleDataSource(); // 造Oracle数据源
}
}
// ========== 调用方使用(核心:拿整套产品族) ==========
public class Client {
public static void main(String[] args) {
// 步骤1:选择产品族(想要MySQL全套,就用MySQL工厂)
DBFactory factory = new MySQLFactory();
// 步骤2:获取产品族的所有产品(连接+数据源)
Connection conn = factory.createConnection();
DataSource ds = factory.createDataSource();
// 步骤3:使用产品(无需关心具体是哪个产品族)
conn.connect();
ds.getDataSource();
// 切换成Oracle产品族:只需换工厂,全套产品自动配套
factory = new OracleFactory();
conn = factory.createConnection();
ds = factory.createDataSource();
conn.connect();
ds.getDataSource();
}
}
(5)、抽象工厂模式的核心优势(解决了什么问题)
- **保证产品族的「配套性」和「兼容性」**抽象工厂确保你拿到的所有产品都属于同一个产品族 —— 永远不会出现「MySQL 连接 + Oracle 数据源」这种不兼容的组合,这是工厂方法做不到的(工厂方法只能保证单个产品的正确性)。
- 整体切换产品族的成本极低比如你的系统要从 MySQL 全套换成 Oracle 全套,只需把
new MySQLFactory()改成new OracleFactory(),所有产品都会自动切换成 Oracle 版本,调用方代码完全不用改。 - 更高层次的封装和解耦调用方不仅不依赖具体产品,甚至不依赖「单个产品的创建逻辑」—— 只依赖「产品族的创建规范」(抽象工厂接口),底层产品的实现逻辑再怎么变,调用方都不受影响。
- **符合「单一职责」**每个具体工厂只负责创建一个产品族的所有产品,逻辑边界清晰(MySQL 工厂只管 MySQL 全家桶,Oracle 工厂只管 Oracle 全家桶)。
(6)、抽象工厂模式的适用场景
- 当你需要创建「配套使用的一套产品」(产品族),且希望保证产品之间的兼容性时;
- 当你的系统需要整体切换「产品族」(比如从 MySQL 全套换成 Oracle 全套)时;
- 当产品等级结构相对固定(新增产品族容易,新增产品等级难)时;
- 框架开发中(比如 Spring 的 BeanFactory、MyBatis 的 DataSourceFactory),需要提供整套组件的扩展能力时。
(7)、抽象工厂 vs 工厂方法(核心区别)
| 维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 创建目标 | 单个产品(一个产品等级结构) | 一套产品(多个产品等级结构组成的产品族) |
| 工厂接口 | 只有一个工厂方法(造单个产品) | 多个工厂方法(造产品族的每个产品) |
| 扩展难度 | 新增产品容易(加具体产品 + 具体工厂) | 新增产品族容易(加具体工厂 + 对应产品);新增产品等级难(需修改抽象工厂接口) |
| 核心价值 | 解耦单个产品的创建 | 保证产品族的配套性,整体切换产品族 |
| 适用场景 | 单个产品的灵活扩展 | 整套产品的配套使用和整体 |
三:spring中的bean注入机制(补充点,可忽略)
1. 手动注入(显式配置,可控性强)
手动注入是通过 XML / 注解显式指定要注入的 Bean,是最基础、最可控的方式,也是 BeanFactory 最核心的注入逻辑。
(1)构造方法注入(推荐,强制依赖)
核心逻辑:BeanFactory 通过 Bean 的构造方法,将依赖 Bean 作为参数传入,完成注入。适合「必须存在的依赖」(比如数据源、工厂类),能保证 Bean 创建时依赖已初始化。
适用场景:依赖不可为空、需要保证 Bean 初始化时依赖完整。
代码示例(注解版,Spring Boot 主流) :
// 步骤1:定义被依赖的 Bean(比如数据库连接工厂)
@Component
public class MySQLConnectionFactory implements ConnectionFactory {
@Override
public Connection createConnection() {
return new MySQLConnection();
}
}
// 步骤2:目标 Bean 通过构造方法注入依赖
@Component
public class UserService {
// 依赖的接口(面向接口编程,符合工厂模式思想)
private final ConnectionFactory connectionFactory;
// 构造方法注入(Spring 会自动匹配类型对应的 Bean)
// 注:Spring 4.3+ 中,单构造方法无需加 @Autowired
public UserService(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
public void queryUser() {
Connection conn = connectionFactory.createConnection();
conn.connect();
}
}
XML 配置版(传统方式) :
<!-- 定义依赖 Bean -->
<bean id="mysqlConnectionFactory" class="com.example.MySQLConnectionFactory"/>
<!-- 构造方法注入 -->
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="mysqlConnectionFactory"/>
</bean>
(2)Setter 方法注入(可选依赖,依赖可以不存在)
核心逻辑:BeanFactory 调用 Bean 的 Setter 方法,将依赖 Bean 赋值给属性。适合「可选依赖」(可通过 Setter 设置默认值)。
适用场景:依赖非必须、需要动态修改依赖的场景。
代码示例(注解版) :
@Component
public class UserService {
private ConnectionFactory connectionFactory;
// Setter 方法注入(@Autowired 标识需要注入的方法)
@Autowired
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
}
(3)字段注入(简化写法,不推荐)
核心逻辑:直接在字段上标注 @Autowired,BeanFactory 通过反射直接赋值给私有字段(跳过 Setter / 构造方法)。
优缺点:写法极简,但破坏封装性、难以单元测试(无法手动传入依赖),Spring 官方不推荐在生产环境使用。
代码示例:
@Component
public class UserService {
// 字段注入(仅演示,不推荐)
@Autowired
private ConnectionFactory connectionFactory;
}
2. 自动注入(自动匹配,简化配置)
自动注入是让 BeanFactory 自动识别并注入依赖,无需显式指定,核心依赖「Bean 的类型 / 名称」匹配。
(1)按类型注入(@Autowired,默认)
核心逻辑:BeanFactory 根据「字段 / 方法参数的类型」,在容器中查找匹配的 Bean 并注入。如果同类型有多个 Bean,会抛出异常(需配合 @Qualifier 指定名称)。
代码示例(解决多实例冲突) :
// 多实例:MySQL 和 Oracle 工厂都实现 ConnectionFactory
@Component("mysqlFactory")
public class MySQLConnectionFactory implements ConnectionFactory { ... }
@Component("oracleFactory")
public class OracleConnectionFactory implements ConnectionFactory { ... }
// 目标 Bean:按类型+名称注入
@Component
public class UserService {
// @Qualifier 指定要注入的 Bean 名称
@Autowired
@Qualifier("mysqlFactory")
private ConnectionFactory connectionFactory;
}
(2)按名称注入(@Resource)
核心逻辑:BeanFactory 根据「字段名称 /@Resource 的 name 属性」匹配 Bean 名称,完成注入(优先按名称,名称匹配失败再按类型)。
代码示例:
@Component
public class UserService {
// 方式1:按字段名称(mysqlFactory)匹配 Bean
@Resource
private ConnectionFactory mysqlFactory;
// 方式2:显式指定 Bean 名称
@Resource(name = "oracleFactory")
private ConnectionFactory connectionFactory;
}
3. 特殊注入形式
(1)方法参数注入(适合批量注入)
核心逻辑:BeanFactory 将依赖注入到非 Setter 方法的参数中,适合需要批量处理依赖的场景(比如初始化方法)。
@Component
public class DataService {
private List<ConnectionFactory> factoryList;
// 方法参数注入:注入所有实现 ConnectionFactory 的 Bean
@Autowired
public void initFactories(List<ConnectionFactory> factoryList) {
this.factoryList = factoryList;
}
}
(2)延迟注入(ObjectFactory/Provider)
核心逻辑:通过 ObjectFactory 或 javax.inject.Provider 延迟获取 Bean,避免循环依赖或提前初始化。
@Component
public class UserService {
// 延迟注入:调用 get() 时才真正获取 Bean
@Autowired
private ObjectFactory<ConnectionFactory> factoryObject;
public void query() {
// 延迟初始化
ConnectionFactory factory = factoryObject.getObject();
factory.createConnection();
}
}
(3)循环依赖注入(Spring 内置解决)
当 A 依赖 B、B 又依赖 A 时,BeanFactory 通过「三级缓存」解决构造方法注入外的循环依赖(构造方法注入无法解决,会抛异常):
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
4、Spring Bean 注入的核心原则
- 优先构造方法注入:保证依赖不可变、Bean 初始化时依赖完整,符合「不可变对象」设计思想;
- 面向接口注入:注入接口而非具体实现(比如注入
ConnectionFactory而非MySQLConnectionFactory),符合工厂模式解耦思想; - 避免字段注入:破坏封装性,单元测试困难,仅适合简单演示场景;
- 自动注入慎用:多实例场景需配合
@Qualifier/@Resource指定名称,避免匹配失败