工厂方法模式是一种创建型设计模式,通过定义创建对象的接口,让子类决定实例化哪个类。它包含抽象产品、具体产品、抽象工厂和具体工厂等角色。该模式使类的实例化延迟到子类,具有良好的扩展性和灵活性,适用于多种场景,如Spring框架中的应用。文中还探讨了结合配置中心动态切换消息类型、Spring Boot自动配置与SPI加载工厂等实战示例,以及运行时动态添加SPI实现类的热插拔技术。
工厂方法模式 是一种 创建型设计模式,它通过 定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使一个类的实例化延迟到其子类。
定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类中进行。
角色 |
说明 |
Product |
抽象产品类,定义产品的公共接口 |
ConcreteProduct |
具体产品类,实现了抽象产品接口 |
Creator |
抽象工厂类,声明工厂方法 |
ConcreteCreator |
具体工厂类,重写 |
工厂方法模式的核心是:将对象的创建延迟到子类中去实现,从而达到 “对扩展开放,对修改关闭” 的目的。
public interface Product {
void use();
}
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");
}
}
public abstract class Creator {
public abstract Product createProduct();
}
public class ConcreteCreatorA extends Creator {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
public class ConcreteCreatorB extends Creator {
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}
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();
}
}
如果后续要新增 ConcreteProductC
,只需:
ConcreteProductC
类ConcreteCreatorC
类,实现 createProduct()
返回 ProductC
比较点 |
简单工厂 |
工厂方法 |
工厂类 |
一个类处理所有产品创建逻辑 |
每个产品对应一个工厂类 |
新增产品时 |
修改工厂逻辑(违反OCP) |
新增产品类和工厂类(符合OCP) |
灵活性 |
较差 |
更强 |
类的数量 |
少 |
多 |
场景 |
描述 |
需要创建的对象具有复杂构建逻辑 |
创建过程复杂或需要依赖其他对象时,用工厂方法将其封装。 |
系统中有多个产品族,且产品种类经常扩展 |
每新增一个产品,不希望改动原来的工厂类。符合开闭原则(OCP)。 |
希望客户端代码与具体产品解耦 |
客户端只依赖产品接口,不依赖具体实现类,降低耦合。 |
框架级别开发,预留扩展点给业务方 |
比如 Spring 中的 BeanFactory、MyBatis 的 TypeHandlerFactory。 |
对象生命周期由工厂统一管理 |
方便缓存、单例控制等。 |
例子:
FactoryBean
实现 bean 的创建解耦。Connection
、Statement
创建由 ConnectionFactory
封装。PayHandler
。 场景 |
原因 |
❌ 产品种类非常少或固定,不需要频繁扩展 |
工厂类和产品类较少,使用工厂方法反而会增加类数量和结构复杂度。 |
❌ 系统结构简单,对象创建逻辑很简单 |
直接 就可以,无需额外的设计模式来封装。 |
❌ 项目初期阶段,功能未稳定,频繁重构 |
工厂方法类结构多,频繁修改成本高。 |
❌ 对性能要求极高的场景 |
反射或额外的工厂逻辑可能有性能损耗,需权衡使用。 |
例子:
new
更直观清晰。总结:当你面对不断变化的对象创建需求,并希望将变化隔离时,用工厂方法模式;如果结构简单、创建逻辑轻量,直接 new
反而更高效。
下面是一个基于 工厂方法模式 的 Spring 项目示例,适合的业务场景是:多渠道消息发送系统,如支持短信(SMS)、邮件(Email)、推送(Push)等多种消息发送方式,且将来可能还会扩展更多类型。消息发送系统:不同类型消息的发送逻辑不同,易于扩展,且客户端无需关心具体实现。
public interface MessageSender {
void send(String message);
}
@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);
}
}
public interface MessageSenderFactory {
MessageSender createSender();
}
@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;
}
}
@Service
public class NotificationService {
// 也可以通过配置动态切换
@Resource(name = "emailFactory")
private MessageSenderFactory senderFactory;
public void notify(String msg) {
MessageSender sender = senderFactory.createSender();
sender.send(msg);
}
}
优点 |
说明 |
解耦 |
|
易于扩展 |
新增 |
支持 Spring 管理生命周期 |
工厂和产品都可作为 Spring Bean 管理 |
单一职责 |
每个工厂负责一个类型消息的创建,职责清晰 |
你还可以用配置中心动态切换默认的消息类型:
@Value("${message.type}")
private String type; // "email" / "sms"
通过一个总的 MessageSenderFactoryRegistry
统一管理不同的工厂,按类型取出。
application.yml
└─ message.type=email
MessageSender 接口
├─ EmailMessageSender
└─ SmsMessageSender
MessageSenderFactory 接口
├─ EmailSenderFactory
└─ SmsSenderFactory
MessageSenderFactoryRegistry(注册所有工厂)
NotificationService(注入 @Value 配置,从 Registry 获取 Sender)
message:
type: email
在客户端(如 NotificationService)中使用:
@Value("${message.type}")
private String type; // 注入配置中心的值
@Component
public class MessageSenderFactoryRegistry {
private final Map factoryMap = new HashMap<>();
@Autowired
public MessageSenderFactoryRegistry(List 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;
}
}
@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";
}
}
@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);
}
}
点 |
说明 |
|
注入配置中心的策略标识 |
|
将所有具体工厂注册进来,用于动态选择 |
|
各个工厂自报家门,便于注册 |
使用场景 |
可灵活切换策略(消息类型),并支持后续热扩展 |
以下是基于 Spring Boot 自动配置 + SPI + 工厂注册机制 的完整示例,用于实现插件式、可扩展的策略工厂体系,常用于消息发送、支付方式、风控策略等系统中。
支持在多个 jar 插件中以 SPI 方式注册 MessageSender
实现,并通过 Spring Boot 自动装配到一个注册中心中,客户端使用配置中心动态切换使用哪个策略。
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
package com.example.spi;
public interface MessageSender {
void send(String message);
// 用于标识类型,比如 "email"、"sms"
String type();
}
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";
}
}
在 resources/META-INF/services/
目录下创建:
com.example.spi.MessageSender
内容为:
com.example.impl.EmailMessageSender
com.example.impl.SmsMessageSender
package com.example.factory;
import com.example.spi.MessageSender;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class MessageSenderFactoryRegistry {
private final Map senderMap = new HashMap<>();
public MessageSenderFactoryRegistry() {
// 初始化加载spi接口实现类
ServiceLoader 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 supportedTypes() {
return senderMap.keySet();
}
}
如果你将 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();
}
}
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);
}
}
message:
type: email
方式 |
普通简单工厂 |
SPI + 自动装配 |
实现方式 |
手动注册、代码维护类型 -> 实例映射 |
自动发现、解耦模块 |
扩展性 |
差,改动原有类 |
高,插件式加载新类型 |
热插拔 |
不支持 |
支持 jar 插件添加 |
Spring 兼容 |
需自己管理 Bean |
可与自动配置整合 |
ServiceLoader.load(...)
是不是启动加载?ServiceLoader.load(...)
不是 Spring 启动时自动加载的 —— 它是懒加载(Lazy Load)机制。
ServiceLoader
是 Java 原生的 SPI 加载器。ServiceLoader.load(...)
的代码什么时候执行。ServiceLoader
来加载服务,除非你显式地调用了它(例如在某个 Bean 初始化时)。会在第一次使用时动态加载,但不是运行时热加载。
ServiceLoader
内部使用懒加载机制:在第一次调用 iterator()
或 for
循环时才加载并实例化实现类。ServiceLoader.load(Xxx.class)
会从:
classpath:/META-INF/services/your.interface.FullyQualifiedName
去读取该文件,文件内容为接口实现类的全限定类名,每一行一个。
然后使用当前线程的 ClassLoader
反射实例化实现类。
举个例子:
// 手动触发加载(不是 Spring 自动做的)
ServiceLoader loader = ServiceLoader.load(PayChannel.class);
for (PayChannel channel : loader) {
channel.pay();
}
for (PayChannel channel : loader)
时,才真正实例化每个实现类。/META-INF/services
目录。ClassLoader
加载外部 Jar。ServiceLoader.reload()
。如果你希望 在运行时动态添加 SPI 实现类(即热插拔插件机制),Java 原生的 ServiceLoader
默认不支持运行时添加实现类,但你可以通过自定义 ClassLoader
+ SPI 机制 + 热加载逻辑 实现。
要实现 动态 SPI 实现类加载(比如加载一个新 jar 的 SPI 实现),需要以下关键步骤:
例如:
public interface MessageSender {
String type();
void send(String message);
}
META-INF/services/com.example.MessageSender
中声明实现类com.example.impl.EmailSender
plugins/
└── message-email.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());
}
}
public class PluginManager {
private final Map senderMap = new ConcurrentHashMap<>();
public void loadPlugin(String jarPath) throws Exception {
// 自定义 URLClassLoader 加载 jar
PluginClassLoader loader = PluginClassLoader.loadFrom(jarPath);
ServiceLoader serviceLoader = ServiceLoader.load(MessageSender.class, loader);
for (MessageSender sender : serviceLoader) {
senderMap.put(sender.type(), sender);
}
}
public MessageSender getSender(String type) {
return senderMap.get(type);
}
}
PluginManager manager = new PluginManager();
manager.loadPlugin("plugins/message-email.jar");
MessageSender sender = manager.getSender("email");
sender.send("Hello Dynamic Plugin!");
步骤 |
说明 |
|
使用 URLClassLoader 加载外部 jar,和主应用隔离 |
|
指定加载器读取 |
插件隔离 |
保持插件 jar 中依赖与主程序不冲突 |
实例缓存 |
|
SPI 定义 |
每个插件都要放置标准的 |