Java 设计模式详解 -- 从理论到实战
Java 设计模式详解 – 从理论到实战
本文系统梳理 Java 中常用的设计模式,涵盖创建型、结构型、行为型三大分类,结合 Spring Boot 实际项目(LinkForge)中的真实应用场景进行解析。
目录
一、创建型模式
创建型模式关注对象的创建机制,将对象的创建与使用分离,降低系统的耦合度。
1.1 单例模式
意图: 保证一个类仅有一个实例,并提供一个全局访问点。
适用场景: 全局配置管理器、线程池、数据库连接池、日志对象等需要全局唯一实例的场景。
1.1.1 双重检查锁(DCL)
public class Singleton {
// volatile 禁止指令重排序,保证多线程环境下实例的可见性
private static volatile Singleton INSTANCE = null;
// 私有构造方法,防止外部直接 new
private Singleton() {
}
// 公有静态工厂方法,返回唯一实例
public static Singleton getInstance() {
if (INSTANCE == null) { // 第一次检查(无锁,提高性能)
synchronized (Singleton.class) {
if (INSTANCE == null) { // 第二次检查(锁内,保证唯一)
INSTANCE = new Singleton(); // 问题:此处非原子操作
}
}
}
return INSTANCE;
}
}
为什么需要 volatile? INSTANCE = new Singleton() 这一行并非原子操作,JVM 会将其编译为三条指令:
- 分配内存空间
- 初始化对象
- 将引用指向内存地址
在 JDK 1.5 之前,指令 2 和 3 可能被重排序(先执行 3 再执行 2)。如果线程 A 执行完指令 3 还未执行 2 时,线程 B 判断 INSTANCE != null 直接返回,就会拿到一个尚未初始化的对象。volatile 关键字通过内存屏障(Memory Barrier)禁止这种重排序。
注意: 从 JDK 5 开始,
volatile已经能可靠地解决这个问题。在 Java 9+ 中还可以结合VarHandle实现更细粒度的控制,但 DCL 仍然是实践中最常用的单例写法之一。
1.1.2 静态内部类
public class Singleton {
// 静态内部类在外部类加载时不会被加载,实现了懒加载
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理: JVM 在类的初始化阶段(即首次主动使用时)会获取锁,这个机制被 JVM 规范保证是线程安全的。静态内部类 SingletonHolder 只有在 getInstance() 首次被调用时才会加载,天然实现了懒加载,同时不需要 synchronized,性能优于 DCL。
1.1.3 枚举单例
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("execute business logic");
}
}
枚举实现单例的优势:
| 特性 | 枚举单例 | DCL | 静态内部类 |
|---|---|---|---|
| 线程安全 | JVM 保证 | 需 volatile | JVM 保证 |
| 防止反射攻击 | 天然防止(无公共构造器) | 不防止 | 不防止 |
| 防止序列化破坏 | 天然防止 | 需 readResolve | 需 readResolve |
| 代码简洁度 | 最高 | 较复杂 | 中等 |
| 懒加载 | 否(类加载即初始化) | 是 | 是 |
关于序列化: 枚举类型的序列化机制与普通类不同。JVM 规范规定,枚举的序列化只写入枚举常量的名称,反序列化时通过 Enum.valueOf() 返回已有的实例,不会创建新对象。因此枚举单例天然免疫序列化/反序列化攻击,无需额外实现 readResolve()。
《Effective Java》作者 Joshua Bloch 推荐枚举作为实现单例的最佳方式。唯一的限制是枚举不支持懒加载,不过在实践中,大部分单例对象的初始化成本并不高,这一限制通常可以接受。
1.2 工厂方法模式
意图: 定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法使类的实例化延迟到子类。
适用场景: 创建逻辑复杂、需要根据条件动态选择实现类、希望与具体创建逻辑解耦时。
标准实现
// 产品接口
public interface Product {
void use();
}
// 具体产品 A
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("使用 ConcreteProductA");
}
}
// 具体产品 B
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("使用 ConcreteProductB");
}
}
// 抽象工厂(Creator)
public abstract class Creator {
public abstract Product factoryMethod();
}
// 具体工厂 A
public class ConcreteCreatorA extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}
// 具体工厂 B
public class ConcreteCreatorB extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}
客户端调用:
Creator creatorA = new ConcreteCreatorA();
Product productA = creatorA.factoryMethod();
productA.use(); // 输出: 使用 ConcreteProductA
工厂方法 vs 简单工厂 vs 抽象工厂
| 模式 | 核心特点 | 开闭原则 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 简单工厂 | 一个工厂类包含所有创建逻辑 | 不满足(新增产品需修改工厂) | 低 | 产品种类少且稳定 |
| 工厂方法 | 每个产品对应一个工厂子类 | 满足 | 中 | 产品种类多且经常扩展 |
| 抽象工厂 | 一组相关产品由同一工厂族创建 | 满足 | 高 | 跨平台/跨主题的多产品族 |
在实际开发中,如果产品种类不多且变化不频繁,简单工厂往往是最务实的选择。设计模式的目的是解决问题,而不是为了使用而使用。
1.3 建造者模式
意图: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
适用场景: 对象有大量可选参数、构造过程复杂且需要分步骤、需要创建不可变对象时。
经典实现
public class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private final int timeout;
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = Collections.unmodifiableMap(builder.headers);
this.body = builder.body;
this.timeout = builder.timeout;
}
public static class Builder {
// 必选参数
private final String url;
// 可选参数(有默认值)
private String method = "GET";
private Map<String, String> headers = new HashMap<>();
private String body = null;
private int timeout = 5000;
public Builder(String url) {
this.url = url;
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder addHeader(String key, String value) {
this.headers.put(key, value);
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder timeout(int timeout) {
this.timeout = timeout;
return this;
}
public HttpRequest build() {
return new HttpRequest(this);
}
}
// getter 方法省略...
}
客户端调用:
HttpRequest request = new HttpRequest.Builder("https://api.example.com/users")
.method("POST")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer token")
.body("{\"name\": \"test\"}")
.timeout(10000)
.build();
Lombok @Builder
在实际项目中,通常使用 Lombok 的 @Builder 注解来简化建造者模式的编写:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaidMessage {
private Long orderId;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
private long timestamp;
}
Lombok 会在编译期自动生成 Builder 内部类和 builder() 静态方法,用法如下:
OrderPaidMessage message = OrderPaidMessage.builder()
.orderId(1L)
.orderNo("ORD20260506001")
.userId(100L)
.totalAmount(new BigDecimal("99.00"))
.timestamp(System.currentTimeMillis())
.build();
建造者模式的几个要点:
- 构造器参数设为
final,确保对象构建完成后不可变 build()方法中可以做参数校验(Lombok 可通过自定义 builder 方法实现)- 如果参数超过 4 个且其中大部分是可选的,就应该考虑使用建造者模式
二、结构型模式
结构型模式关注类和对象的组合方式,通过继承和组合来形成更大的结构。
2.1 装饰器模式
意图: 动态地给一个对象添加一些额外的职责,比生成子类更加灵活。
适用场景: 需要在不修改原有类的前提下扩展功能、功能可以动态组合或撤销时。
标准实现
// 组件接口
public interface DataSource {
String readData();
void writeData(String data);
}
// 具体组件
public class FileDataSource implements DataSource {
private final String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
@Override
public String readData() {
// 从文件读取数据
return "raw data from " + filename;
}
@Override
public void writeData(String data) {
// 写入文件
System.out.println("写入文件: " + filename);
}
}
// 装饰器基类
public abstract class DataSourceDecorator implements DataSource {
protected final DataSource wrapped;
protected DataSourceDecorator(DataSource source) {
this.wrapped = source;
}
@Override
public String readData() {
return wrapped.readData();
}
@Override
public void writeData(String data) {
wrapped.writeData(data);
}
}
// 加密装饰器
public class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) {
super(source);
}
@Override
public String readData() {
String data = super.readData();
return decrypt(data);
}
@Override
public void writeData(String data) {
super.writeData(encrypt(data));
}
private String encrypt(String data) { /* 加密逻辑 */ return "encrypted:" + data; }
private String decrypt(String data) { /* 解密逻辑 */ return data.replace("encrypted:", ""); }
}
// 压缩装饰器
public class CompressionDecorator extends DataSourceDecorator {
public CompressionDecorator(DataSource source) {
super(source);
}
@Override
public String readData() {
String data = super.readData();
return decompress(data);
}
@Override
public void writeData(String data) {
super.writeData(compress(data));
}
private String compress(String data) { /* 压缩逻辑 */ return "compressed:" + data; }
private String decompress(String data) { /* 解压逻辑 */ return data.replace("compressed:", ""); }
}
客户端调用:
DataSource source = new FileDataSource("data.txt");
DataSource encrypted = new EncryptionDecorator(source);
DataSource encryptedAndCompressed = new CompressionDecorator(encrypted);
encryptedAndCompressed.writeData("important data"); // 先加密再压缩再写入
String result = encryptedAndCompressed.readData(); // 先读取再解压再解密
装饰器模式的核心特征:
- 装饰器与被装饰对象实现相同的接口
- 装饰器内部持有被装饰对象的引用
- 装饰器可以在调用被装饰对象的前后添加额外行为
- 装饰器可以层层嵌套,形成装饰链
与继承的对比: 装饰器模式比继承更加灵活。如果用继承实现上述加密+压缩的功能,需要
EncryptionDataSource、CompressionDataSource、EncryptionCompressionDataSource等多个子类,组合数量随功能维度呈指数增长。装饰器模式则可以任意组合。
Java I/O 体系是装饰器模式的经典应用:BufferedInputStream(new GZIPInputStream(new FileInputStream("data.gz"))) 就是通过多层装饰实现了缓冲+解压缩+文件读取的功能组合。
2.2 桥接模式
意图: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
适用场景: 一个类存在两个或多个独立变化的维度,且这两个维度都需要扩展时。
标准实现
// 实现接口(形状的颜色)
public interface Color {
String applyColor();
}
// 具体实现
public class Red implements Color {
@Override
public String applyColor() { return "红色"; }
}
public class Blue implements Color {
@Override
public String applyColor() { return "蓝色"; }
}
// 抽象部分(形状)
public abstract class Shape {
protected final Color color;
protected Shape(Color color) {
this.color = color;
}
public abstract String draw();
}
// 扩展抽象
public class Circle extends Shape {
protected Circle(Color color) {
super(color);
}
@Override
public String draw() {
return "圆形,颜色: " + color.applyColor();
}
}
public class Square extends Shape {
protected Square(Color color) {
super(color);
}
@Override
public String draw() {
return "正方形,颜色: " + color.applyColor();
}
}
客户端调用:
Shape redCircle = new Circle(new Red());
Shape blueSquare = new Square(new Blue());
System.out.println(redCircle.draw()); // 圆形,颜色: 红色
System.out.println(blueSquare.draw()); // 正方形,颜色: 蓝色
桥接 vs 继承: 如果不使用桥接模式,要支持 N 种形状 x M 种颜色,需要 N x M 个子类。使用桥接模式后,只需要 N + M 个类。桥接的本质是"组合优于继承"这一原则在特定场景下的应用。
JDBC 中的桥接模式
JDBC 是桥接模式的一个经典应用。java.sql.Driver 是实现接口,每个数据库厂商(MySQL、PostgreSQL 等)提供自己的实现。DriverManager 是抽象部分,通过 Driver 接口与具体数据库驱动交互,应用代码只依赖 JDBC API,不关心底层驱动实现。
2.3 适配器模式
意图: 将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。
适用场景: 需要复用已有的类但接口不匹配、集成第三方库但接口风格不一致时。
类适配器(通过继承)
// 已有的第三方接口
public class LegacyLogger {
public void logMessage(String level, String msg) {
System.out.println("[" + level + "] " + msg);
}
}
// 目标接口
public interface ModernLogger {
void info(String msg);
void error(String msg);
}
// 适配器(继承被适配者,实现目标接口)
public class LoggerAdapter extends LegacyLogger implements ModernLogger {
@Override
public void info(String msg) {
logMessage("INFO", msg);
}
@Override
public void error(String msg) {
logMessage("ERROR", msg);
}
}
对象适配器(通过组合,推荐)
public class LoggerAdapter implements ModernLogger {
private final LegacyLogger legacyLogger;
public LoggerAdapter(LegacyLogger legacyLogger) {
this.legacyLogger = legacyLogger;
}
@Override
public void info(String msg) {
legacyLogger.logMessage("INFO", msg);
}
@Override
public void error(String msg) {
legacyLogger.logMessage("ERROR", msg);
}
}
类适配器 vs 对象适配器: 类适配器通过继承实现,可以重写被适配者的方法,但受限于 Java 的单继承;对象适配器通过组合实现,更灵活,可以适配被适配者的子类,是实践中更常用的方式。
2.4 代理模式
意图: 为其他对象提供一种代理以控制对这个对象的访问。
适用场景: 远程代理(RPC)、虚拟代理(延迟加载)、保护代理(权限控制)、智能引用(AOP)。
静态代理
public interface UserService {
User getUserById(Long id);
void createUser(User user);
}
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) {
System.out.println("查询数据库获取用户: " + id);
return new User(id, "张三");
}
@Override
public void createUser(User user) {
System.out.println("插入数据库: " + user);
}
}
// 代理类
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public User getUserById(Long id) {
System.out.println("[代理] 缓存检查...");
User user = target.getUserById(id);
System.out.println("[代理] 缓存结果...");
return user;
}
@Override
public void createUser(User user) {
System.out.println("[代理] 权限校验...");
target.createUser(user);
System.out.println("[代理] 记录日志...");
}
}
静态代理的缺点: 每个被代理的接口都需要手动编写一个代理类,接口方法变更时代理类也需要同步修改,维护成本高。
JDK 动态代理
public class JdkProxy implements InvocationHandler {
private final Object target;
public JdkProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[动态代理] 前置增强");
Object result = method.invoke(target, args);
System.out.println("[动态代理] 后置增强");
return result;
}
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkProxy(target)
);
}
}
客户端调用:
UserService userService = JdkProxy.createProxy(new UserServiceImpl());
userService.getUserById(1L); // 自动触发代理逻辑
JDK 动态代理 vs CGLIB: JDK 动态代理要求目标类实现至少一个接口,通过接口创建代理对象;CGLIB 通过生成目标类的子类来实现代理,不需要接口。Spring AOP 默认策略是:目标类实现了接口用 JDK 代理,否则用 CGLIB。在现代 Spring Boot 项目中(Spring Boot 2.0+),默认统一使用 CGLIB。
Spring AOP 中的代理模式
Spring 的 @Transactional、@Cacheable、@Async 等注解本质上都是通过代理模式实现的:
@Service
public class UserServiceImpl implements UserService {
@Cacheable(value = "user", key = "#id")
@Override
public UserResponse getUserById(Long id) {
// 业务逻辑
return userMapper.selectById(id);
}
}
当调用 getUserById() 时,实际执行的是 Spring 生成的代理对象。代理对象在方法执行前检查缓存,命中则直接返回,未命中才执行目标方法并缓存结果。这就是代理模式在 AOP 中的典型应用。
2.5 外观模式
意图: 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,使得子系统更加容易使用。
适用场景: 子系统复杂、需要简化调用链路、需要为子系统提供分层访问入口时。
标准实现
// 子系统 A:视频编解码
public class VideoEncoder {
public byte[] encode(String format, byte[] rawData) {
System.out.println("使用 " + format + " 编解码器编码视频");
return rawData; // 简化示例
}
}
// 子系统 B:音频处理
public class AudioProcessor {
public byte[] normalize(byte[] audioData) {
System.out.println("音频标准化处理");
return audioData;
}
}
// 子系统 C:字幕合成
public class SubtitleRenderer {
public byte[] render(byte[] videoData, String subtitleFile) {
System.out.println("合成字幕: " + subtitleFile);
return videoData;
}
}
// 外观类
public class MediaConverterFacade {
private final VideoEncoder encoder = new VideoEncoder();
private final AudioProcessor audioProcessor = new AudioProcessor();
private final SubtitleRenderer subtitleRenderer = new SubtitleRenderer();
public byte[] convertToMp4(byte[] rawVideo, byte[] rawAudio, String subtitleFile) {
byte[] encoded = encoder.encode("H.264", rawVideo);
byte[] normalized = audioProcessor.normalize(rawAudio);
byte[] withSubtitle = subtitleRenderer.render(encoded, subtitleFile);
System.out.println("MP4 转换完成");
return withSubtitle;
}
}
客户端调用:
// 不使用外观:客户端需要了解所有子系统的 API
VideoEncoder encoder = new VideoEncoder();
AudioProcessor audio = new AudioProcessor();
SubtitleRenderer subtitle = new SubtitleRenderer();
byte[] step1 = encoder.encode("H.264", rawVideo);
byte[] step2 = audio.normalize(rawAudio);
byte[] step3 = subtitle.render(step1, subtitleFile);
// 使用外观:一个调用搞定
MediaConverterFacade converter = new MediaConverterFacade();
byte[] result = converter.convertToMp4(rawVideo, rawAudio, subtitleFile);
外观模式在框架层面也有广泛应用。Spring Boot 的自动配置(Auto-Configuration)本质上就是一种外观模式 – 它将繁琐的手动配置封装成简单的 @SpringBootApplication 启动方式,开发者不需要了解底层 DataSource、EntityManager、TransactionManager 等组件的具体配置方式。
三、行为型模式
行为型模式关注对象之间的通信机制,描述对象之间怎样协作完成任务。
3.1 模板方法模式
意图: 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。
适用场景: 多个类有相似的业务流程但个别步骤不同、需要控制子类扩展点的固定算法骨架。
标准实现
public abstract class DataParser {
// 模板方法(final 防止子类重写骨架)
public final void parse(String filePath) {
String rawData = readFile(filePath);
Object parsed = parseData(rawData);
validateData(parsed);
saveData(parsed);
postProcess();
}
// 具体方法(子类共享)
private String readFile(String filePath) {
System.out.println("读取文件: " + filePath);
return "raw content";
}
// 抽象方法(子类必须实现)
protected abstract Object parseData(String rawData);
protected abstract void validateData(Object data);
// 钩子方法(子类可选重写)
protected void saveData(Object data) {
System.out.println("默认保存逻辑");
}
protected void postProcess() {
// 默认空实现,子类可选重写
}
}
public class JsonDataParser extends DataParser {
@Override
protected Object parseData(String rawData) {
System.out.println("JSON 格式解析");
return new Object();
}
@Override
protected void validateData(Object data) {
System.out.println("JSON Schema 校验");
}
@Override
protected void saveData(Object data) {
System.out.println("保存到 MongoDB");
}
}
public class CsvDataParser extends DataParser {
@Override
protected Object parseData(String rawData) {
System.out.println("CSV 格式解析");
return new Object();
}
@Override
protected void validateData(Object data) {
System.out.println("CSV 列数校验");
}
}
模板方法中的方法类型
| 方法类型 | 说明 | 是否必须重写 |
|---|---|---|
| 模板方法 | 定义算法骨架,通常是 final |
不允许重写 |
| 抽象方法 | 算法中的必要步骤 | 子类必须实现 |
| 钩子方法 | 提供默认实现,子类可选重写 | 可选 |
| 具体方法 | 所有子类共享的通用逻辑 | 不应该重写 |
MyBatis 中的模板方法
MyBatis 的 BaseExecutor 是模板方法模式的经典实现。BaseExecutor 定义了 SQL 执行的核心骨架(update()、query() 方法),其中 doUpdate()、doQuery() 等是抽象方法,由 SimpleExecutor、ReuseExecutor、BatchExecutor 等子类实现具体的执行逻辑。同时 SqlSession 的标准使用流程(获取 SqlSession -> 获取 Mapper -> 执行 SQL -> 提交/回滚 -> 关闭)也是一个典型的模板方法。
3.2 策略模式
意图: 定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户端变化。
适用场景: 同一个任务有多种处理方式、需要在运行时动态切换算法、避免大量条件判断语句时。
标准实现
// 策略接口
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// 具体策略
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付宝支付: " + amount + " 元");
}
}
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("微信支付: " + amount + " 元");
}
}
public class CreditCardStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("信用卡支付: " + amount + " 元");
}
}
// 上下文
public class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(BigDecimal amount) {
strategy.pay(amount);
}
}
客户端调用:
PaymentContext context = new PaymentContext();
context.setStrategy(new AlipayStrategy());
context.executePayment(new BigDecimal("99.00")); // 支付宝支付
context.setStrategy(new WechatPayStrategy());
context.executePayment(new BigDecimal("49.50")); // 微信支付
策略模式 vs if-else
策略模式的核心价值在于消除冗长的 if-else / switch 分支。当 if-else 中的每个分支都代表一种独立的算法时,就应该考虑将其提取为策略:
// 策略模式通常配合工厂 + 枚举/Map 来消除条件判断
public enum PaymentType {
ALIPAY, WECHAT, CREDIT_CARD
}
public class PaymentStrategyFactory {
private static final Map<PaymentType, PaymentStrategy> STRATEGIES = Map.of(
PaymentType.ALIPAY, new AlipayStrategy(),
PaymentType.WECHAT, new WechatPayStrategy(),
PaymentType.CREDIT_CARD, new CreditCardStrategy()
);
public static PaymentStrategy getStrategy(PaymentType type) {
return STRATEGIES.get(type);
}
}
3.3 观察者模式
意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
适用场景: 事件驱动架构、消息通知、数据变更同步、解耦生产者和消费者。
标准实现
// 观察者接口
public interface EventListener {
void onEvent(Object event);
}
// 事件发布者
public class EventBus {
private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
public void register(EventListener listener) {
listeners.add(listener);
}
public void unregister(EventListener listener) {
listeners.remove(listener);
}
public void publish(Object event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
// 具体观察者
public class EmailNotificationListener implements EventListener {
@Override
public void onEvent(Object event) {
System.out.println("发送邮件通知: " + event);
}
}
public class LogListener implements EventListener {
@Override
public void onEvent(Object event) {
System.out.println("记录日志: " + event);
}
}
客户端调用:
EventBus bus = new EventBus();
bus.register(new EmailNotificationListener());
bus.register(new LogListener());
bus.publish("用户注册成功"); // 自动通知所有观察者
Spring 事件机制
Spring 的 ApplicationEvent 体系是观察者模式的内置实现,支持同步和异步两种模式:
// 定义事件(继承 ApplicationEvent)
@Getter
public class UserCreatedEvent extends ApplicationEvent {
private final Long userId;
private final String username;
public UserCreatedEvent(Object source, Long userId, String username) {
super(source);
this.userId = userId;
this.username = username;
}
}
// 发布事件(通过 ApplicationEventPublisher)
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final ApplicationEventPublisher eventPublisher;
public Long createUser(UserCreateRequest request) {
// ... 业务逻辑
eventPublisher.publishEvent(
new UserCreatedEvent(this, user.getId(), user.getUsername())
);
return user.getId();
}
}
// 监听事件(通过 @EventListener)
@Component
public class UserEventListener {
@Async
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
// 异步发送欢迎邮件、初始化默认数据等
System.out.println("用户注册通知: " + event.getUsername());
}
}
观察者模式的变体对比
| 变体 | 通信范围 | 持久化 | 可靠性 | 典型实现 |
|---|---|---|---|---|
| 进程内观察者 | 单 JVM 内 | 否 | JVM 崩溃即丢失 | Spring ApplicationEvent |
| 跨进程发布订阅 | 多 JVM / 服务间 | 消息持久化到 Broker | 高(支持重试、死信队列) | Kafka、RabbitMQ |
| 分布式事件驱动 | 跨网络 | 持久化 | 最高(支持 exactly-once) | Kafka Streams、EventBridge |
在单体应用中,Spring Event 足以应对大部分异步解耦需求;在微服务架构下,通常需要引入 Kafka 或 RabbitMQ 实现跨进程的事件驱动。
3.4 责任链模式
意图: 将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
适用场景: 请求需要经过多个处理步骤、处理步骤可以动态增减、每个步骤可以决定放行或终止请求时。
标准实现
// 抽象处理器
public abstract class Handler {
protected Handler next;
public Handler setNext(Handler next) {
this.next = next;
return next; // 支持链式调用
}
public abstract boolean handle(String request);
}
// 具体处理器
public class AuthHandler extends Handler {
@Override
public boolean handle(String request) {
if (!request.contains("token")) {
System.out.println("认证失败:缺少 token");
return false;
}
System.out.println("认证通过");
return next == null || next.handle(request);
}
}
public class RateLimitHandler extends Handler {
@Override
public boolean handle(String request) {
if (request.contains("blocked")) {
System.out.println("限流拦截:请求过于频繁");
return false;
}
System.out.println("限流通过");
return next == null || next.handle(request);
}
}
public class LogHandler extends Handler {
@Override
public boolean handle(String request) {
System.out.println("记录访问日志: " + request);
return next == null || next.handle(request);
}
}
客户端调用:
Handler auth = new AuthHandler();
Handler rateLimit = new RateLimitHandler();
Handler log = new LogHandler();
auth.setNext(rateLimit).setNext(log);
auth.handle("request with token"); // 认证通过 -> 限流通过 -> 记录日志
auth.handle("blocked request"); // 认证通过 -> 限流拦截
auth.handle("request without token"); // 认证失败
Servlet Filter 与 Spring Security FilterChain
Servlet 规范中的 Filter 和 Spring Security 的 SecurityFilterChain 是责任链模式在 Web 开发中最典型的应用。每个 HTTP 请求都会依次经过 Filter 链中的所有过滤器,任何一个过滤器都可以决定放行(chain.doFilter())或终止(直接返回响应)。
HTTP 请求
|
v
[TraceFilter] -- 生成 TraceId,写入 MDC
|
v
[RateLimitInterceptor] -- 接口限流检查
|
v
[JwtAuthenticationFilter] -- JWT 认证
|
v
[SecurityFilterChain] -- Spring Security 授权
|
v
[Controller] -- 业务处理
|
v
HTTP 响应(原路返回)
四、分析实际项目中的设计模式
以下基于 LinkForge(Spring Boot + MyBatis + Redis + Kafka 技术栈)项目中的实际代码,分析设计模式在真实业务场景中的应用。
LinkForge项目GitHub地址.
4.1 适配器模式 – Jackson3JsonRedisSerializer
项目自定义了 Jackson3JsonRedisSerializer,实现 Spring Data Redis 的 RedisSerializer<Object> 接口。这个类的职责是将 Java 对象"适配"成 Redis 能存储的字节数组格式。
public class Jackson3JsonRedisSerializer implements RedisSerializer<Object> {
private final ObjectMapper mapper;
public Jackson3JsonRedisSerializer() {
this.mapper = JsonMapper.builder()
.findAndAddModules()
.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Object.class)
.build(),
DefaultTyping.NON_FINAL
)
.build();
}
@Override
public byte[] serialize(Object value) throws SerializationException {
if (value == null) return new byte[0];
return mapper.writeValueAsBytes(value); // Object -> JSON bytes
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) return null;
return mapper.readValue(bytes, Object.class); // JSON bytes -> Object
}
}
模式分析: RedisSerializer<Object> 是目标接口,Jackson3JsonRedisSerializer 是适配器,ObjectMapper(Jackson 3)是被适配者。Spring Data Redis 只需要知道 RedisSerializer 接口,不关心底层具体用哪种 JSON 序列化库。
在 RedisConfig 中使用:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson3JsonRedisSerializer()); // 注入适配器
template.afterPropertiesSet();
return template;
}
}
4.2 代理模式 – Spring AOP 的实际应用
项目中大量使用了 Spring AOP 提供的声明式注解,这些注解的底层实现都是代理模式:
@Service
public class UserServiceImpl implements UserService {
@Cacheable(value = "user", key = "#id", unless = "#result == null")
@Override
public UserResponse getUserById(Long id) {
// 实际执行时,调用的是 Spring 生成的代理对象
// 代理对象会先查 Redis 缓存,命中则直接返回
User user = userMapper.selectById(id);
return toResponse(user);
}
@Transactional(rollbackFor = Exception.class)
public Long createUser(UserCreateRequest request) {
// 代理对象负责开启事务、提交/回滚
userMapper.insert(user);
return user.getId();
}
}
模式分析: 当调用 getUserById() 时,实际执行的并非 UserServiceImpl 本身的方法,而是 Spring 通过 CGLIB 生成的代理对象的方法。代理对象在目标方法执行前后插入缓存查询、事务管理等横切逻辑。调用方完全感知不到代理的存在,这就是代理模式的精髓。
4.3 观察者/发布订阅模式 – Spring Event 与 Kafka
项目中同时使用了两种层次的发布订阅机制:
进程内:Spring ApplicationEvent
用户注册成功后发布事件,异步执行非核心逻辑(发送欢迎邮件、初始化默认数据):
// 事件定义
@Getter
public class UserCreatedEvent extends ApplicationEvent {
private final Long userId;
private final String username;
private final String nickname;
// ...
}
// 事件发布(在 Service 中)
eventPublisher.publishEvent(
new UserCreatedEvent(this, user.getId(), user.getUsername(), user.getNickname())
);
// 事件监听(异步处理)
@Async
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
log.info("[异步] 用户注册成功,发送欢迎通知: userId={}", event.getUserId());
}
@Async
@EventListener
public void handleUserCreated_InitDefaults(UserCreatedEvent event) {
log.info("[异步] 初始化用户默认数据: userId={}", event.getUserId());
}
跨进程:Kafka
订单支付/取消事件通过 Kafka 实现跨进程的发布订阅:
// 生产者:发送消息到 Kafka
@Component
@RequiredArgsConstructor
public class OrderKafkaProducer {
private final KafkaTemplate<String, Object> kafkaTemplate;
public void sendOrderPaid(OrderPaidMessage message) {
kafkaTemplate.send("order-paid",
String.valueOf(message.getUserId()), // partition key,保证同一用户消息有序
message);
}
}
// 消费者:从 Kafka 消费消息
@KafkaListener(topics = "${order.topic.paid:order-paid}",
groupId = "order-notification-group")
public void handleOrderPaid(@Payload OrderPaidMessage message,
Acknowledgment ack) {
// 通知仓库备货、发送短信、更新积分...
ack.acknowledge(); // 手动 ACK
}
两者的选择逻辑:
| 维度 | Spring Event | Kafka |
|---|---|---|
| 通信范围 | 单 JVM 内 | 跨 JVM / 服务 |
| 消息持久化 | 否(JVM 崩溃即丢失) | 是(持久化到 Broker) |
| 可靠性 | 中(可配合 @TransactionalEventListener) |
高(支持手动 ACK + 重试 + 死信队列) |
| 适用场景 | 同一服务内的异步解耦 | 微服务间的事件驱动、需要持久化的异步处理 |
项目中,用户注册通知使用 Spring Event(轻量、快速),订单支付通知使用 Kafka(需要持久化、可能被其他微服务消费),这是根据实际需求做出的合理选择。
4.4 策略模式 – RateLimitInterceptor 限流策略
RateLimitInterceptor 对不同接口使用不同的限流策略,虽然实现上是用 if-else 路由到不同的 LoadingCache,但本质上是策略模式的思想:
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
// 策略 1:登录接口限流(5次/分钟/IP)
private final LoadingCache<String, RateLimiter> loginRateLimiters = CacheBuilder.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<>() {
@Override
public RateLimiter load(String key) {
return RateLimiter.create(5.0 / 60.0); // 5 次/60秒
}
});
// 策略 2:注册接口限流(10次/小时/IP)
private final LoadingCache<String, RateLimiter> registerRateLimiters = CacheBuilder.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(2, TimeUnit.HOURS)
.build(new CacheLoader<>() {
@Override
public RateLimiter load(String key) {
return RateLimiter.create(10.0 / 3600.0); // 10 次/3600秒
}
});
// 策略 3:通用接口限流(100 QPS/IP)
private final LoadingCache<String, RateLimiter> globalRateLimiters = CacheBuilder.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<>() {
@Override
public RateLimiter load(String key) {
return RateLimiter.create(100.0);
}
});
// 策略路由
private boolean isAllowed(String ip, String uri, String method) throws ExecutionException {
if ("/api/auth/login".equals(uri) && "POST".equalsIgnoreCase(method)) {
return loginRateLimiters.get(ip).tryAcquire();
}
if ("/api/users".equals(uri) && "POST".equalsIgnoreCase(method)) {
return registerRateLimiters.get(ip).tryAcquire();
}
return globalRateLimiters.get(ip).tryAcquire();
}
}
模式分析: 每种限流策略(登录/注册/通用)对应一个 LoadingCache<RateLimiter>,它们共享相同的接口(tryAcquire()),但配置不同的速率参数。当接口数量增加时,可以将路由逻辑提取为 Map<String, RateLimiterSupplier> 进一步优化。
4.5 责任链模式 – Spring Security 过滤器链
项目的 SecurityConfig 配置了标准的过滤器链,HTTP 请求依次经过认证过滤器:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/users").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
其中 JwtAuthenticationFilter 的实现遵循责任链模式中的"放行或终止"逻辑:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = extractToken(request);
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
// 认证通过,设置 SecurityContext
Long userId = jwtUtil.getUserId(token);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null,
List.of(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(auth);
}
// 放行到下一个过滤器
filterChain.doFilter(request, response);
}
}
请求经过过滤器链的顺序:TraceFilter(生成 TraceId) -> RateLimitInterceptor(限流检查) -> JwtAuthenticationFilter(JWT 认证) -> AuthorizationFilter(权限校验) -> Controller。任何一个环节都可以决定终止请求。
4.6 单例模式 – Spring Bean 与 SnowflakeIdGenerator
Spring 容器管理的 Bean 默认作用域就是单例(singleton)。项目中的 SnowflakeIdGenerator 通过 @Component 注册为 Spring Bean,由容器保证全局唯一实例:
@Component // Spring 默认 singleton 作用域
public class SnowflakeIdGenerator {
// 雪花算法生成分布式唯一 ID
// 1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
private final long workerId;
private final long datacenterId;
public synchronized long nextId() {
// ...
}
}
模式分析: 这里不需要手动实现单例模式。Spring 的 IoC 容器本身就是一个"单例工厂" – 通过 @Component、@Service、@Configuration 等注解声明的 Bean 默认都是单例的,由容器保证线程安全和全局唯一。这也体现了"不要重复造轮子"的原则:在框架已经提供了单例管理能力的情况下,手动实现 DCL 或静态内部类反而多余。
4.7 建造者模式 – Lombok @Builder 在 DTO 中的应用
项目中大量使用 Lombok @Builder 简化复杂对象的构建过程,以 OrderPaidMessage 为例:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaidMessage {
private Long orderId;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
private long timestamp;
}
使用时通过链式调用构建对象:
OrderPaidMessage message = OrderPaidMessage.builder()
.orderId(order.getId())
.orderNo(order.getOrderNo())
.userId(order.getUserId())
.totalAmount(order.getTotalAmount())
.timestamp(System.currentTimeMillis())
.build();
相比调用全参构造器或逐步 setter,Builder 的可读性更好,参数含义一目了然,且不会出现参数顺序传错的问题。
五、总结与思考
设计模式的本质
设计模式的本质是对面向对象设计原则的实践总结,是前人解决重复问题的经验沉淀。GoF 23 种设计模式并不是需要死记硬背的公式,而是理解其背后思想后在合适场景下的自然运用。
使用原则
- 不要为了使用而使用: 设计模式解决的是特定问题。如果问题不存在,引入模式只会增加不必要的复杂度。
- 识别问题,再找模式: 遇到"需要解耦"时想到观察者模式,遇到"需要统一处理"时想到拦截器/责任链,遇到"需要灵活替换"时想到策略模式。
- 框架已经帮你做了很多: Spring 框架内置了大量设计模式的实现(单例、代理、观察者、工厂方法、模板方法等),实际开发中更重要的是理解框架的设计思想,而不是手动重复实现。
- 适度即可: 3 层继承已经够复杂了,不需要为了"扩展性"预留 7 层抽象。YAGNI(You Aren’t Gonna Need It)原则同样适用于设计模式的使用。
项目中的设计模式总结
| 分类 | 模式 | 项目中的体现 |
|---|---|---|
| 创建型 | 单例模式 | Spring Bean 默认单例(@Component) |
| 创建型 | 建造者模式 | Lombok @Builder 在 DTO 中的应用 |
| 结构型 | 适配器模式 | Jackson3JsonRedisSerializer 适配 Redis 序列化接口 |
| 结构型 | 代理模式 | Spring AOP(@Cacheable、@Transactional) |
| 行为型 | 观察者模式 | Spring ApplicationEvent + Kafka 发布订阅 |
| 行为型 | 策略模式 | RateLimitInterceptor 多策略限流 |
| 行为型 | 责任链模式 | Spring Security 过滤器链 |
| 行为型 | 模板方法模式 | MyBatis BaseExecutor 执行骨架 |
更多推荐



所有评论(0)