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框架自带了一套事件驱动机制(ApplicationEvent、ApplicationListener),基于观察者模式设计,我们可以直接借助Spring原生能力简化实现,无需手动维护观察者列表和发布逻辑。
4.1 定义Spring事件(替代自定义事件实体)
继承Spring的ApplicationEvent,定义支付成功事件:
/**
* 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的优化实践,主要收获如下:
- 解决了核心逻辑与关联操作的耦合问题:将短信通知、库存扣减等操作封装为独立观察者,核心支付逻辑无需关注具体实现;
- 符合开闭原则:新增关联操作时,只需新增观察者/监听器,无需修改核心代码;
- 灵活支持同步/异步执行:可根据业务场景选择执行方式,优化系统性能;
- 借助
Spring事件机制简化开发:利用Spring原生能力减少重复代码,集成度更高; - 提供了常见问题的解决方案:如执行顺序控制、异常处理、动态注册等,满足实际开发需求。
观察者模式不仅适用于订单支付场景,还可广泛应用于:
- 系统通知:如用户注册成功后触发邮件通知、短信验证、权限初始化等;
- 状态变更:如商品状态变更后触发缓存更新、索引重建、消息推送等;
- 业务监控:如接口调用异常后触发告警通知、日志记录、熔断降级等。
在实际开发中,推荐优先使用Spring事件机制实现观察者模式,其简洁的API和强大的生态支持能大幅提升开发效率。如果需要更灵活的控制(如动态注册、复杂顺序控制),可结合自定义事件发布器实现。

浙公网安备 33010602011771号