SpringBoot使用设计模式一观察者模式

一、前言

在实际开发中,经常会遇到这样的场景:当某个核心事件发生时,需要触发一系列关联操作。例如,用户完成订单支付后,系统需要自动执行发送支付成功短信、更新订单状态、扣减库存、生成物流单等操作。

如果按照传统的写法,可能会出现如下耦合度极高的代码:

public void paySuccess(String orderNo) {
    // 更新订单状态
    orderService.updateStatus(orderNo, "PAID");
    // 发送短信通知
    smsService.sendSms(orderNo);
    // 扣减库存
    stockService.reduceStock(orderNo);
    // 生成物流单
    logisticsService.createLogistics(orderNo);
    // 后续可能新增更多操作...
}

这种写法存在明显的问题:核心支付逻辑与后续关联操作强耦合,新增或删除操作时需要修改核心代码,违反开闭原则;代码冗长且可读性差,后续维护成本高。

针对这种“一个事件触发多个依赖操作”的场景,我们可以使用观察者模式来优雅解决。

二、观察者模式

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)都会自动收到通知并进行相应操作。

核心角色

  • 抽象被观察者(Subject):定义被观察者的核心行为,包括注册观察者、移除观察者、通知所有观察者的方法;
  • 具体被观察者(Concrete Subject):实现抽象被观察者接口,维护观察者列表,当自身状态变化时,调用通知方法触发所有观察者;
  • 抽象观察者(Observer):定义观察者的统一接口,规范观察者接收通知后需要执行的方法;
  • 具体观察者(Concrete Observer):实现抽象观察者接口,封装具体的业务逻辑,接收被观察者的通知并执行。

使用场景

  • 当一个对象的状态变化需要触发多个其他对象的行为,且这些对象之间是松散耦合的;
  • 当需要动态添加或移除关联操作,且不希望修改核心事件的代码;
  • 当系统中存在一对多的依赖关系,一个对象的变化需要同步通知多个对象。

优点

  • 降低耦合度:被观察者与观察者之间通过接口通信,互不依赖具体实现;
  • 符合开闭原则:新增观察者时无需修改被观察者代码,只需实现观察者接口;
  • 支持动态组合:可以灵活添加或移除观察者,动态调整响应行为;
  • 代码结构清晰:核心逻辑与关联操作分离,职责单一。

缺点

  • 如果观察者数量过多,通知所有观察者会导致性能开销增大;
  • 观察者与被观察者之间存在循环依赖时,可能会导致通知死循环;
  • 观察者无法知道被观察者状态变化的具体细节,只能被动接收通知。

三、实现案例

以“订单支付成功”事件为例,实现观察者模式,触发短信通知、库存扣减、物流生成等操作。

3.1 定义事件与枚举

首先定义订单相关枚举和事件实体,封装事件所需的核心数据:

订单状态枚举

@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum OrderStatusEnum {

    UNPAID("0", "未支付"),
    PAID("1", "已支付"),
    SHIPPED("2", "已发货"),
    COMPLETED("3", "已完成"),
    CANCELLED("4", "已取消");

    private String code;
    private String name;

    public static OrderStatusEnum getByCode(String code) {
        for (OrderStatusEnum status : values()) {
            if (status.getCode().equals(code)) {
                return status;
            }
        }
        throw new IllegalArgumentException("不支持的订单状态:" + code);
    }
}

支付成功事件实体

封装事件触发时需要传递的数据(如订单号、支付金额、支付时间等):

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderPayEvent {
    /**
     * 订单号
     */
    private String orderNo;
    /**
     * 支付金额(分)
     */
    private Integer payAmount;
    /**
     * 支付时间
     */
    private LocalDateTime payTime;
    /**
     * 支付方式(WECHAT/ALIPAY/UNIONPAY)
     */
    private String payMethod;
}

3.2 定义抽象观察者接口

定义观察者的统一接口,所有需要响应“支付成功”事件的操作都需实现该接口:

