惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
D
Docker
The GitHub Blog
The GitHub Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
About on SuperTechFans
WordPress大学
WordPress大学
Recent Announcements
Recent Announcements
Engineering at Meta
Engineering at Meta
H
Help Net Security
Vercel News
Vercel News
S
SegmentFault 最新的问题
罗磊的独立博客
F
Full Disclosure
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
Last Week in AI
Last Week in AI
V
V2EX
腾讯CDC
IT之家
IT之家
爱范儿
爱范儿
博客园 - Franky
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
The Register - Security
The Register - Security
Help Net Security
Help Net Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Proofpoint News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
B
Blog RSS Feed
雷峰网
雷峰网
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
Recorded Future
Recorded Future
A
Arctic Wolf
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
S
Schneier on Security
GbyAI
GbyAI
Schneier on Security
Schneier on Security
O
OpenAI News
F
Fortinet All Blogs
Y
Y Combinator Blog
Forbes - Security
Forbes - Security
NISL@THU
NISL@THU
M
MIT News - Artificial intelligence
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
MongoDB | Blog
MongoDB | Blog

博客园 - 有点懒惰的大青年

用一个实际业务场景演示ON和WHERE的正确使用方式 idea中查看源码 异或运算 使用Hutool对时间的花式操作 合并K个升序列表 比较器 枚举类的设计模式 spring boot中使用RedissonClient实现分布式锁 idea同时启动application,启用不同端口 @PostConstruct用法 java自带命令jps介绍 关于maven中标签的说明 mysql的跨库查询 linux命令ll显示结果的含义 Files类的使用 java环境变量设置 java值传递和引用传递 kafka的集群与可靠性 kafka的消费全流程 关于kafka 达梦数据库执行计划介绍 关于MQ 用位运算实现加减乘除(3)
ApplicationContext 事件发布与监听机制详解
有点懒惰的大青年 · 2026-01-15 · via 博客园 - 有点懒惰的大青年

参考文章:

ApplicationContext 事件发布与监听机制详解

观察者 PublishEvent

Spring ApplicationEventPublisher 事件发布

需求:          

利用ApplicationContext的事件机制来完成下面的功能:
1. 订单创建
2. 接受订单,分配商家
3. 商家做好了,呼叫骑手
4. 骑手收到,开始送货
5. 收到外卖

代码实现如下:        

1.代码结构:    

image

2.事件event类:      

订单上下文事件
package com.publish.event;

import org.springframework.context.ApplicationEvent;

/**
 * @description com.publish.event
 * @author: chengyu
 * @date: 2026-01-15 20:54
 *
 * 当父类没有默认构造方法(无参构造器)时,所有子类(无论是抽象类还是具体类)都必须在自己的构造方法中显式调用父类的某个构造方法。
 *
 * 原因分析
 * 在Java中,子类构造方法的第一行必须调用父类的构造方法(super(...))。
 * 如果父类没有默认构造方法(无参构造器),编译器不会自动生成super()调用,此时必须显式调用。
 *
 * 当父类有构造方法时,子类的构造方法中不需要显式调用super(),因为编译器会自动添加
 * 构造方法的调用规则与类是否是抽象类无关。只要父类没有默认构造器,所有子类都必须在自己的构造方法中显式调用父类的某个构造方法
 */
public abstract class OrderContextEvent<T> extends ApplicationEvent {

    public OrderContextEvent(T source) {
        super(source);
    }

    @Override
    public T getSource() {
        return (T) super.getSource();
    }
}
订单创建事件
package com.publish.event;

import com.publish.model.Order;
import lombok.Getter;
import lombok.Setter;

/**
 * @description 订单创建事件
 * @author: chengyu
 * @date: 2026-01-15 20:15
 */
@Getter
@Setter
public class OrderCreatedEvent extends OrderContextEvent<Order>{

    public OrderCreatedEvent(Order order) {
        super(order);
    }
}
呼叫骑手事件
package com.publish.event;

import com.publish.model.Order;
import com.publish.model.Rider;
import lombok.Getter;
import lombok.Setter;

/**
 * @description 呼叫骑手事件
 * @author: chengyu
 * @date: 2026-01-15 20:21
 */
@Getter
@Setter
public class CallRiderEvent extends OrderContextEvent<Order> {

    public CallRiderEvent(Order order) {
        super(order);
    }
}
订单送达事件
package com.publish.event;

import com.publish.model.Rider;
import lombok.Getter;
import lombok.Setter;

/**
 * @description 订单送达事件
 * @author: chengyu
 * @date: 2026-01-15 20:24
 */
@Getter
@Setter
public class OrderArrivedEvent extends OrderContextEvent<Rider>{

    public OrderArrivedEvent(Rider rider) {
        super(rider);
    }
}

3.实体类:      

订单类
package com.publish.model;

import lombok.Data;

/**
 * @description com.publish.model
 * @author: chengyu
 * @date: 2026-01-15 21:05
 */
@Data
public class Order {
    private String orderName;

    private String customerName;

    private String phone;

    private String address;

    private String message;
}
骑手类
package com.publish.model;

import lombok.Data;

