3 分钟让你彻底搞懂 Spring 观察者和发布者模式的本质区别

前言

大家好,我是大华!这篇文章我们来讲讲Spring中的两种设计模式:观察者模式和发布订阅模式

我发现很多文章讲这两个模式时,都是用类似的代码示例,结果读者看完更迷糊了。今天我用完全不同的角度,带大家看清它们的本质区别!

核心概念

首先需要明确:发布订阅模式是观察者模式的一种演进和扩展,两者是同一思想谱系上的不同实现阶段:

  • 观察者模式(直接观察):目标对象直接维护观察者列表并通知,结构上有依赖但通过接口解耦
  • 发布订阅模式(通过中介):引入事件通道,发布者和订阅者完全解耦,不知彼此存在

下面举例:两种不同的通信模型

观察者模式:像部门内部的晨会

  • 经理(被观察者)直接通知团队成员(观察者)
  • 面对面沟通,实时确认理解
  • 经理知道每个成员的存在,但通过会议议程(接口)规范沟通

发布订阅模式:像城市报纸分发系统

  • 报社(发布者)只负责印刷报纸并送到邮局
  • 邮局(事件通道)负责分发给各个订阅者
  • 报社不知道谁订阅了报纸,订阅者也不知道报纸如何印刷

代码层面的区别

观察者模式:直接调用,强耦合

java 复制代码
// 1. 观察者接口 - 约定通信契约
public interface OrderObserver {
    void onOrderCreated(Order order);
}

// 2. 具体观察者实现
@Component
public class InventoryObserver implements OrderObserver {
    @Override
    public void onOrderCreated(Order order) {
        System.out.println("库存观察者:同步检查库存");
        // 必须立即完成,订单创建依赖此结果
    }
}

@Component 
public class PriceObserver implements OrderObserver {
    @Override
    public void onOrderCreated(Order order) {
        System.out.println("价格观察者:计算最终价格");
        // 实时计算,影响订单金额
    }
}

// 3. 被观察的主题 - 利用Spring实现松耦合
@Service
public class OrderService {
    
    // Spring自动注入所有OrderObserver实现
    @Autowired(required = false)
    private List<OrderObserver> observers = Collections.emptyList();
    
    public Order createOrder(Order order) {
        // 核心业务逻辑
        System.out.println("执行订单创建核心逻辑...");
        
        // 关键特征:同步调用观察者
        for (OrderObserver observer : observers) {
            observer.onOrderCreated(order); // 阻塞等待每个观察者处理完成
        }
        
        System.out.println("所有观察者处理完成,订单创建流程结束");
        return order;
    }
}

观察者模式的核心特征

  • 接口解耦:通过Observer接口依赖,不依赖具体实现类
  • 同步执行:观察者按顺序执行,主线程等待所有处理完成
  • 强一致性:所有观察者成功,整个操作才成功
  • 实时反馈:可以立即处理观察者的返回值或异常
  • 结构依赖:Subject仍然需要维护Observer集合

Spring事件机制:灵活支持两种模式

Spring的事件机制是一个强大的抽象层,根据配置可以体现不同模式的特征:

java 复制代码
// 1. 事件对象 - 纯数据载体
public class OrderCreatedEvent {
    private final Order order;
    private final LocalDateTime eventTime;
    
    public OrderCreatedEvent(Order order) {
        this.order = order;
        this.eventTime = LocalDateTime.now();
    }
    // getters...
}

// 2. 事件发布者
@Service
public class OrderService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public Order createOrder(Order order) {
        // 核心业务逻辑
        System.out.println("执行订单创建核心逻辑...");
        
        // 发布事件
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
        
        return order;
    }
}

模式一:同步事件(更接近观察者模式)

java 复制代码
@Component
public class SyncInventoryService {
    
    // 默认同步执行 - 发布者线程会阻塞等待此方法完成
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("同步处理库存: " + Thread.currentThread().getName());
        // 异常会传播到发布者,影响主流程
    }
}

特征:通过ApplicationContext中介,但同步阻塞,更像解耦版的观察者模式。

模式二:异步事件(真正的发布订阅模式)

java 复制代码
@Component
public class AsyncInventoryService {
    
    @EventListener
    @Async  // 关键注解,切换到异步模式
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("异步处理库存: " + Thread.currentThread().getName());
        try {
            Thread.sleep(2000); // 模拟耗时操作
            System.out.println("库存服务:库存扣减完成");
        } catch (Exception e) {
            // 异常不会影响主流程
            System.out.println("库存处理失败,需要后续补偿");
        }
    }
}