/**
 * 订单事件观察者接口
 */
public interface OrderObserver {
    /**
     * 接收支付成功事件通知并执行操作
     * @param event 支付成功事件实体
     */
    void onPaySuccess(OrderPayEvent event);
}

3.3 实现具体观察者类

针对不同的关联操作,实现具体的观察者类,封装各自的业务逻辑:

短信通知观察者

@Service
@Slf4j
public class SmsNotifyObserver implements OrderObserver {

    @Override
    public void onPaySuccess(OrderPayEvent event) {
        log.info("【短信通知】接收支付成功事件,订单号:{},开始发送支付成功短信", event.getOrderNo());
        // 业务逻辑:调用短信服务API发送通知
        // smsClient.sendSms("您的订单" + event.getOrderNo() + "已支付成功,感谢您的购买!");
        log.info("【短信通知】订单{}支付成功短信发送完成", event.getOrderNo());
    }
}

库存扣减观察者

@Service
@Slf4j
public class StockReduceObserver implements OrderObserver {

    @Override
    public void onPaySuccess(OrderPayEvent event) {
        log.info("【库存扣减】接收支付成功事件,订单号:{},开始扣减库存", event.getOrderNo());
        // 业务逻辑:查询订单商品,扣减对应库存
        // List<OrderItem> itemList = orderItemMapper.selectByOrderNo(event.getOrderNo());
        // itemList.forEach(item -> stockMapper.reduceStock(item.getProductId(), item.getQuantity()));
        log.info("【库存扣减】订单{}库存扣减完成", event.getOrderNo());
    }
}

物流单生成观察者

@Service
@Slf4j
public class LogisticsCreateObserver implements OrderObserver {

    @Override
    public void onPaySuccess(OrderPayEvent event) {
        log.info("【物流生成】接收支付成功事件,订单号:{},开始生成物流单", event.getOrderNo());
        // 业务逻辑:创建物流单记录,关联订单号
        // LogisticsOrder logistics = new LogisticsOrder();
        // logistics.setOrderNo(event.getOrderNo());
        // logistics.setCreateTime(LocalDateTime.now());
        // logisticsMapper.insert(logistics);
        log.info("【物流生成】订单{}物流单生成完成", event.getOrderNo());
    }
}

3.4 实现抽象被观察者(事件发布器)

定义被观察者接口,规范事件发布、观察者注册/移除的行为:

/**
 * 订单事件发布器(抽象被观察者)
 */
public interface OrderEventPublisher {
    /**
     * 注册观察者
     * @param observer 观察者对象
     */
    void registerObserver(OrderObserver observer);

    /**
     * 移除观察者
     * @param observer 观察者对象
     */
    void removeObserver(OrderObserver observer);

    /**
     * 发布支付成功事件,通知所有观察者
     * @param event 支付成功事件
     */
    void publishPaySuccessEvent(OrderPayEvent event);
}

3.5 实现具体被观察者(事件发布器实现)

基于Spring容器,实现事件发布器,自动扫描所有观察者并注册,同时提供事件发布能力:

@Service
@Slf4j
public class OrderEventPublisherImpl implements OrderEventPublisher {

    /**
     * 存储观察者列表(线程安全)
     */
    private final List<OrderObserver> observers = new CopyOnWriteArrayList<>();

    /**
     * 构造方法:Spring自动注入所有OrderObserver实现类并注册
     * @param observerList 观察者列表
     */
    public OrderEventPublisherImpl(List<OrderObserver> observerList) {
        this.observers.addAll(observerList);
        log.info("初始化订单事件发布器,注册观察者数量:{}", observerList.size());
    }

    @Override
    public void registerObserver(OrderObserver observer) {
        observers.add(observer);
        log.info("新增注册观察者:{}", observer.getClass().getSimpleName());
    }

    @Override
    public void removeObserver(OrderObserver observer) {
        observers.remove(observer);
        log.info("移除观察者:{}", observer.getClass().getSimpleName());
    }

