程序可以同时监听tcp和udp吗?_Spring事件监听机制

本文从JDK的事件机制讲起,再到Spring、SpringBoot的事件机制,并结合源码进行分析,同时给出使用方式。 事件监听机制和设计模式中的观察者模式如出一辙,在Spring框架中被广泛使用。

基本概念

应用程序事件允许我们发送和接收特定事件,我们可以根据需要处理这些事件。事件用于在松散耦合的组件之间交换信息。由于发布者和订阅者之间没有直接耦合,因此可以在不影响发布者的情况下修改订阅者,反之亦然。在一个事件体系中,有以下几个重要的概念:

  1. 事件源:事件对象的产生者,任何一个事件都有一个来源
  2. 事件监听器注册表:当事件框架或组件收到一个事件后,需要通知所有相关的事件监听器来进行处理,这个时候就需要有个存储监听器的地方,也就是事件监听器注册表
  3. 事件广播器:事件广播器在整个事件机制中扮演一个中介的角色,当事件发布者发布一个事件后,就需要通过广播器来通知所有相关的监听器对该事件进行处理

JDK事件机制

在Java中,通过java.util. EventObject来描述事件,通过java.util. EventListener来描述事件监听器。

// 用户实体
public class User {
    private Long id;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public User(Long id) {this.id = id;}
}
​
// 用户事件
public class UserEvent extends EventObject {
    public UserEvent(Object source) {super(source);}
}
​
// 邮件事件
public class EmailEvent extends UserEvent{
    public EmailEvent(Object source) {super(source);}
}
​
// 用户监听器
public interface UserListener extends EventListener {
    // 处理用户注册事件
    void onRegister(UserEvent event);
}
​
// 邮件监听器
public class EmailListener implements UserListener {
    @Override
    public void onRegister(UserEvent event) {
        if (event instanceof EmailEvent){
            User user = (User) event.getSource();
            System.out.println("给User Id为: " + user.getId() + "的用户发送邮件");
        }
    }
}
​
// 用户服务
public class UserService {
    // 存放所有监听器
    private final List<UserListener> listeners = new ArrayList<>();
    // 添加监听器
    public void addListener(UserListener userListener){
        this.listeners.add(userListener);
    }
    // 注册用户并发布用户注册事件
    public void register(User user){
        System.out.println("注册新用户,Id为【" + user.getId() + "】");
        publishEvent(new EmailEvent(user));
    }
    // 广播用户注册事件
    private void publishEvent(UserEvent event){
        for (UserListener listener : listeners) {
            listener.onRegister(event);
        }
    }
    public static void main(String[] args) {
        UserService userService = new UserService();
        User user = new User(1000L);
        userService.addListener(new EmailListener());
        userService.register(user);
    }
}
​
// ==输出结果==
// 注册新用户,Id为【1000】
// 给User Id为: 1000的用户发送邮件

Spring事件

在Spring中,事件机制采用观察者模式进行具体实现,设计类图如下:

affe890ed8aa63a9d2536ae0f9312b9b.png
  • ApplicationEvent是Spring事件的顶层抽象类,代表事件本身,继承自JDK的EventObject。Spring提供了一些内置事件,如ContextClosedEvent、ContextStartedEvent、ContextRefreshedEvent、ContextStopedEvent等
  • ApplicationListener是Spring事件监听器顶层接口,所有的监听器都实现该接口,继承自JDK的EventListener。Spring提供了一些易扩展的接口,如SmartApplicationListener、GenericApplicationListener
  • ApplicationEventPublisher是Spring的事件发布接口,事件源通过该接口的publishEvent方法发布事件,所有的应用上下文都具备事件发布能力,因为ApplicationContext继承了该接口
  • ApplicationEventMulticaster是Spring事件机制中的事件广播器,它默认提供一个SimpleApplicationEventMulticaster实现,如果用户没有自定义广播器,则使用默认的(初始化逻辑见AbstractApplicationContext的refresh方法)。它通过父类AbstractApplicationEventMulticaster的getApplicationListeners方法从事件注册表中获取事件监听器,并且通过invokeListener方法执行监听器的具体逻辑。

89be1d3a13c53231ca4731e29527248c.png

默认的广播器是同步调用监听器的执行逻辑,但是可以通过为广播器配置Executor实现监听器的异步执行。

在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法。

当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。

SpringBoot事件

SpringApplicationEvent是SpringBoot事件的顶层抽象类,且SpringBoot内置了7个事件:

c693cc9b724b1e1efe1b8e1377b7292f.png
  • ApplicationStartingEvent:SpringBoot启动开始的时候发布的事件
  • ApplicationEnvironmentPreparedEvent:SpringBoot对应Environment准备完毕时发布的事件,此时上下文还没有创建,该事件中可以获取配置信息。通过监听该事件可以修改默认的配置信息,配置中心就是借助该事件完成将远端配置放入应用中
  • ApplicationContextInitializedEvent:当准备好上下文,并且在加载任何Bean之前发布的事件。该事件可以获取上下文信息
  • ApplicationPreparedEvent:上下文创建完成时发布的事件,此时的Bean还没有完全加载完成,只是加载了部分特定Bean。该事件可以获取上下文信息,但是无法获取自定义Bean,应为还没有被加载
  • ApplicationStartedEvent:上下文刷新(完成所有Bean的加载)之后,但是还没有调用任何ApplicationRunner 和CommandLineRunner运行程序之前发布的事件。该事件同样可以获取上下文信息,并且可以获取自定义的Bean
  • ApplicationReadyEvent:完成ApplicationRunner 和CommandLineRunner运行程序调用之后发布的事件,此时的SpringBoot已全部启动完成。该事件同样可以获取上下文信息
  • ApplicationFailedEvent:SpringBoot启动异常时发布的事件。该事件可以获取上下文信息和异常信息,通过该事件可以友好地完成资源的回收