// 异步配置
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-event-");
        return executor;
    }
}

真正的发布订阅模式特征

  • 完全解耦:发布者不知道订阅者的存在
  • 异步处理:不阻塞主业务流程
  • 动态扩展:新增订阅者无需修改发布者代码
  • 容错性强:单个订阅者失败不影响其他处理

实际项目中的选择标准

选择观察者模式/同步事件当(需要强一致性):

核心业务验证场景

java 复制代码
@Service
public class OrderValidationService {
    
    @Autowired
    private List<OrderObserver> validators;
    
    public ValidationResult validateOrder(Order order) {
        // 需要立即得到所有验证结果
        for (OrderObserver validator : validators) {
            ValidationResult result = validator.validate(order);
            if (!result.isValid()) {
                return result; // 实时返回失败
            }
        }
        return ValidationResult.success();
    }
}

适用场景

  • 订单创建前的数据校验(库存、价格、风控)
  • 业务流程的状态转换验证
  • 需要实时反馈的业务规则检查

选择发布订阅模式当(可以最终一致性):

后续业务处理场景

java 复制代码
// 使用Spring异步事件(应用内发布订阅)
@Service
public class OrderPostProcessService {
    
    @EventListener
    @Async
    public void handleOrderSuccess(OrderSuccessEvent event) {
        // 这些操作可以异步完成,不影响主流程
        sendCongratulationsEmail(event.getOrder());
        updateUserLevel(event.getUserId());
        syncToDataWarehouse(event.getOrder());
    }
}

// 或使用消息中间件(分布式发布订阅)
@Service
public class DistributedNotificationService {
    
    @JmsListener(destination = "order.success.queue")
    public void handleOrderSuccess(Order order) {
        // 跨服务的异步处理
        notifyShippingService(order);
        updateBusinessIntelligence(order);
    }
}

适用场景

  • 用户注册后的欢迎流程
  • 订单支付成功后的后续处理
  • 数据同步、日志记录、统计分析
  • 微服务间的事件通知

混合使用的最佳实践

在实际复杂业务中,我们通常采用混合模式,根据业务需求选择合适的技术:

java 复制代码
@Service
public class ComprehensiveOrderService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public Order createOrder(Order order) {
        
        // 阶段一:同步观察者模式 - 强一致性保证
        validateOrder(order);     // 实时验证(观察者模式)
        deductInventory(order);   // 实时扣库存(观察者模式)
        processPayment(order);    // 实时支付(观察者模式)
        
        // 核心业务操作
        saveOrder(order);
        
        // 阶段二:应用内发布订阅 - 异步最终一致性
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
        
        return order;
    }
}

总结

理解这两种模式的关键在于认识它们的演进路径:

  1. 经典观察者模式:直接依赖,紧耦合
  2. 接口观察者模式:接口依赖,松耦合
  3. Spring同步事件:中介解耦,同步执行(观察者模式变体)
  4. Spring异步事件:中介解耦,异步执行(应用内发布订阅)

简单记忆法则

  • 观察者模式 = "打电话" → 需要对方立即接听并回应
  • 发布订阅模式 = "发邮件" → 发送后继续工作,对方稍后处理

现在你应该能清楚区分这两种模式了吧?希望这篇文章能帮助你在实际项目中,灵活的选择合适的技术方案。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《SpringBoot 中的 7 种耗时统计方式,你用过几种?》

《千万级大表如何新增字段?别再直接 ALTER 了》

《Vue3 如何优雅地实现一个全局的 loading 组件》

《Vue3+CSS实现一个非常丝滑的 input 标签上浮动画,设计师看了都点赞》

相关推荐
言之。2 小时前
LiteLLM:让LLM调用变得简单统一
后端·python·flask
没有bug.的程序员2 小时前
服务治理与 API 网关:微服务流量管理的艺术
java·分布式·微服务·架构·wpf
宠友信息2 小时前
java微服务驱动的社区平台:友猫社区的功能模块与实现逻辑
java·开发语言·微服务
驰羽2 小时前
[GO]golang接口入门:从一个简单示例看懂接口的多态与实现
开发语言·后端·golang
ZhengEnCi3 小时前
Python_try-except-finally 完全指南-从异常处理到程序稳定的 Python 编程利器
后端·python
Full Stack Developme3 小时前
jdk.random 包详解
java·开发语言·python
懒羊羊不懒@3 小时前
Java基础入门
java·开发语言
程序员小假4 小时前
我们来说一说 Redisson 的原理
java·后端