/**
 * @description com.publish.model
 * @author: chengyu
 * @date: 2026-01-15 21:21
 */
@Data
public class Rider {
    private Order order;

    private String name;

    private String age;
}

4.客户下单,订单创建发布事件:

package com.publish.controller;

import com.publish.event.OrderCreatedEvent;
import com.publish.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @description com.publish.controller
 * @author: chengyu
 * @date: 2026-01-15 20:30
 */
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {

    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/createOrder")
    public String placeAnOrder() {
        Order order = new Order();
        order.setOrderName("煲仔饭");
        order.setCustomerName("张三");
        order.setPhone("13567678877");
        order.setAddress("上海市浦东新区华胜天成小区1栋101");
        order.setMessage("外卖放在家门口");
        OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent(order);
        applicationContext.publishEvent(orderCreatedEvent);
        log.info("订单创建完成事件发布完成");
        return "下单成功";
    }

}

5.监听器:    

商家接受外卖事件监听器,这里使用ApplicationEventPublisher来发布事件:

package com.publish.listener;

import com.publish.event.CallRiderEvent;
import com.publish.event.OrderCreatedEvent;
import com.publish.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @description 商家接受外卖事件监听器
 * @author: chengyu
 * @date: 2026-01-15 20:41
 */
@Component
@Slf4j
public class MerchantReceiveOrderListener implements ApplicationListener<OrderCreatedEvent> {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * listener监听事件:
     * 实现方式1:实现 ApplicationListener 接口,重写onApplicationEvent方法
     * 实现方式2:使用 @EventListener 注解
     */
    @Override
    @Async
    public void onApplicationEvent(OrderCreatedEvent event) {
        Order order = event.getSource();
        log.info("商家收到订单:{}", order);
        log.info("开始呼叫骑手...");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * 使用applicationContext.publishEvent(new CallRiderEvent(order));也可以。
         * 因为applicationContext实现了ApplicationEventPublisher接口
         */
        applicationEventPublisher.publishEvent(new CallRiderEvent(order));
        log.info("呼叫骑手事件发布完成" + System.lineSeparator());
    }
}
骑手接受呼叫骑手事件:
package com.publish.listener;

import com.publish.event.CallRiderEvent;
import com.publish.event.OrderArrivedEvent;
import com.publish.model.Order;
import com.publish.model.Rider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @description 骑手接受呼叫骑手事件
 * @author: chengyu
 * @date: 2026-01-15 21:31
 */
@Component
@Slf4j
public class RiderReceiveOrderListener {

    @Autowired
    private ApplicationContext applicationContext;

    @EventListener
    @Async
    public void handleCallRiderEvent(CallRiderEvent event) throws Exception {
        Order order = event.getSource();
        Rider rider = new Rider();
        rider.setOrder(order);
        rider.setAge("30");
        rider.setName("饿了么潘伟");
        log.info("骑手接到订单:{}", order);
        log.info("骑手开始送货...");
        TimeUnit.SECONDS.sleep(10);
        applicationContext.publishEvent(new OrderArrivedEvent(rider));
        log.info("订单送达事件发布完成" + System.lineSeparator());
    }
}
客户接受外卖送达事件:
package com.publish.listener;

import com.publish.event.OrderArrivedEvent;
import com.publish.model.Rider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @description 客户接受外卖送达事件
 * @author: chengyu
 * @date: 2026-01-15 21:41
 */
@Component
@Slf4j
public class CustomerReceiveOrderListener {

    @EventListener
    @Async
    public void handleOrderArrivedEvent(OrderArrivedEvent event) throws InterruptedException {
        Rider rider = event.getSource();
        log.info("客户收到外卖,订单信息:{}", rider.getOrder());
        log.info("骑手姓名:{},骑手年龄:{},请给个好评", rider.getName(), rider.getAge());
        log.info("开始好好吃饭....");
        TimeUnit.SECONDS.sleep(10);
        log.info("吃完啦!" + System.lineSeparator());
    }
}

测试:浏览器输入:http://localhost:8080/testHello/order/createOrder

马上显示:下单成功        可见是异步。

注意这里@Async注解的使用,上面例子中每个事件监听器上都有@Async注解,都是异步执行,发布完事件后立马方法结束,无需等待下个监听者执行完。

日志打印如下:

2026-03-15 11:55:29.988  INFO 9412 --- [nio-8080-exec-1] com.publish.controller.OrderController   : 订单创建完成事件发布完成
2026-03-15 11:55:29.988  INFO 9412 --- [         task-1] c.p.l.MerchantReceiveOrderListener       : 商家收到订单:Order(orderName=煲仔饭, customerName=张三, phone=13567678877, address=上海市浦东新区华胜天成小区1栋101, message=外卖放在家门口)
2026-03-15 11:55:29.989  INFO 9412 --- [         task-1] c.p.l.MerchantReceiveOrderListener       : 开始呼叫骑手...
2026-03-15 11:55:40.001  INFO 9412 --- [         task-1] c.p.l.MerchantReceiveOrderListener       : 呼叫骑手事件发布完成