    @Override
    public void publishPaySuccessEvent(OrderPayEvent event) {
        log.info("开始发布支付成功事件,订单号:{},触发观察者执行", event.getOrderNo());
        // 遍历所有观察者,触发事件处理
        for (OrderObserver observer : observers) {
            try {
                observer.onPaySuccess(event);
            } catch (Exception e) {
                log.error("观察者{}处理订单{}支付事件失败", 
                        observer.getClass().getSimpleName(), event.getOrderNo(), e);
                // 可选:是否继续执行其他观察者?根据业务场景决定
                // 此处选择继续执行,避免单个观察者异常影响整体流程
            }
        }
        log.info("支付成功事件发布完成,订单号:{}", event.getOrderNo());
    }
}

3.6 控制器层调用(事件触发)

在订单支付接口中,当支付逻辑执行成功后,通过事件发布器发布事件,触发所有观察者操作:

@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderEventPublisher orderEventPublisher;

    /**
     * 订单支付接口
     */
    @PostMapping("/pay")
    public R<?> payOrder(@RequestBody OrderPayRequest request) {
        log.info("接收订单支付请求,订单号:{}", request.getOrderNo());
        try {
            // 1. 执行核心支付逻辑(如调用支付网关、验证支付结果等)
            OrderPayResult payResult = orderService.doPay(request);
            if (!payResult.isSuccess()) {
                return R.fail("支付失败:" + payResult.getMsg());
            }

            // 2. 构造支付成功事件
            OrderPayEvent payEvent = new OrderPayEvent();
            payEvent.setOrderNo(request.getOrderNo());
            payEvent.setPayAmount(request.getPayAmount());
            payEvent.setPayTime(LocalDateTime.now());
            payEvent.setPayMethod(request.getPayMethod());

            // 3. 发布事件,触发所有观察者执行
            orderEventPublisher.publishPaySuccessEvent(payEvent);

            return R.success("支付成功", payResult);
        } catch (Exception e) {
            log.error("订单{}支付处理失败", request.getOrderNo(), e);
            return R.fail("支付处理异常:" + e.getMessage());
        }
    }
}

四、结合Spring事件机制优化实现

Spring框架自带了一套事件驱动机制(ApplicationEventApplicationListener),基于观察者模式设计,我们可以直接借助Spring原生能力简化实现,无需手动维护观察者列表和发布逻辑。

4.1 定义Spring事件(替代自定义事件实体)

继承SpringApplicationEvent,定义支付成功事件:

/**
 * Spring支付成功事件(集成Spring原生事件)
 */
public class SpringOrderPayEvent extends ApplicationEvent {

    private final OrderPayEvent payEvent;

    /**
     * 构造方法
     * @param source 事件源(通常为触发事件的对象)
     * @param payEvent 支付事件数据
     */
    public SpringOrderPayEvent(Object source, OrderPayEvent payEvent) {
        super(source);
        this.payEvent = payEvent;
    }

    // getter方法
    public OrderPayEvent getPayEvent() {
        return payEvent;
    }
}

4.2 实现Spring事件监听器(替代自定义观察者)

使用@EventListener注解或实现ApplicationListener接口,定义事件监听器(观察者):

方式1:@EventListener注解(推荐,更简洁)

@Service
@Slf4j
public class SmsNotifyListener {

    /**
     * 监听SpringOrderPayEvent事件
     */
    @EventListener
    public void onApplicationEvent(SpringOrderPayEvent event) {
        OrderPayEvent payEvent = event.getPayEvent();
        log.info("【Spring事件-短信通知】接收支付成功事件,订单号:{},开始发送短信", payEvent.getOrderNo());
        // 业务逻辑实现...
        log.info("【Spring事件-短信通知】订单{}短信发送完成", payEvent.getOrderNo());
    }
}

方式 2:实现ApplicationListener接口

@Service
@Slf4j
public class StockReduceListener implements ApplicationListener<SpringOrderPayEvent> {

