Spring事件驱动编程(观察者模式)

事件驱动编程

ApplicationListener 是 Spring Framework 事件驱动编程模型的核心接口之一。它允许你的应用程序组件(监听器)对 Spring 容器中发生的特定事件做出反应,而无需与事件发布者紧密耦合。这是一种实现观察者模式发布-订阅模式的优雅方式,有助于:

  1. 解耦: 事件发布者(通常是框架本身或你的业务组件)不需要知道有哪些监听器存在。监听器只关心自己感兴趣的事件。

  2. 可扩展性: 可以轻松地添加新的监听器来处理事件,而无需修改发布事件的代码。

  3. 内聚性: 将与特定事件相关的处理逻辑集中在一个地方(监听器中)。

  4. 框架集成: Spring 容器自身会发布多种内置事件(如 ContextRefreshedEventContextStartedEvent 等),你的应用可以方便地响应这些生命周期事件。


一、 ApplicationListener 接口

ApplicationListener 是一个泛型接口,定义在 org.springframework.context 包中:

package org.springframework.context;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}
  • <E extends ApplicationEvent> 这是一个泛型参数,指定了这个监听器感兴趣的事件类型E 必须是 ApplicationEvent 或其子类。

  • onApplicationEvent(E event) 这是接口中唯一的抽象方法。当 Spring 容器发布一个类型为 E(或其子类型)的事件时,容器会自动调用所有注册的、监听该类型事件的 ApplicationListener 的 onApplicationEvent 方法,并将事件对象 event 传递给它。

二、ApplicationEvent

    所有 Spring 应用事件的基类。自定义事件通常直接或间接继承此类

三、ApplicationEventPublisher

  1.  Spring 容器(ApplicationContext 实现了此接口)提供的一个核心接口,用于发布事件。你可以在你的 Bean 中注入 ApplicationEventPublisher 来发布自定义事件或内置事件(虽然通常不需要手动发布内置事件)。

    核心方法:publishEvent(ApplicationEvent event)

三、 使用方法

使用 ApplicationListener 主要涉及两个方面:定义监听器 和 发布事件

1. 定义监听器 (处理事件)

有两种主要方式定义监听器:

  • 方式一:实现 ApplicationListener 接口 (经典方式)

    • 创建一个BaseListener抽象类,可以用来自定义一些属性或 

      import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.ApplicationListener;
      import org.springframework.stereotype.Component;
      
      /**
       * BaseListener 类描述
       *
       * @author jerry
       * @date 2025/6/24 14:06
       */
      @Slf4j
      @Component
      public  abstract  class BaseListener <T extends BaseEvent<?>> implements ApplicationListener<T> {
          @Override
          public void onApplicationEvent(T event) {
              log.info("触发事件,source={},event={}", this.getClass().getSimpleName(), event);
              onApplicationEvent0(event);
          }
      
          /**
           * 在应用程序event0上
           *
           * @param event 事件
           */
          public abstract void onApplicationEvent0(T event) ;
      }
    •  继承BaseListener

      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Component;
      
      @Component
      @Slf4j
      public class NoticeListener extends BaseListener<NoticeEnvent> {
      
          @Override
          public void onApplicationEvent0(NoticeEnvent event)  {
              // 当触发通知逻辑时,执行此逻辑可以进行消息推送,流程记录等操作
              log.info("开始推送消息:{}",event);
              //获取事件内容
              NoticeData source = event.getSource();
              System.out.println(source);
          }
      }
  • 方式二:使用 @EventListener 注解 (推荐方式 - 更简洁)
    Spring 4.2+ 引入了更简洁的 @EventListener 注解。你可以在任何 Spring 管理的 Bean 的方法上使用此注解,将其标记为事件监听器。

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Component;
    
    @Component
    @Slf4j
    public class NoticeListener {
    
        @EventListener
        public void onApplicationEvent0(NoticeEnvent event)  {
            // 当触发通知逻辑时,执行此逻辑可以进行消息推送,流程记录等操作
            log.info("开始推送消息:{}",event);
            //获取事件内容
            NoticeData source = event.getSource();
            System.out.println(source);
        }
    }
    • 优点:

      • 简洁: 不需要实现特定接口。

      • 灵活: 方法名可以自由定义,方法参数直接声明为要监听的事件类型(Spring 根据参数类型推断)。

      • 复用: 可以在同一个类中定义多个监听不同事件的方法。

      • 支持条件: 结合 @Conditional 或 condition 属性(如 @EventListener(condition = "#event.success")),可以根据 SpEL 表达式条件化地监听事件。

    • 关键点:

      • 方法所在的类必须是 Spring Bean。

      • 方法参数类型定义了要监听的事件类型。

      • 方法可以有返回值(支持异步监听处理结果,见下文异步部分),也可以没有(void)。