上述7个事件在容器启动的合适阶段进行发布,发布顺序自上而下,可查看SpringApplication的run(String... args)方法,分别对应SpringApplicationRunListener的7个方法,源码如下:

// 该接口规定了SpringBoot的生命周期,会在各个生命周期广播响应的事件,调用实际的监听器
public interface SpringApplicationRunListener {
    // 发布ApplicationStartingEvent
    default void starting() {
    }
    // 发布ApplicationEnvironmentPreparedEvent
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }
    // 发布ApplicationContextInitializedEvent
    default void contextPrepared(ConfigurableApplicationContext context) {
    }
    // 发布ApplicationPreparedEvent
    default void contextLoaded(ConfigurableApplicationContext context) {
    }
    // 发布ApplicationStartedEvent
    default void started(ConfigurableApplicationContext context) {
    }
    // 发布ApplicationReadyEvent
    default void running(ConfigurableApplicationContext context) {
    }
    // 发布ApplicationFailedEvent
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }
}

上述接口提供了唯一的实现类EventPublishingRunListener,该类聚合了SpringApplication和SimpleApplicationEventMulticaster,前4个事件的发布由广播器完成,后3个委托ApplicationContext完成发布,其实最终都是由广播器负责发布。

监听器使用方式

1、通过SPI机制配置

在resources目录下新建META-INF目录,并创建名为spring.factories的文件,在文件中声明以ApplicationListener接口全路径为键的配置,配置内容为ApplicationListener实现类的全路径,若有多个以逗号间隔,示例如下:

org.springframework.context.ApplicationListener=
com.lyentech.bdc.tuya.listener.HunterApplicationContextInitializedListener,
com.lyentech.bdc.tuya.listener.HunterApplicationEnvironmentPreparedListener,
com.lyentech.bdc.tuya.listener.HunterApplicationFailedListener,
com.lyentech.bdc.tuya.listener.HunterApplicationPreparedListener,
com.lyentech.bdc.tuya.listener.HunterApplicationReadyListener,
com.lyentech.bdc.tuya.listener.HunterApplicationStartedListener,
com.lyentech.bdc.tuya.listener.HunterApplicationStartingListener

Spring在启动的时候会加载spring.factories中配置的监听器实现类,并在合适的阶段通过发布事件触发相应监听器的逻辑。此方式可以实现针对任何事件(SpringBoot内置7个事件都支持)监听器的成功加载。

2、启动类main方法手动添加

在引用启动类的main方法中先创建一个SpringApplication实例,然后调用addListeners方法添加自定义的监听器实现类,最后调用实例的run方法启动容器,示例如下:

public class TuyaServiceApplication{
    public static void main(String[] args) {
        // 习惯写法
        //SpringApplication.run(TuyaServiceApplication.class, args);
        
        // 新写法:实例化对象、添加监听器、启动
        SpringApplication springApplication = new SpringApplication(TuyaServiceApplication.class);
        springApplication.addListeners(new HunterApplicationStartingListener());
        springApplication.addListeners(new HunterApplicationPreparedListener());
        springApplication.run(args);
    }
}

这种方式也可以实现针对任何事件(SpringBoot内置7个事件都支持)监听器的成功加载。

3、使用@Component

在监听器实现类定义的时候适用@Component注解注释当前监听器,确保让Spring可以扫描到并完成加载,示例如下:

// 监听ApplicationReadyEvent事件
@Component
public class HunterApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.err.println("===execute 【ApplicationReadyEvent】 listener...");
    }
}

这种方式只能实现对SpringBoot内置事件的后3个进行监听,因为容器启动在借助@Component加载Bean的时候,前4个事件已经发布了,所以不会生效。

4、使用@Component和@EventListener

这种方式可以减少监听器类的个数,示例如下:

@Component
public class HunterListener {
​
    @EventListener
    public void listener1(ApplicationStartingEvent event){
        System.err.println("使用@EventListener注册监听器,监听ApplicationStartingEvent事件");
    }
​
    @EventListener
    public void listener2(ApplicationStartedEvent event){
        System.err.println("使用@EventListener注册监听器,监听ApplicationStartedEvent事件");
    }
​
    @EventListener
    public void listener3(ApplicationReadyEvent event){
        System.err.println("使用@EventListener注册监听器,监听ApplicationReadyEvent事件");
    }
}

这种方式配置的对前四个事件的监听不会生效,原因同第三种方式。

5、总结

前三种方式的监听器都需要通过实现ApplicationListener接口给出具体定义,如果监听器特别多,就会造成类的数量很大;第四种方式仅需一个类就可以完成多个监听器的定义,但是适用的事件有限。

如果多种方式共存,监听器会被执行多次。

如果需要指定同类事件监听器的执行顺序,可以通过@Order或者实现Ordered接口完成。

监听器默认同步执行,如果需要异步执行,一种方式是为广播器提供Executor,另一种就是在后两种使用方式上结合@Async注解(此时需要通过@EnableAsync开启容器对异步的支持)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值