    @Override
    public void onApplicationEvent(SpringOrderPayEvent event) {
        OrderPayEvent payEvent = event.getPayEvent();
        log.info("【Spring事件-库存扣减】接收支付成功事件,订单号:{},开始扣减库存", payEvent.getOrderNo());
        // 业务逻辑实现...
        log.info("【Spring事件-库存扣减】订单{}库存扣减完成", payEvent.getOrderNo());
    }
}

4.3 事件发布(使用Spring的ApplicationEventPublisher)

Spring容器默认提供了ApplicationEventPublisher实例,直接注入即可发布事件:

@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 注入Spring事件发布器
     */
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @PostMapping("/pay")
    public R<?> payOrder(@RequestBody OrderPayRequest request) {
        log.info("接收订单支付请求,订单号:{}", request.getOrderNo());
        try {
            // 1. 执行核心支付逻辑
            OrderPayResult payResult = orderService.doPay(request);
            if (!payResult.isSuccess()) {
                return R.fail("支付失败:" + payResult.getMsg());
            }

            // 2. 构造Spring事件
            OrderPayEvent payEvent = new OrderPayEvent();
            payEvent.setOrderNo(request.getOrderNo());
            payEvent.setPayAmount(request.getPayAmount());
            payEvent.setPayTime(LocalDateTime.now());
            payEvent.setPayMethod(request.getPayMethod());
            SpringOrderPayEvent springEvent = new SpringOrderPayEvent(this, payEvent);

            // 3. 发布Spring事件
            applicationEventPublisher.publishEvent(springEvent);

            return R.success("支付成功", payResult);
        } catch (Exception e) {
            log.error("订单{}支付处理失败", request.getOrderNo(), e);
            return R.fail("支付处理异常:" + e.getMessage());
        }
    }
}

4.4 异步事件处理(优化性能)

默认情况下,Spring事件发布是同步的,观察者执行完成后才会返回结果,可能导致接口响应变慢。可以通过@Async注解实现异步处理:

1. 开启异步支持

SpringBoot主类上添加@EnableAsync注解:

@SpringBootApplication
@EnableAsync
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

2. 在监听器方法上添加@Async注解

@Service
@Slf4j
public class SmsNotifyListener {

    @Async // 异步执行
    @EventListener
    public void onApplicationEvent(SpringOrderPayEvent event) {
        OrderPayEvent payEvent = event.getPayEvent();
        log.info("【异步短信通知】接收支付成功事件,订单号:{},开始发送短信", payEvent.getOrderNo());
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        log.info("【异步短信通知】订单{}短信发送完成", payEvent.getOrderNo());
    }
}

4.5 Spring事件机制优势

  • 无需手动维护观察者列表,Spring自动扫描监听器并注册;
  • 支持同步/异步执行,可灵活优化性能;
  • 集成Spring生态,支持依赖注入、事务管理等特性;
  • 提供事件传播机制、事件过滤等高级功能;
  • 代码更简洁,减少重复开发。

五、解决观察者模式的常见问题

5.1 观察者执行顺序控制

默认情况下,观察者的执行顺序是不确定的(取决于Spring扫描Bean的顺序)。如果需要指定执行顺序,可以使用@Order注解:

@Service
@Slf4j
public class SmsNotifyListener {

    @Order(1) // 顺序1:优先执行
    @EventListener
    public void onApplicationEvent(SpringOrderPayEvent event) {
        // 短信通知逻辑...
    }
}

@Service
@Slf4j
public class StockReduceListener {

    @Order(2) // 顺序2:次优先执行
    @EventListener
    public void onApplicationEvent(SpringOrderPayEvent event) {
        // 库存扣减逻辑...
    }
}

@Service
@Slf4j
public class LogisticsCreateListener {

    @Order(3) // 顺序3:最后执行
    @EventListener
    public void onApplicationEvent(SpringOrderPayEvent event) {
        // 物流生成逻辑...
    }
}

5.2 观察者异常处理