2. 发布事件

  • 发布内置事件: Spring 容器会在相应生命周期节点(初始化、启动、停止、关闭等)自动发布对应的内置事件(ContextRefreshedEventContextStartedEvent 等)。你通常不需要手动发布它们。

  • 定义自定义事件类: 创建BaseEvent抽象类类继承 ApplicationEvent

    import cn.hutool.core.util.IdUtil;
    import lombok.Getter;
    import org.springframework.context.ApplicationEvent;
    
    import java.util.function.Function;
    
    /**
     * BaseEvent 类描述
     *
     * @author jerry
     * @date 2025/6/24 13:59
     */
    
    public abstract class BaseEvent<T> extends ApplicationEvent {
    
        @Getter
        private final String eventId;
    
        @Override
        public T getSource () {
            return (T) super.getSource();
        }
    
        /**
         * Create a new {@code ApplicationEvent}.
         *
         * @param t the object on which the event initially occurred or with
         *               which the event is associated (never {@code null})
         */
        public BaseEvent(T t) {
            super(t);
            this.eventId = IdUtil.objectId();
        }
    
        final void mapSource(Function<T, T> mapper) {
    
            if (source == null) {
                return;
            }
    
            this.source = mapper.apply(getSource());
        }
    }
  • 创建NoticeEvent继承BaseEvent

    /**
     * NoticeData 类描述
     *
     * @author jerry
     * @date 2025/6/24 14:16
     */
    public class NoticeEvent extends BaseEvent<NoticeData>{
        /**
         * Create a new {@code ApplicationEvent}.
         *
         * @param data the object on which the event initially occurred or with
         *          which the event is associated (never {@code null})
         */
        public NoticeEvent(NoticeData data) {
            super(data);
        }
    }
  • 发布自定义事件:

    封装ApplicationEventPublisher
    /**
     * @author jerry
     */
    @Slf4j
    public class EventBus implements ApplicationEventPublisher {
        /**
         * Singleton INSTANCE to use.
         */
        public static final EventBus INSTANCE = new EventBus();
    
        /**
         * 推送事件
         *
         * @param event 事件
         */
        @Override
        public void publishEvent(@NonNull Object event) {
            log.info("event={}", event);
            SpringUtil.getApplicationContext().publishEvent(event);
        }
    }

定义消息实体类

/**
 * NoticeData 类描述
 *
 * @author jerry
 * @date 2025/6/24 14:25
 */
@Data
@AllArgsConstructor(staticName = "of")
public class NoticeData {

    /**
     * 接收消息人员集合
     */
    private List<String> loginIds;

    /**
     * 消息体
     */
    private String message;
}

最后在相关的代码里进行使用就可以了

import lombok.extern.slf4j.Slf4j;
import org.example.mqdemo.templateNotice.EventBus;
import org.example.mqdemo.templateNotice.NoticeData;
import org.example.mqdemo.templateNotice.NoticeEvent;
import org.springframework.stereotype.Service;

import java.util.Collections;

