事件(Application Event)

2020-09-18   103 次阅读


Application Event

本文代码

Spring 中的事件 Application EventBeanBean 之间的消息通信提供了支持。
当一个Bean处理完成事件之后,其他的Bean能够知道并作出相应的处理,这时候就要让另外一个 Bean监听当前 Bean所发送的事件。

Spring中的事件需要遵循下述流程:

  1. 自定义事件,继承 ApplicationEvent
  2. 定义事件监听器,实现 ApplicationEvent
  3. 发布事件。

1:自定义事件

import org.springframework.context.ApplicationEvent;
/**
 * @author xiaoFsu
 * 继承 ApplicationEvent
 */
public class DemoEvent extends ApplicationEvent {

    private String msg;

    public DemoEvent(Object source,String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

继承 ApplicationEvent之后需要在当前类的构造器中调用父类的构造器,并传入与事件相关联的对象,不能为 null

image.png

2:事件监听器

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author xiaoFsu
 * ApplicationListener<DemoEvent>   实现ApplicationListener接口,指定监听的事件类型
 *
 */
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {

    /**
     * author:xiaoFsu
     * time:2020.09.18 9:59:09
     * 使用此方法对消息进行接收处理
     */
    @Override
    public void onApplicationEvent(DemoEvent event) {
        System.out.println("The Msg is :"+event.getMsg());
    }
}

注解 @FunctionalInterface 函数式接口,具体解释可以查看:函数式接口

ApplicationListener 中定义了一个方法,用来处理事件的响应。

image.png

3:事件发布类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class DemoPublisher {

    @Autowired
    private ApplicationContext applicationContext;

    public void publishMsg(String msg){
        applicationContext.publishEvent(new DemoEvent(this,msg));
    }
}

注入 ApplicationContext 用来发布事件,使用 publishEvent 方法来进行发布。

image.png

4:配置包扫描

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.xiaofsu.demo.event")  // 替换成你的包名称
public class DemoConfig {
}

5:执行

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        // 从配置文件中扫描包中的类获取上下文对象
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
	// 获取到Spring注入的Bean
        DemoPublisher bean = context.getBean(DemoPublisher.class);
	// 发布消息
        bean.publishMsg("233333");
        context.close();
    }

这时候我们运行可以看到控制台输出了在 #2事件监听器 中设置的输出。

image.png

6:回顾

实现简单的监听之后我们需要考虑一下这个消息的处理逻辑:

  1. 消息通过 #3 事件发布类 进行发布之后。
  2. 消息会在 #2 事件监听器 中监听到。

7:问题疑惑

7.1:同步异步?

考虑一下,这时候如果大量的消息涌入,这个监听器是同步处理还是异步处理?假设我现在监听的是一个 用户注册的方法,当用户注册完成之后要发送 短信通知用户,那么我就可以如下模拟:

// 启动类中的修改
 public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        // 必须指定一下这个
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
	// 模拟6个用户注册成功,都需要通过监听器来发送注册成功的短信
        DemoPublisher bean = context.getBean(DemoPublisher.class);
        bean.publishMsg("A 注册成功");
        bean.publishMsg("B 注册成功");
        bean.publishMsg("C 注册成功");
        bean.publishMsg("D 注册成功");
        bean.publishMsg("E 注册成功");
        bean.publishMsg("F 注册成功");
        context.close();
    }