单个观察者执行异常可能影响其他观察者(同步场景),可以通过以下方式处理:

  • 局部异常捕获:在每个观察者方法内部捕获异常,避免异常传播;
  • 全局异常处理:实现ApplicationListener<ApplicationEventMulticaster.ListenerRetriesExhaustedEvent>接口,处理所有监听器的异常;
  • 异步场景:结合Spring@Async和异常处理器,单独处理异步任务的异常。

示例:局部异常捕获

@Service
@Slf4j
public class StockReduceListener {

    @EventListener
    public void onApplicationEvent(SpringOrderPayEvent event) {
        OrderPayEvent payEvent = event.getPayEvent();
        try {
            log.info("【库存扣减】接收支付成功事件,订单号:{}", payEvent.getOrderNo());
            // 业务逻辑...
        } catch (Exception e) {
            log.error("【库存扣减】订单{}处理失败", payEvent.getOrderNo(), e);
            // 可选:发送告警通知、记录异常日志等
        }
    }
}

5.3 动态添加/移除观察者

在某些场景下,需要根据业务逻辑动态添加或移除观察者。基于自定义事件发布器可以轻松实现:

@RestController
@RequestMapping("/observer")
public class ObserverManagerController {

    @Autowired
    private OrderEventPublisherImpl orderEventPublisher;

    @Autowired
    private CouponSendObserver couponSendObserver; // 新增的优惠券发放观察者

    /**
     * 动态注册观察者
     */
    @PostMapping("/register")
    public R<?> registerObserver() {
        orderEventPublisher.registerObserver(couponSendObserver);
        return R.success("观察者注册成功");
    }

    /**
     * 动态移除观察者
     */
    @PostMapping("/remove")
    public R<?> removeObserver() {
        orderEventPublisher.removeObserver(couponSendObserver);
        return R.success("观察者移除成功");
    }
}

5.4 避免循环依赖

当观察者与被观察者之间存在依赖注入关系时,可能导致循环依赖。解决方案:

  • 使用构造方法注入时,通过@Lazy注解延迟加载;
  • 优先使用字段注入(@Autowired)或setter注入;
  • 拆分服务职责,避免不必要的依赖。

示例:@Lazy注解解决循环依赖

@Service
public class OrderEventPublisherImpl implements OrderEventPublisher {

    private final List<OrderObserver> observers;

    // 构造方法注入时,对观察者列表使用@Lazy延迟加载
    public OrderEventPublisherImpl(@Lazy List<OrderObserver> observerList) {
        this.observers = observerList;
    }

    // 其他方法...
}

六、总结

本文通过“订单支付成功”业务场景,详细介绍了观察者模式的原理、实现方式,以及结合SpringBoot的优化实践,主要收获如下:

  1. 解决了核心逻辑与关联操作的耦合问题:将短信通知、库存扣减等操作封装为独立观察者,核心支付逻辑无需关注具体实现;
  2. 符合开闭原则:新增关联操作时,只需新增观察者/监听器,无需修改核心代码;
  3. 灵活支持同步/异步执行:可根据业务场景选择执行方式,优化系统性能;
  4. 借助Spring事件机制简化开发:利用Spring原生能力减少重复代码,集成度更高;
  5. 提供了常见问题的解决方案:如执行顺序控制、异常处理、动态注册等,满足实际开发需求。

观察者模式不仅适用于订单支付场景,还可广泛应用于:

  1. 系统通知:如用户注册成功后触发邮件通知、短信验证、权限初始化等;
  2. 状态变更:如商品状态变更后触发缓存更新、索引重建、消息推送等;
  3. 业务监控:如接口调用异常后触发告警通知、日志记录、熔断降级等。

在实际开发中,推荐优先使用Spring事件机制实现观察者模式,其简洁的API和强大的生态支持能大幅提升开发效率。如果需要更灵活的控制(如动态注册、复杂顺序控制),可结合自定义事件发布器实现。

posted @ 2025-12-17 22:25  夏尔_717  阅读(3)  评论(0)    收藏  举报