2026-03-15 11:55:40.004  INFO 9412 --- [         task-2] c.p.listener.RiderReceiveOrderListener   : 骑手接到订单:Order(orderName=煲仔饭, customerName=张三, phone=13567678877, address=上海市浦东新区华胜天成小区1栋101, message=外卖放在家门口)
2026-03-15 11:55:40.006  INFO 9412 --- [         task-2] c.p.listener.RiderReceiveOrderListener   : 骑手开始送货...
2026-03-15 11:55:50.010  INFO 9412 --- [         task-2] c.p.listener.RiderReceiveOrderListener   : 订单送达事件发布完成

2026-03-15 11:55:50.012  INFO 9412 --- [         task-3] c.p.l.CustomerReceiveOrderListener       : 客户收到外卖,订单信息:Order(orderName=煲仔饭, customerName=张三, phone=13567678877, address=上海市浦东新区华胜天成小区1栋101, message=外卖放在家门口)
2026-03-15 11:55:50.013  INFO 9412 --- [         task-3] c.p.l.CustomerReceiveOrderListener       : 骑手姓名:饿了么潘伟,骑手年龄:30,请给个好评
2026-03-15 11:55:50.013  INFO 9412 --- [         task-3] c.p.l.CustomerReceiveOrderListener       : 开始好好吃饭....
2026-03-15 11:56:00.025  INFO 9412 --- [         task-3] c.p.l.CustomerReceiveOrderListener       : 吃完啦!

但是一旦将RiderReceiveOrderListener、CustomerReceiveOrderListener上面的@Async注解注释掉,打印就不一样了:

2026-03-15 11:57:38.626  INFO 9064 --- [nio-8080-exec-1] com.publish.controller.OrderController   : 订单创建完成事件发布完成
2026-03-15 11:57:38.626  INFO 9064 --- [         task-1] c.p.l.MerchantReceiveOrderListener       : 商家收到订单:Order(orderName=煲仔饭, customerName=张三, phone=13567678877, address=上海市浦东新区华胜天成小区1栋101, message=外卖放在家门口)
2026-03-15 11:57:38.627  INFO 9064 --- [         task-1] c.p.l.MerchantReceiveOrderListener       : 开始呼叫骑手...
2026-03-15 11:57:48.639  INFO 9064 --- [         task-1] c.p.listener.RiderReceiveOrderListener   : 骑手接到订单:Order(orderName=煲仔饭, customerName=张三, phone=13567678877, address=上海市浦东新区华胜天成小区1栋101, message=外卖放在家门口)
2026-03-15 11:57:48.639  INFO 9064 --- [         task-1] c.p.listener.RiderReceiveOrderListener   : 骑手开始送货...
2026-03-15 11:57:58.650  INFO 9064 --- [         task-1] c.p.l.CustomerReceiveOrderListener       : 客户收到外卖,订单信息:Order(orderName=煲仔饭, customerName=张三, phone=13567678877, address=上海市浦东新区华胜天成小区1栋101, message=外卖放在家门口)
2026-03-15 11:57:58.651  INFO 9064 --- [         task-1] c.p.l.CustomerReceiveOrderListener       : 骑手姓名:饿了么潘伟,骑手年龄:30,请给个好评
2026-03-15 11:57:58.651  INFO 9064 --- [         task-1] c.p.l.CustomerReceiveOrderListener       : 开始好好吃饭....
2026-03-15 11:58:08.654  INFO 9064 --- [         task-1] c.p.l.CustomerReceiveOrderListener       : 吃完啦!

2026-03-15 11:58:08.654  INFO 9064 --- [         task-1] c.p.listener.RiderReceiveOrderListener   : 订单送达事件发布完成

2026-03-15 11:58:08.654  INFO 9064 --- [         task-1] c.p.l.MerchantReceiveOrderListener       : 呼叫骑手事件发布完成

事件发布者必须等时间监听者执行完,方法才结束。所以上面每个listener上面都加了@Async

PS:        

还可以增加订单状态:OrderStatus,每一次流转定义订单的状态:创建、商家接单、骑手接单、送货、已送达

注意:
(1) ApplicationContext.publishEvent 默认是同步操作, 并非发布后不管的异步操作,发布事件后需要等 @EventListener 执行完。
(2) 如果需要开启异步操作需要在@EventListener上增加@Async 注解。时间监听类通过 @Async 实现非阻塞事件处理。这样,当事件发布后,监听器的处理将会在一个新的线程中进行,不会阻塞事件发布者的执行。
(3) 启动类上增加@EnableAsync注解 

如果多个Listener监听同一个事件,比如OrderCreatedEvent,需要控制多个监听器的执行顺序,可以使用 @Order 注解:

@Component
@Order(1)
class FirstOrderListener {
    
    @EventListener
    public void handleFirst(OrderCreatedEvent event) {
        System.out.println("First listener for order: " + event.getOrderId());
    }
}

@Component
@Order(2)
class SecondOrderListener {
    
    @EventListener
    public void handleSecond(OrderCreatedEvent event) {
        System.out.println("Second listener for order: " + event.getOrderId());
    }
}

--