SpringBoot 03 事件ApplicationEvent监听处理

1.为什么使用事件

事件和直接方法调用适合于不同的情况。使用方法调用需要知道某件事A发生并调用某个方法B处理它, 即知道发生什么事情然后做某件事的场景; 对于事件来说我们知道发生了一个事件A,那些模块会被通知处理并不是我们关系的问题,即知道发生什么事情但不关心要做某事的场景。比如,当我们想要将某些业务处理传递给另一个线程时候,使用事件更好一些


2.Spring 自定义事件

Spring 事件/监听机制属于事件/监听器设计模式, 可以视为观察者模式(Observer Pattern)的扩展, 观察者模式的Observable发布的对象是Object,因此观察者模式可以发布任何对象, 而事件/监听器模式发布的内容是有类型限制的,在 Java中它必须是EventObject对象, 因此Spring 事件抽象类ApplicationEvent继承了EventObject

Spring事件机制的几个重要组件,

  • 事件(event)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标,一般是ApplicationEvent的子类

  • 事件发布者(publisher)事件发生的触发者。

  • 事件广播器(Multicaster)作为实际发布事件的委托,将所有事件多播给注册的监听器,实现让监听器忽略它们不感兴趣的事件

  • 监听器(listener)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。

2.1 创建事件的方式

创建事件类MessageEvent 继承ApplicationEvent即可

public class MessageEvent extends ApplicationEvent {

    private String message;

    public MessageEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    //省略getter和setter方法
}

2.2发布事件方法

创建消息发布对象MessagePublish

  • 通过@Autowired自动向bean中注入AnnotationConfigApplicationContext
  • publish方法通过ApplicationContext#publishEvent发布消息,
package com.boot.basic.event;

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

@Component
public class MessagePublish {
    
    @Autowired
    AnnotationConfigApplicationContext applicationContext;

    public void publish(String msg) {
        applicationContext.publishEvent(new MessageEvent(this,msg));
    }
}

 在Spring Boot中应用上下文对象publishEvent后, 事件将交给SimpleApplicationEventMulticaster处理, 它是ApplicationEventMulticaster接口的实现类, 该接口主要承担两种职责,一是关联ApplicationListener,二是广播ApplicationEvent

2.3.事件监听器监听并处理

  • 方式1面向接口编程: 此方式实现ApplicationListener接口, 创建MessageListener监听
  • 方式2基于注解驱动: 使用注解 @EventListener实现Spring事件监听

 创建启动类将EventConfig通过@Configuration声明为配置类,启动后发布条内容为This is a event msg的消息,用于测试上面的场景

package com.boot.basic.event;

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

@Configuration
@ComponentScan("com.boot.basic.event")
public class EventConfig {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("==========start context=============");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(EventConfig.class);
        //容器启动后发布1条消息
        MessagePublish publish = context.getBean(MessagePublish.class);
        publish.publish("This is a event msg");

        System.out.println("==========close context=============");
    }
}

2.3.1面向接口编程

此方式实现ApplicationListener接口, 创建MessageListener监听

package com.boot.basic.event;

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


@Component
public class MessageListener implements ApplicationListener<MessageEvent> {

    @Override
    public void onApplicationEvent(MessageEvent messageEvent) {

        String msg = messageEvent.getMessage();
        System.out.println();
        System.out.println("Message Tesing######## "+this.getClass().getName() + ":" + msg);

    }
}

监听器实现方式

1. 启动容器时从注册的Bean中找到所有ApplicationListener类型的Bean,将这些监听注册到事件广播对象中

    // AbstractApplicationContext#registerListeners部分代码
	protected void registerListeners() {
        //略掉部分代码
        //1. 通过ApplicationListener获取到实现其接口的所有BEAN
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
            //2.将ApplicationListener监听Beans 都添加到事件广播器中
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

	    //略掉部分代码
	}

2.,事件发布者将时间提交到广播器中, 广播器循环注册的监听如果符合条件就让监听器执行事件处理

  • 事件发布者发布事件后将时间发给SimpleApplicationEventMulticaster 对象
  • 事件广播对象SimpleApplicationEventMulticaster先解析resource获取类型,然后从注册到自身的事件监听器集合applicationListeners中获取满足条件的监听器
  • 每个满足监听条件的监听器执行自己的onApplicationEvent方法处理监听到的事件信息

2.3.2基于注解驱动

使用注解 @EventListener实现Spring事件监听,简化编写监听类的步骤,不需要再继承ApplicationListener接口去实现onApplicationEvent

  •  @EventListener 注解,指定 classes,即需要处理的事件类型,一般就是 ApplicationEven 及其子类,例如@EventListener(MessageEvent.class)

package com.boot.basic.event;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 基于注解驱动 使用注解 @EventListener实现Spring事件监听
 */
@Component
class EventsListener {

    @EventListener(MessageEvent.class)
    public void onEvent(MessageEvent event) {
        String msg = event.getMessage();
        System.out.println();
        System.out.println("Message Tesing######## "+this.getClass().getName() + ":" + msg);
    }
}

监听器实现方式

1. Spring容器初始化 时, AbstractApplicationContext#finishBeanFactoryInitialization阶段将注解类生成一个事件ApplicationListener接口的ApplicationListenerMethodAdapter对象

