摘要
工厂方法模式是一种创建型设计模式,通过定义创建对象的接口,让子类决定实例化哪个类。它包含抽象产品、具体产品、抽象工厂和具体工厂等角色。该模式使类的实例化延迟到子类,具有良好的扩展性和灵活性,适用于多种场景,如Spring框架中的应用。文中还探讨了结合配置中心动态切换消息类型、Spring Boot自动配置与SPI加载工厂等实战示例,以及运行时动态添加SPI实现类的热插拔技术。
1. 工厂方法设计模式定义
工厂方法模式 是一种 创建型设计模式,它通过 定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使一个类的实例化延迟到其子类。
定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类中进行。
角色 |
说明 |
Product |
抽象产品类,定义产品的公共接口 |
ConcreteProduct |
具体产品类,实现了抽象产品接口 |
Creator |
抽象工厂类,声明工厂方法 |
ConcreteCreator |
具体工厂类,重写 |
2. 工厂方法设计模式结构
2.1. 工厂方法类图
- Product:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂
2.2. 工厂方法时序图
3. 工厂方法设计模式实现方式
工厂方法模式的核心是:将对象的创建延迟到子类中去实现,从而达到 “对扩展开放,对修改关闭” 的目的。
3.1. 🧱 实现步骤
- 定义产品接口(Product)
- 创建多个具体产品类(ConcreteProduct)
- 定义工厂抽象类(Creator)
- 创建具体工厂类(ConcreteCreator)实现产品的创建
- 客户端调用:使用工厂创建产品
3.2. ✅ 示例代码实现
3.2.1. 1️⃣ 抽象产品接口
public interface Product {
void use();
}
3.2.2. 2️⃣ 具体产品类
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("使用产品 A");
}
}
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("使用产品 B");
}
}
3.2.3. 3️⃣ 抽象工厂类
public abstract class Creator {
public abstract Product createProduct();
}
3.2.4. 4️⃣ 具体工厂类
public class ConcreteCreatorA extends Creator {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
public class ConcreteCreatorB extends Creator {
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}
3.2.5. 5️⃣ 客户端代码
public class Client {
public static void main(String[] args) {
Creator creatorA = new ConcreteCreatorA();
Product productA = creatorA.createProduct();
productA.use();
Creator creatorB = new ConcreteCreatorB();
Product productB = creatorB.createProduct();
productB.use();
}
}
3.2.6. 📌 可扩展性体现
如果后续要新增 ConcreteProductC
,只需:
- 新增一个
ConcreteProductC
类 - 新增一个
ConcreteCreatorC
类,实现createProduct()
返回ProductC
- 不修改原有工厂代码,符合开闭原则(OCP)
3.3. ✳️ 工厂方法总结:
比较点 |
简单工厂 |
工厂方法 |
工厂类 |
一个类处理所有产品创建逻辑 |
每个产品对应一个工厂类 |
新增产品时 |
修改工厂逻辑(违反OCP) |
新增产品类和工厂类(符合OCP) |
灵活性 |
较差 |
更强 |
类的数量 |
少 |
多 |
4. 工厂方法设计模式适合场景
4.1. ✅ 适合使用工厂方法模式的场景
场景 |
描述 |
需要创建的对象具有复杂构建逻辑 |
创建过程复杂或需要依赖其他对象时,用工厂方法将其封装。 |
系统中有多个产品族,且产品种类经常扩展 |
每新增一个产品,不希望改动原来的工厂类。符合开闭原则(OCP)。 |
希望客户端代码与具体产品解耦 |
客户端只依赖产品接口,不依赖具体实现类,降低耦合。 |
框架级别开发,预留扩展点给业务方 |
比如 Spring 中的 BeanFactory、MyBatis 的 TypeHandlerFactory。 |
对象生命周期由工厂统一管理 |
方便缓存、单例控制等。 |
例子:
- Spring 框架中使用
FactoryBean
实现 bean 的创建解耦。 - JDBC 的
Connection
、Statement
创建由ConnectionFactory
封装。 - 不同支付渠道(微信、支付宝、银联)通过工厂生成对应
PayHandler
。
4.2. ❌ 不适合使用工厂方法模式的场景
场景 |
原因 |
❌ 产品种类非常少或固定,不需要频繁扩展 |
工厂类和产品类较少,使用工厂方法反而会增加类数量和结构复杂度。 |
❌ 系统结构简单,对象创建逻辑很简单 |
直接 就可以,无需额外的设计模式来封装。 |
❌ 项目初期阶段,功能未稳定,频繁重构 |
工厂方法类结构多,频繁修改成本高。 |
❌ 对性能要求极高的场景 |
反射或额外的工厂逻辑可能有性能损耗,需权衡使用。 |
例子:
- 简单数据封装对象(DTO/VO),使用
new
更直观清晰。 - 稳定的、不会扩展的枚举或常量类,不需要通过工厂方法。
总结:当你面对不断变化的对象创建需求,并希望将变化隔离时,用工厂方法模式;如果结构简单、创建逻辑轻量,直接 new
反而更高效。
5. 工厂方法设计模式实战示例
下面是一个基于 工厂方法模式 的 Spring 项目示例,适合的业务场景是:多渠道消息发送系统,如支持短信(SMS)、邮件(Email)、推送(Push)等多种消息发送方式,且将来可能还会扩展更多类型。消息发送系统:不同类型消息的发送逻辑不同,易于扩展,且客户端无需关心具体实现。
5.1. Spring 工厂方法模式示例
5.1.1. 定义统一接口(产品接口)
public interface MessageSender {
void send(String message);
}
5.1.2. 实现具体的产品类(不同的消息发送实现)
@Component
public class EmailMessageSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("发送邮件: " + message);
}
}
@Component
public class SmsMessageSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("发送短信: " + message);
}
}
5.1.3. 定义抽象工厂类(可选)
public interface MessageSenderFactory {
MessageSender createSender();
}
5.1.4. 实现具体工厂类(根据不同产品创建)
@Component("emailFactory")
public class EmailSenderFactory implements MessageSenderFactory {
@Autowired
private EmailMessageSender sender;
@Override
public MessageSender createSender() {
return sender;
}
}
@Component("smsFactory")
public class SmsSenderFactory implements MessageSenderFactory {
@Autowired
private SmsMessageSender sender;
@Override
public MessageSender createSender() {
return sender;
}
}
5.1.5. 客户端代码(注入工厂调用)
@Service
public class NotificationService {
// 也可以通过配置动态切换
@Resource(name = "emailFactory")
private MessageSenderFactory senderFactory;
public void notify(String msg) {
MessageSender sender = senderFactory.createSender();
sender.send(msg);
}
}
5.2. ✅ 优点体现(为何使用工厂方法)
优点 |
说明 |
解耦 |
|
易于扩展 |
新增 |
支持 Spring 管理生命周期 |
工厂和产品都可作为 Spring Bean 管理 |
单一职责 |
每个工厂负责一个类型消息的创建,职责清晰 |
5.3. ✅ 可选升级(使用配置中心 + @Value)
你还可以用配置中心动态切换默认的消息类型:
@Value("${message.type}")
private String type; // "email" / "sms"
通过一个总的 MessageSenderFactoryRegistry
统一管理不同的工厂,按类型取出。
6. 工厂方法设计模式思考
6.1. 配置中心动态切换消息类型
6.1.1. 总体结构图
application.yml
└─ message.type=email
MessageSender 接口
├─ EmailMessageSender
└─ SmsMessageSender
MessageSenderFactory 接口
├─ EmailSenderFactory
└─ SmsSenderFactory
MessageSenderFactoryRegistry(注册所有工厂)
NotificationService(注入 @Value 配置,从 Registry 获取 Sender)
6.1.2. ✅ application.yml 配置
message:
type: email
6.1.3. ✅ @Value 注入默认类型
在客户端(如 NotificationService)中使用:
@Value("${message.type}")
private String type; // 注入配置中心的值
6.1.4. ✅ Factory Registry 实现
@Component
public class MessageSenderFactoryRegistry {
private final Map<String, MessageSenderFactory> factoryMap = new HashMap<>();
@Autowired
public MessageSenderFactoryRegistry(List<MessageSenderFactory> factories) {
for (MessageSenderFactory factory : factories) {
factoryMap.put(factory.getType(), factory); // getType 方法用于标识工厂
}
}
public MessageSenderFactory getFactory(String type) {
MessageSenderFactory factory = factoryMap.get(type);
if (factory == null) {
throw new IllegalArgumentException("不支持的消息类型: " + type);
}
return factory;
}
}
6.1.5. ✅ 各具体工厂类
@Component
public class EmailSenderFactory implements MessageSenderFactory {
@Autowired
private EmailMessageSender sender;
@Override
public MessageSender createSender() {
return sender;
}
@Override
public String getType() {
return "email";
}
}
@Component
public class SmsSenderFactory implements MessageSenderFactory {
@Autowired
private SmsMessageSender sender;
@Override
public MessageSender createSender() {
return sender;
}
@Override
public String getType() {
return "sms";
}
}
6.1.6. ✅ 客户端使用 Registry + 配置中心
@Service
public class NotificationService {
// 配置中心动态切换消息类型
@Value("${message.type}")
private String type;
@Autowired
private MessageSenderFactoryRegistry factoryRegistry;
public void notify(String msg) {
MessageSender sender = factoryRegistry.getFactory(type).createSender();
sender.send(msg);
}
}
6.1.7. ✅ 动态配置总结
点 |
说明 |
|
注入配置中心的策略标识 |
|
将所有具体工厂注册进来,用于动态选择 |
|
各个工厂自报家门,便于注册 |
使用场景 |
可灵活切换策略(消息类型),并支持后续热扩展 |
6.2. Spring Boot 自动配置 + SPI 加载工厂
以下是基于 Spring Boot 自动配置 + SPI + 工厂注册机制 的完整示例,用于实现插件式、可扩展的策略工厂体系,常用于消息发送、支付方式、风控策略等系统中。
支持在多个 jar 插件中以 SPI 方式注册 MessageSender
实现,并通过 Spring Boot 自动装配到一个注册中心中,客户端使用配置中心动态切换使用哪个策略。
6.2.1. ✅ 项目结构概览
src
├── META-INF
│ └── services
│ └── com.example.spi.MessageSender
├── com.example.spi
│ └── MessageSender.java
├── com.example.impl
│ ├── EmailMessageSender.java
│ └── SmsMessageSender.java
├── com.example.factory
│ ├── MessageSenderFactory.java
│ └── MessageSenderFactoryRegistry.java
├── com.example.config
│ └── MessageSenderAutoConfiguration.java
└── com.example.client
└── NotificationService.java
6.2.2. ✳️ 定义 SPI 接口
package com.example.spi;
public interface MessageSender {
void send(String message);
// 用于标识类型,比如 "email"、"sms"
String type();
}
6.2.3. ✳️ 实现两个 SPI 类
package com.example.impl;
import com.example.spi.MessageSender;
public class EmailMessageSender implements MessageSender {
public void send(String message) {
System.out.println("发送邮件:" + message);
}
public String type() {
return "email";
}
}
package com.example.impl;
import com.example.spi.MessageSender;
public class SmsMessageSender implements MessageSender {
public void send(String message) {
System.out.println("发送短信:" + message);
}
public String type() {
return "sms";
}
}
6.2.4. ✳️ 创建 SPI 配置文件
在 resources/META-INF/services/
目录下创建:
com.example.spi.MessageSender
内容为:
com.example.impl.EmailMessageSender
com.example.impl.SmsMessageSender
6.2.5. ✳️ 创建 Spring 注册工厂类
package com.example.factory;
import com.example.spi.MessageSender;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class MessageSenderFactoryRegistry {
private final Map<String, MessageSender> senderMap = new HashMap<>();
public MessageSenderFactoryRegistry() {
// 初始化加载spi接口实现类
ServiceLoader<MessageSender> loader = ServiceLoader.load(MessageSender.class);
for (MessageSender sender : loader) {
senderMap.put(sender.type(), sender);
}
}
public MessageSender getSender(String type) {
MessageSender sender = senderMap.get(type);
if (sender == null) {
throw new IllegalArgumentException("不支持的消息类型: " + type);
}
return sender;
}
public Set<String> supportedTypes() {
return senderMap.keySet();
}
}
6.2.6. ✳️ 自动配置类(可选)
如果你将 SPI 插件打包成独立 jar,可以增加 spring.factories
/ spring.factories
文件,实现自动注册:
resources/META-INF/spring.factories
(Spring Boot 2)或 spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(Spring Boot 3):
# spring.factories 示例
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MessageSenderAutoConfiguration
package com.example.config;
import com.example.factory.MessageSenderFactoryRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MessageSenderAutoConfiguration {
@Bean
public MessageSenderFactoryRegistry messageSenderFactoryRegistry() {
return new MessageSenderFactoryRegistry();
}
}
6.2.7. ✳️ 使用配置动态切换策略
package com.example.client;
import com.example.factory.MessageSenderFactoryRegistry;
import com.example.spi.MessageSender;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
@Value("${message.type}")
private String messageType;
private final MessageSenderFactoryRegistry registry;
public NotificationService(MessageSenderFactoryRegistry registry) {
this.registry = registry;
}
public void notify(String content) {
MessageSender sender = registry.getSender(messageType);
sender.send(content);
}
}
6.2.8. ✳️ application.yml 示例
message:
type: email
6.2.9. ✅ 与普通简单工厂对比总结
方式 |
普通简单工厂 |
SPI + 自动装配 |
实现方式 |
手动注册、代码维护类型 -> 实例映射 |
自动发现、解耦模块 |
扩展性 |
差,改动原有类 |
高,插件式加载新类型 |
热插拔 |
不支持 |
支持 jar 插件添加 |
Spring 兼容 |
需自己管理 Bean |
可与自动配置整合 |
6.2.10. ✅ 适用场景
- 插件式服务(支付、消息、风控、日志);
- 支持 jar 级别扩展;
- 想在不改主工程代码的情况下支持“新策略”;
- 想结合 Spring 自动装配实现热加载、策略切换。
6.3. ServiceLoader.load(...)
是不是启动加载?
6.3.1. 是不是 Spring 启动时加载?
ServiceLoader.load(...)
不是 Spring 启动时自动加载的 —— 它是懒加载(Lazy Load)机制。
ServiceLoader
是 Java 原生的 SPI 加载器。- 它的加载时机完全取决于你调用
ServiceLoader.load(...)
的代码什么时候执行。 - Spring 启动过程中,不会自动调用
ServiceLoader
来加载服务,除非你显式地调用了它(例如在某个 Bean 初始化时)。
6.3.2. 会不会动态加载 SPI 接口实现类?
会在第一次使用时动态加载,但不是运行时热加载。
ServiceLoader
内部使用懒加载机制:在第一次调用iterator()
或for
循环时才加载并实例化实现类。- 但是这个“动态加载”是指JVM 当前 ClassLoader 中已存在的实现类。
- 不是热加载,不支持运行时新增 jar 或类不重启就生效。
6.3.3. 加载机制原理简要
ServiceLoader.load(Xxx.class)
会从:
classpath:/META-INF/services/your.interface.FullyQualifiedName
去读取该文件,文件内容为接口实现类的全限定类名,每一行一个。
然后使用当前线程的 ClassLoader
反射实例化实现类。
举个例子:
// 手动触发加载(不是 Spring 自动做的)
ServiceLoader<PayChannel> loader = ServiceLoader.load(PayChannel.class);
for (PayChannel channel : loader) {
channel.pay();
}
- 只有执行
for (PayChannel channel : loader)
时,才真正实例化每个实现类。 - Spring 本身并不扫描
/META-INF/services
目录。
6.3.4. 如果你想支持运行时动态添加 SPI 实现类(热插拔):
- 需要自定义
ClassLoader
加载外部 Jar。 - 手动调用
ServiceLoader.reload()
。 - Spring 本身不提供这套机制,但可以封装插件框架支持(如 Apache Dubbo、Spring Boot Plugin、OSGi)。
6.4. 运行时动态添加 SPI 实现类(热插拔)示例
如果你希望 在运行时动态添加 SPI 实现类(即热插拔插件机制),Java 原生的 ServiceLoader
默认不支持运行时添加实现类,但你可以通过自定义 ClassLoader
+ SPI 机制 + 热加载逻辑 实现。
要实现 动态 SPI 实现类加载(比如加载一个新 jar 的 SPI 实现),需要以下关键步骤:
6.4.1. 🧩 准备 SPI 接口
例如:
public interface MessageSender {
String type();
void send(String message);
}
6.4.2. 📁 插件 jar 的 META-INF/services/com.example.MessageSender
中声明实现类
com.example.impl.EmailSender
6.4.3. 🛠️ 构建插件目录结构
plugins/
└── message-email.jar
6.4.4. 🧠 自定义 URLClassLoader 加载 jar
public class PluginClassLoader extends URLClassLoader {
public PluginClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public static PluginClassLoader loadFrom(String jarPath) throws MalformedURLException {
File jarFile = new File(jarPath);
return new PluginClassLoader(new URL[]{jarFile.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
}
}
6.4.5. 🧪 使用 ServiceLoader + 自定义 ClassLoader 加载插件
public class PluginManager {
private final Map<String, MessageSender> senderMap = new ConcurrentHashMap<>();
public void loadPlugin(String jarPath) throws Exception {
// 自定义 URLClassLoader 加载 jar
PluginClassLoader loader = PluginClassLoader.loadFrom(jarPath);
ServiceLoader<MessageSender> serviceLoader = ServiceLoader.load(MessageSender.class, loader);
for (MessageSender sender : serviceLoader) {
senderMap.put(sender.type(), sender);
}
}
public MessageSender getSender(String type) {
return senderMap.get(type);
}
}
6.4.6. 🔄 动态加载新 jar 插件
PluginManager manager = new PluginManager();
manager.loadPlugin("plugins/message-email.jar");
MessageSender sender = manager.getSender("email");
sender.send("Hello Dynamic Plugin!");
6.5. ✅ 支持动态热插拔的关键点
步骤 |
说明 |
|
使用 URLClassLoader 加载外部 jar,和主应用隔离 |
|
指定加载器读取 |
插件隔离 |
保持插件 jar 中依赖与主程序不冲突 |
实例缓存 |
|
SPI 定义 |
每个插件都要放置标准的 |