// #2 监听器类的修改
    @Override
    public void onApplicationEvent(DemoEvent event) {
        System.out.println("线程名称:"+Thread.currentThread().getName()+ " ->> 时间:"+ new Date()+ " ->>消息:"+event.getMsg());
        // 模拟短信发送处理需要的时长
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

这时候再运行程序,会得到主线程 main 一直在运行,目前模拟 6位用户,第一位用户得到短信的时间是1s之后,貌似可以接受,但是如果1000位用户同时注册呢?那第1000位用户得到消息的时间呢?

image.png

所以我们得知事件监听是同步执行的,那么该怎么换成异步的呢?只需要一个 @EnableSync 开启异步注解并把 @Async 注解注入到想异步执行的方法上就可以了。@Async 如果注解到类上,则代表此类的所有方法都是异步方法,如果注解到方法上,则代表该方法为异步方法。

所以再改造:


@Component
@EnableAsync	// #2 事件监听器上加上此注解代表这个类开启异步处理
public class DemoListener implements ApplicationListener<DemoEvent>

// 并在方法上添加  @Async  注解表示此方法开启异步
@Override
@Async
public void onApplicationEvent(DemoEvent event)

------------------------------------------------

@Component
@EnableAsync	// #3 事件发布类上加上此注解代表这个类开启异步处理
public class DemoPublisher 

// 并在方法上添加  @Async  注解表示此方法开启异步
@Async
public void publishMsg(String msg)

此时再运行程序,可以看到新建了线程帮我们异步处理了数据。

image.png

7.2:更简单的方式

都用 Spring了,有没有更简单的方式,比如注解?

这时候可以使用 @EventListener 来进行处理操作。

在 #2 事件监听器的时候,我们需要实现 ApplicationListener接口 并指定类型,重写 onApplicationEvent 方法,当我们有多个需要监听的时候,这种方式就显得特别复杂繁琐。

所以在使用 @EnentListener 注解就可以完美的融入到我们的业务类当中,只需要将 #2 事件监听器中的代码改为下述代码:

@Component
@EnableAsync
public class DemoListener{

    /**
     * author:xiaoFsu
     * time:2020.09.18 9:59:09
     * 使用此方法对消息进行接收处理
     */
    @Async
    @EventListener
    public void onApplicationEvent(DemoEvent event) {
        System.out.println("DemoEvent -- >  线程名称:"+Thread.currentThread().getName()+ " ->> 时间:"+ new Date()+ " ->>消息:"+event.getMsg());
        // 模拟短信发送处理需要的时长
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * author:xiaoFsu
     * time:2020.09.18 9:59:09
     * 使用此方法对消息进行接收处理
     */
    @Async
    @EventListener
    public void onApplicationEvent(MyEvent event) {
	// 我这里新建了一个 MyEvent,与DemoEvent相同,仅更改了名称,为了演示使用。
        System.out.println("MyEvent -- > 线程名称:"+Thread.currentThread().getName()+ " ->> 时间:"+ new Date()+ " ->>消息:"+event.getMsg());
        // 模拟短信发送处理需要的时长
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在启动类中,我们来发布这些消息,看看是否能够监听到这些消息:

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        // 必须指定一下这个
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);

        DemoPublisher bean = context.getBean(DemoPublisher.class);
	// DemoEvent 类,模拟注册成功操作
        bean.publishMsg("A 注册成功");
        bean.publishMsg("B 注册成功");
        bean.publishMsg("C 注册成功");
        bean.publishMsg("D 注册成功");
        bean.publishMsg("E 注册成功");
        bean.publishMsg("F 注册成功");
        bean.publishMsg("G 注册成功");

	// MyEvent 类,模拟发送数据操作
        bean.publishMsg2("A 发送成功");
        bean.publishMsg2("B 发送成功");
        bean.publishMsg2("C 发送成功");
        bean.publishMsg2("D 发送成功");
        bean.publishMsg2("E 发送成功");
        bean.publishMsg2("F 发送成功");
        bean.publishMsg2("G 发送成功");
//        context.close();
    }

编写完成之后,查看控制台输出如下:
image.png

8:总结

SpringEvent 自定义事件链,是实用性很强的一种设计,可以利用它来做业务剥离,复杂场景解耦、代码独立等,也是事件驱动模型的核心,并且可以处理一对多,点对点,发布订阅的场景。

勿忘国耻,铭记历史,吾辈自强。

image.png

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议