@Service
@Slf4j
public class MqServiceImpl  {
    public boolean sendMessage() {
        try {
            EventBus.INSTANCE.publishEvent(new NoticeEvent(NoticeData.of(Collections.singletonList("jerry"),"测试消息")));
            log.info("发送消息成功");
        }catch (Exception e){
            log.error("发送消息异常",e);
        }
        return false;
    }
}

测试类

/**
 * @author rui.li
 */
@RequestMapping("/test")
@RestController
public class SendController {

    @Resource
    private MqServiceImpl mqService;

    @GetMapping(value = "send")
    public void boardList() {
        mqService.sendMessage();
        System.out.println("sucess");
    }
}

执行结果


四、 高级用法与注意事项

  1. 监听器执行顺序:

    • 默认情况下,监听器的执行顺序是不确定的。

    • 如果需要控制顺序,可以让监听器类实现 SmartApplicationListener 接口(实现 getOrder() 方法返回一个 Ordered 值),或者在 @EventListener 注解的方法上添加 @Order 注解。

    • 顺序值越小(Ordered.HIGHEST_PRECEDENCE),优先级越高,越先执行。

  2. 异步事件监听:

    • 默认情况下,所有监听器都是在事件发布者的同一个线程中同步执行的。如果一个监听器执行缓慢,会阻塞后续监听器和发布者的线程。

    • 要异步执行监听器逻辑:

      使用 @Async 注解: 在 @EventListener 注解的方法上同时标注 @Async。这需要你在 Spring 配置中启用异步支持(通常在主配置类或 @SpringBootApplication 类上添
    • 加 @EnableAsync)。
    • import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.event.EventListener;
      import org.springframework.scheduling.annotation.Async;
      import org.springframework.stereotype.Component;
      
      @Component
      @Slf4j
      public class NoticeListener {
      
          @EventListener()
          @Async
          public void onApplicationEvent0(NoticeEvent event)  {
              // 当触发通知逻辑时,执行此逻辑可以进行消息推送,流程记录等操作
              log.info("开始推送消息:{}",event);
              //获取事件内容
              NoticeData source = event.getSource();
              System.out.println(source);
          }
      }

注意:如果是通过实现

ApplicationListener接口的方式实现的事件监听,异步注解可能会失效,需要注意Bean都交给Spring统一管理。如果是对ApplicationListener接口进行了封装则需要对封装的接口里面的 onApplicationEvent也加上@Async注解
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * BaseListener 类描述
 *
 * @author jerry
 * @date 2025/6/24 14:06
 */
@Slf4j
@Component
public  abstract  class BaseListener <T extends BaseEvent<?>> implements ApplicationListener<T> {
    @Override
    @Async
    public void onApplicationEvent(T event) {
        log.info("触发事件,source={},event={}", this.getClass().getSimpleName(), event);
        onApplicationEvent0(event);
    }

    /**
     * 在应用程序event0上
     *
     * @param event 事件
     */
    public abstract void onApplicationEvent0(T event) ;
}

总结

ApplicationListener 是 Spring 事件机制的关键接口,用于响应容器生命周期事件和自定义应用事件。使用它(或更现代的 @EventListener 注解)可以实现组件间的松耦合通信。

核心步骤:

  1. 定义事件: 继承 ApplicationEvent(对于自定义事件)。

  2. 定义监听器:

    • 实现 ApplicationListener<E> 接口并实现 onApplicationEvent

    • 或在 Spring Bean 的方法上使用 @EventListener 注解(推荐)。

    • 确保监听器类是 Spring Bean (@Component@Service 等)。

  3. 发布事件:

    • 在需要发布事件的地方注入 ApplicationEventPublisher

    • 调用 publishEvent(event) 方法发布事件。

选择建议:

  • 对于新项目或需要简洁性、灵活性和条件监听时,优先使用 @EventListener

  • 需要强制实现接口或处理非常早期的事件(可能在 @EventListener 处理器注册之前发生)时,可以考虑实现 ApplicationListener

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值