1707 字
约 5 分钟
1
Spring Boot 中什么时候使用事件发布订阅?以文章阅读量 +1 为例

Spring Boot 中什么时候使用事件发布订阅?以文章阅读量 +1 为例

在开发 Spring Boot 项目时,我们经常会遇到这样一种场景:

主业务已经完成了,但是还需要顺便做一些"额外"的事情。

例如:

  • 用户阅读文章,阅读量 +1;
  • 用户注册成功,发送欢迎邮件;
  • 用户下单成功,发送短信通知;
  • 用户登录成功,记录登录日志;
  • 用户发表评论,发送消息提醒。

很多人的第一反应就是把这些逻辑全部写到一个方法里面。

例如文章详情接口:

public ArticleVO detail(Long articleId) {

    // 查询文章
    ArticleVO article = articleMapper.selectById(articleId);

    // 阅读量 +1
    articleMapper.increaseReadCount(articleId);

    return article;
}

功能能够实现,但随着业务越来越复杂,问题也开始出现。


一、为什么不要把所有逻辑写在一起?

假设以后需求不断增加:

用户阅读文章后需要:

  • 阅读量 +1
  • 记录阅读历史
  • 更新文章热度
  • 记录用户行为
  • 推送推荐算法
  • 给作者增加积分

那么代码很快会变成:

public ArticleVO detail(Long articleId) {

    ArticleVO article = queryArticle(articleId);

    increaseReadCount(articleId);

    saveReadHistory(articleId);

    updateHotScore(articleId);

    saveBehavior(articleId);

    addAuthorPoint(articleId);

    return article;
}

虽然代码没有问题,但是职责已经越来越混乱。

文章详情接口既负责查询文章,又负责处理各种后续业务。

任何一个需求发生变化,都可能修改这里。

这明显违背了单一职责原则


二、什么是事件发布订阅?

Spring 提供了一套非常轻量的事件机制。

它的思想其实非常简单:

我只负责告诉别人:"发生了一件事情。"

至于谁对这件事情感兴趣,由监听者自己决定。

例如:

文章被阅读
        │
        ▼
发布 ArticleReadEvent

所有监听这个事件的模块都会收到通知。

例如:

                 ArticleReadEvent
                        │
      ┌─────────┬──────────┬──────────┐
      ▼         ▼          ▼          ▼
 阅读量+1   阅读历史    热度计算    行为统计

这样,文章模块根本不需要知道后面还有哪些业务。

它只负责:

文章被阅读了。


三、Spring 中如何发布事件?

Spring 提供了:

ApplicationEventPublisher

发布事件:

publisher.publishEvent(new ArticleReadEvent(articleId));

然后对应业务监听事件:

@EventListener
public void handleReadEvent(ArticleReadEvent event){

    increaseReadCount(event.getArticleId());

}

整个过程就是:

查询文章
      │
      ▼
发布事件
      │
      ▼
监听器收到事件
      │
      ▼
阅读量 +1

四、事件机制已经异步了吗?

很多人第一次接触 Spring Event 都会误以为:

发布事件以后就是异步执行。

其实不是。

默认情况下:

publisher.publishEvent(event);

仍然是在当前线程执行。

流程其实是:

Tomcat线程

查询文章
      │
      ▼
发布事件
      │
      ▼
执行Listener
      │
      ▼
阅读量+1
      │
      ▼
返回页面

虽然代码解耦了,但是性能没有任何提升。


五、为什么还要配合线程池?

真正希望的是:

文章能够先返回。

至于阅读量什么时候增加,其实用户根本感觉不到。

所以监听器可以改成:

@Async("articleThreadPool")
@EventListener
public void handleReadEvent(ArticleReadEvent event){

    increaseReadCount(event.getArticleId());

}

这样流程就变成:

Tomcat线程

查询文章
      │
      ▼
发布事件
      │
      ├──────────────► 线程池
      │                    │
      ▼                    ▼
返回文章              阅读量+1

主线程不用等待阅读量更新完成。

页面响应速度也会更快。


六、为什么不用 new Thread()?

有同学可能会问:

为什么不用:

new Thread(() -> {

    increaseReadCount(id);

}).start();

因为线程创建和销毁都有成本。

如果文章访问量很高:

10000 个请求

↓

10000 个线程

这显然是不现实的。

线程池的作用就是:

提前准备好几个线程

任务来了直接复用

不仅性能更好,也方便统一管理。


七、哪些业务适合使用事件发布订阅?

有一个很简单的判断标准:

这件事情不是主业务,但是必须完成。

例如:

✅ 用户注册

  • 发送欢迎邮件
  • 发放新人优惠券
  • 记录注册日志

✅ 用户登录

  • 更新登录时间
  • 保存登录日志
  • 更新在线状态

✅ 用户阅读文章

  • 阅读量 +1
  • 更新热度
  • 保存阅读历史

✅ 用户发表评论

  • 通知作者
  • 更新评论数量
  • 敏感词检测

这些业务都可以拆成事件。


八、哪些场景不建议使用?

如果主业务必须等待结果,就不要使用事件。

例如:

用户下单:

创建订单

↓

扣减库存

↓

支付金额

↓

返回成功

库存扣减失败,订单就不能创建成功。

这种属于核心业务流程。

应该同步执行。

否则容易造成数据不一致。


九、什么时候使用线程池?

事件并不一定非要异步。

可以简单记住下面这张表:

场景 是否建议异步
记录日志
阅读量统计
用户行为统计
发邮件
发短信
更新推荐算法
创建订单
扣库存
支付

如果用户需要等待执行结果,就不要异步。

如果只是一些"后置处理",就非常适合交给线程池。


十、总结

Spring 的事件发布订阅,本质上是一种解耦业务的设计思想。

它让发布者不需要关心后续有哪些处理逻辑,只需要告诉系统:

某件事情发生了。

而自定义线程池,则进一步让这些后续业务脱离主线程执行,提高接口响应速度。

实际开发中,这种组合经常用于:

  • 阅读量统计
  • 登录日志
  • 操作日志
  • 消息通知
  • 邮件发送
  • 用户行为统计

如果以后项目中遇到"主业务完成后,还需要做很多附加操作"的场景,不妨考虑使用 Spring Event + 自定义线程池 来完成,它既能降低模块耦合,又能提升系统的可维护性和响应性能。

Spring Boot 中什么时候使用事件发布订阅?以文章阅读量 +1 为例
http://www.clxhxhhr.top/posts/386/
作者
clxstart
发布于
2026-09-01
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。