1、spring-监听事件基本原理
- Spring的事件监听机制和发布订阅机制是很相似的:发布了一个事件后,监听该类型事件的所有监听器会触发相应的处理逻辑
2、Spring 监听事件相关规范
在Spring中,事件监听机制主要涉及到了一下几个关键的规范(抽象类及接口):ApplicationEvent
、ApplicationListener
ApplicationEventPublisher
-
ApplicationEvent: Spring的事件是符合jdk的规范的,这个抽象类继承了jdk内置的事件规范类
EventObject
(即jdk建议所有的事件都继承EventObject这个类)。ApplicationEvent
是Spring家的事件规范。所以我们在自定义事件的时候,可以继承与ApplicationEvent,比如,Spring家自己关于容器上下文事件就又定义了一个容器上下文的时间规范ApplicationContextEvent,它同样是继承于ApplicationEvent的,只不过扩充了获取发出事件容器的方法;今后,我们同样可以在继承于ApplicationEvent的基础上定义自己的事件规范。 -
ApplicationListener:这是一个函数式接口,同样时事件监听器的规范,当监听到自己监听的事件类型时就会调用
onApplicationEvent
方法来执行监听逻辑 -
ApplicationEventPublisher:这同样是一个函数式接口,定义了事件发布的规范,任何的事件发布器
ApplicationEventPublisher
都是通过调用publishEvent来进行事件的发布
3、代码实现-自定事件
public class MyEvent extends ApplicationEvent {
private user user;
//注册方法也行。list<> 集合,对象
public MyEvent(Object source, user user) {
super(source);
this.user = user;
}
public user getMessage() {
return user;
}
}
定义一个user实体类
@Data
public class user {
private String name;
private String age;
}
4、监听事件
- springboot进行事件监听有四种方式,选择任意一种即可
- 将监听器装载入spring容器(常用)
- 通过@EventListener注解实现事件监听(常用)
- 手工向ApplicationContext中添加监听器
- 在application.properties中配置监听器
- 4.1、 监听–ApplicationListener
@Component
@Slf4j
public class MyListener1 implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
//写自己的方法
log.info("{}监听器1:监听到事件{}",MyListener1.class.getName(),event.getMessage());
System.out.println("---------------------->监听器1:监听到事件");
}
}
- 4.2、 监听–注释
@Component
@Slf4j
public class MyListener2 {
@EventListener(value = MyEvent.class)
@Async
public void listener(MyEvent event) {
//写自己的方法
log.info("{}监听器1:监听到事件{}", MyListener2.class.getName(),event.getMessage());
System.out.println("---------------------->监听器1:监听到事件");
}
}
- 4.3、监听–配置文件
通过配置文application.properties中配置监听器: context.listener.classes=com.listener.MyListener1
- 4.4、监听–手动添加监听
@SpringBootApplication
public class SpringListenerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringListenerApplication.class, args);
// 添加监听器
run.addApplicationListener(new MyListener1());
}
}
5、发布事件
//使用 applicationContext 和ApplicationEventPublisher 都可以发布事件,人选则其一即可。
@RestController
@RequestMapping("/my")
public class MyController {
@Autowired
private ApplicationContext applicationContext;
//@Autowired
// private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping("/test")
public void test() {
user user = new user();
user.setName("GJ");
user.setAge("12");
System.out.println(applicationContext.getApplicationName());
applicationContext.publishEvent(new MyEvent(this,user));
// applicationEventPublisher.publishEvent(new MyEvent(this,user));
}
}
//直接访问即可
6、工作中使用 ----发布者模式
@Component
public class ApplicationContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
private ApplicationContextUtil() {
}
public static <T> T getBean(Class<T> clazz) throws BeansException{
return applicationContext.getBean(clazz);
}
public static void publishEvent(Object event){;
applicationContext.publishEvent(event);
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
}
- 6.1、添加 NoticeDto 类对MyEvent进行继承
@Data
public class NoticeDto extends MyEvent{
private String noticeType;
public NoticeDto(Object source) {
super(source);
}
}
@Data
public class MyEvent extends ApplicationEvent {
private String message;
//ApplicationEvent 需要有参的构造,所以必须调用super
public MyEvent(Object source) {
super(source);
}
}
- 6.2、监听器
@Component
@Slf4j
public class MyListener3 {
@EventListener
@Async
//当然这个地方也可以使用NoticeDto 作为参数。
public void listener(MyEvent event) {
// 假设 MyEvent 是 NoticeDto 的父类或接口,并且某个 MyEvent 类型的对象 event 实际上是 NoticeDto 的一个实例。
// 当这个对象被传递给 listener 方法时,它仍然保持着其作为 NoticeDto 实例的所有属性和值。
// 在方法内部,通过 (NoticeDto) event 进行向下转型,将这个对象视为 NoticeDto 类型,从而可以访问 NoticeDto 特有的属性和方法。
// 由于对象的内部状态在转换过程中没有改变,所以 NoticeDto 的属性会保留其原有的值。 总的来说,转换类型并没有改变对象的实际内容,只是改变了我们与对象交互的方式。
// 因此,NoticeDto 中的属性在转换后仍然保持其原有的值。
NoticeDto event1 = (NoticeDto) event;
//写自己的方法
log.info("{}监听器:监听到事件{}", MyListener3.class.getName(),event.getMessage());
System.out.println("---------------------->监听器:监听到事件");
System.out.println(event.getMessage()+"\n"+event.getSource().getClass().getName());
System.out.println(event1.getNoticeType());
System.out.println(event1.getMessage());
}
}
- 6.3、访问测试:
@RequestMapping("/test2")
public void test2() {
//不管怎么写,都会被监听,但必须传送一个 object source,传送什么无所谓。
NoticeDto noticeDto = new NoticeDto(this);
noticeDto.setNoticeType("123");
noticeDto.setMessage("abc");
ApplicationContextUtil.publishEvent(noticeDto);
}
}
//结果为:
2024-07-09 23:43:57.180 INFO 8100 --- [nio-8888-exec-1] com.example.listener.MyListener3 : com.example.listener.MyListener3监听器:监听到事件abc
---------------------->监听器:监听到事件
abc
com.example.listener.MyController
123
abc