2. 事件发布者将时间提交到广播器中, 广播器循环注册的监听如果符合条件就让监听器执行事件处理

  • 事件发布者发布事件后将时间发给SimpleApplicationEventMulticaster 对象
  • 事件广播对象SimpleApplicationEventMulticaster先解析resource获取类型,然后从注册到自身的事件监听器集合applicationListeners中获取满足条件的监听器,这里获取到的是ApplicationListenerMethodAdapter对象, 
  • ApplicationListenerMethodAdapter先解析事件参数信息,然后执行invoke,通过反射的方式执行EventListener#onEvent

2.3.3.实现异步事件

上述方式为同步处理,当多个监听器接收事件并处理时,如果先处理的监听器阻塞,那么之后的监听器都需要等其处理完后再处理

Message Tesing######## com.boot.basic.event.EventsListener:This is a event msg

Message Tesing######## com.boot.basic.event.MessageListener:This is a event msg
==========close context=============

SimpleApplicationEventMulticaster允许时间广播的时候ApplicaitonListener异步处理监听时间, 其中ExecutortaskExecutor通过,源码参考如下

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	@Nullable
	private Executor taskExecutor;

    //注入线程池
	public void setTaskExecutor(@Nullable Executor taskExecutor) {
		this.taskExecutor = taskExecutor;
	}

    @Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {    
            
            //这里判断如果对象注入了taskExcutor,那么就创建线程异步处理
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
    
    //略
}

可以使用 @EnableAsync开启Spring异步处理流程,默认启动流程为搜索线程池定义中的 TaskExcute实例,或一个名为 taskExcute的java.util.concurrent.Excecutor实例,如果以上都没找到,则会使用SimpleAsyncTaskExceuteor  处理异步方法调用。

参考代码

修改 EventConfig 类使用 @EnableAsync开启异步处理,并设置一个线程池

package com.boot.basic.event;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@EnableAsync
@Configuration
@ComponentScan("com.boot.basic.event")
public class EventConfig {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("==========start context=============");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(EventConfig.class);
        MessagePublish publish = context.getBean(MessagePublish.class);
        publish.publish("This is a event msg");

        System.out.println("==========close context=============");
        //context.close();
    }

    private static final int corePoolSize = 10;       		// 核心线程数(默认线程数)
    private static final int maxPoolSize = 100;			    // 最大线程数
    private static final int keepAliveTime = 10;			// 允许线程空闲时间(单位:默认为秒)
    private static final int queueCapacity = 200;			// 缓冲队列数
    private static final String threadNamePrefix = "Async-Service-"; // 线程池名前缀

    @Bean("taskExecutor") 
    public ThreadPoolTaskExecutor getAsyncExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

2. 修改监听器,增加注解@Async使监听器处理方法异步实现

package com.boot.basic.event;

import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 面向接口编程,实现ApplicationListener接口
 */
@Component
public class MessageListener implements ApplicationListener<MessageEvent> {

    @Async
    @Override
    public void onApplicationEvent(MessageEvent messageEvent) {
        try {
            Thread.sleep(3000L);
            String msg = messageEvent.getMessage();
            System.out.println();
            System.out.println("Message Tesing######## "+this.getClass().getName() + ":" + msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

重新运行EventConfig,会发现打印close contex后才执行MsssageListener#onApplicationListener 方法, 云因是方法执行异步,且处理时候线程sleep等待了几秒

Message Tesing######## com.boot.basic.event.EventsListener:This is a event msg
==========close context=============

Message Tesing######## com.boot.basic.event.MessageListener:This is a event msg

3.Spring Boot 自定义事件

1. 创建时间监听

package com.boot.basic.event;

import org.springframework.context.ApplicationListener;

public class TestEventListener implements ApplicationListener<MessageEvent> {

    public void onApplicationEvent(MessageEvent messageEvent) {
        System.out.println("Message Tesing######## "+this.getClass().getName() + ":" + messageEvent.getMessage());
    }

}

2. 启动类添加创建的监听

@RestController
@SpringBootApplication
public class EventDemoApplication {

    @Autowired
    private MessagePublish messagePublish;

    public static void main(String[] args) {
        SpringApplication springApplication =
                new SpringApplication(EventDemoApplication.class);
        springApplication.addListeners(new TestEventListener());
        springApplication.run(args);
    }

    @GetMapping("/send")
    public String hello(@RequestParam(value = "msg", defaultValue = "Test message") String message) {
        messagePublish.publish(message);
        return "success";
    }

}

测试访问 http://127.0.0.1:8080/send?msg=show.me.the.money

 查看控制台会发现启动添加的监听TestEventListener:show和通过组件添加的监听器都正常运行

Message Tesing######## com.boot.basic.event.EventsListener:show.me.the.money
Message Tesing######## com.boot.basic.event.TestEventListener:show.me.the.money
Message Tesing######## com.boot.basic.event.MessageListener:show.me.the.money

上面的所有代码都可以从github 找到 :  链接:GITHUB - 事件DEMO

前一篇: SpringBoot 02 配置文件

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

=PNZ=BeijingL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值