Spring中的事件机制

发布时间:2024年01月15日

摘要

在这篇文章我们将介绍Spring的事件机制,包括Spring内置事件、自定义事件、事件监听、事件发布、事件广播机制、事件异常处理等内容。Spring事件是对JDK提供的观察者模式,结合Spring容器进行的深度扩展,了解Spring提供了那些内置事件,以及如何自定义事件可以让我们很方便的对Spring框架进行扩展,来实现更多更丰富的功能。🦾

正文

jdk事件

说到事件监听,不得不提到观察者模式,jdk提供了简单的观察者模式工具类方便我们使用,它包括 java.util.Observable类和java.util.Observer接口,其中 Observer作为观察者来监听事件, Observable作为事件发布者。简单的样例代码如下


public class JdkObserver {
    public static void main(String[] args) {
        MyObservable observable = new MyObservable();
        //监听事件
        observable.addObserver(new MyObserver());
        //发布事件
        observable.notifyObservers("测试事件发布");
    }

}

class MyObservable extends Observable{
    @Override
    public void notifyObservers(Object arg) {
        super.setChanged();
        super.notifyObservers(new EventObject(arg));
        super.clearChanged();
    }
}

class MyObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        EventObject event = (EventObject) arg;
        System.out.println("收到事件:"+event);
    }
}

Spring事件

通过上例可以发现,要想监听一个事件,有三个要素,①事件本身EventObject、②发布事件的动作notifyObservers、③监听事件addObserver。Spring标准事件对Java标准事件EventObject进行了扩展,添加了一个事件发生时间戳属性,同时Spring内置了一系列的应用上下文事件,方便调用

Spring内置的上下文应用事件包括:

  • Spring应用上下文就绪事件:ContextRefreshedEvent
  • Spring应用上下文启动事件:ContextStartedEvent
  • Spring应用上下文停止事件:ContextStoppedEvent
  • Spring应用上下文关闭事件:ContextClosedEvent

以上四个内置事件都继承自 ApplicationContextEvent这个类,而 ApplicationContextEvent继承了 ApplicationEventApplicationEvent继承自 Java.util.EventObject。整体的类继承关系如下图所示

在这里插入图片描述

在实际应用中,可以监听以上四个内置事件,也可以直接监听 ApplicationEvent,实现对所有Spring事件的监听,如果以上几种都不能满足需求,也可以通过实现 ApplicationEventApplicationContextEvent来自定义事件。一个简单的自定义事件如下代码所示

public class MyCustomEvent extends ApplicationContextEvent {
    public MyCustomEvent(Object source) {
        super(source);
    }
}

Spring事件监听

在Spring中监听事件的方法有很多种,主要包括

  1. 通过ApplicationContext主动监听
  2. 通过@EventListener注解监听
  3. 注册一个ApplicationEventListener类型的Bean
ApplicationContext主动监听

通过ApplicationContext主动添加事件监听,需要能获取到 ApplicationContext这个应用上下文,最简单的方式是实现 ApplicationContextAware接口,关于 ApplicationContextAware的具体内容可以看这篇文章

@Component
public class SpringEventComponent implements ApplicationContextAware {
    private Logger logger= LoggerFactory.getLogger(SpringEventComponent.class);

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(applicationContext instanceof AbstractApplicationContext){
            ((AbstractApplicationContext) applicationContext).addApplicationListener(new ApplicationListener<ApplicationEvent>() {
                @Override
                public void onApplicationEvent(ApplicationEvent event) {
                    logger.info("接收到事件:"+event);
                }
            });
        }
        this.context=applicationContext;
    }
}
注解监听

通过注解注册事件监听的方式很简单,只需要在方法上使用 @EventListener注解即可

@Component
public class SpringEventComponent implements ApplicationContextAware {
    private Logger logger= LoggerFactory.getLogger(SpringEventComponent.class);

    private ApplicationContext context;
    
    @EventListener
    public void annoEventListener(ApplicationEvent event){
        logger.info("Annotation接收到事件:"+event);
    }
}

基于注解的这种方式,还支持通过 @Async注解实现异步事件

@EventListener
@Async
public void annoEventListener(ApplicationEvent event){
    logger.info("Annotation接收到事件:"+event);
}

//需要在主类种启动异步支持
@SpringBootApplication
@EnableAsync
public class SpringNotesApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringNotesApplication.class, args);
    }
}

同步和异步的区别在于,同步的方式事件监听在Main线程,异步的方式是在一个新的线程,可以通过日志打印查看具体的执行线程,异步的效果如下

2024-01-15  11:41:00.854 [task-3] INFO  top.sunyog.spring.notes.event.SpringEventComponent - Annotation接收到事件:org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@6feb8a8c]

同步的效果如下

2024-01-15  11:43:24.469 [main] INFO  top.sunyog.spring.notes.event.SpringEventComponent - Annotation接收到事件:org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@726a6b94]
Bean监听

通过注册一个 ApplicationListener类型的Bean也可以实现事件的监听。注册Bean的方式也是多种多样的,如:①可以通过@Bean注解注册一个Bean;②通过ApplicationContext注册Bean;③通过自定义BeanDefinition注册Bean。要了解Bean的配置或注册方式,可以查看这篇文章

以下代码是通过@Bean注解注册一个ApplicationListener

@Bean
public ApplicationListener applicationListener(){
    ApplicationListener<ApplicationEvent> listener = new ApplicationListener<ApplicationEvent>(){
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            logger.info("@Bean接收到事件:"+event);
        }
    };

    return listener;
}

Spring事件发布

Spring内置了事件发布器 ApplicationEventPublisher,通过它可以实现自定义事件的发布,事件发布器可以通过依赖注入获取,也可以通过实现 ApplicationEventPublisherAware来获取。发布一个简单的自定义事件的代码如下

@Component
public class SpringEventComponent implements ApplicationEventPublisherAware, ApplicationRunner {
    private Logger logger= LoggerFactory.getLogger(SpringEventComponent.class);

    private ApplicationEventPublisher publisher;

    @Bean
    public ApplicationListener applicationListener(){
        ApplicationListener<ApplicationEvent> listener = new ApplicationListener<ApplicationEvent>(){
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                logger.info("@Bean接收到事件:"+event);
            }
        };

        return listener;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher=applicationEventPublisher;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.publisher.publishEvent(new MyCustomEvent("自定义事件发布ing"));
    }
}

事件监听结果为

2024-01-15  14:14:17.201 [main] INFO  top.sunyog.spring.notes.event.SpringEventComponent - @Bean接收到事件:top.sunyog.spring.notes.event.MyCustomEvent[source=自定义事件发布ing]

ApplicationEventPublisher中的 publishEvent(Objecy)方法可以发布 PayloadApplicationEvent事件,这是Spring为简化事件发布而推出的一类事件,可以直接通过 ApplicationEventPublisher#publishEvent(Object)方法发布该类型的事件,PayloadApplicationEvent事件发布时除了事件本身外,还包含一个事件消息,publishEvent(Object )方法中的Object就是这个消息。

@Component
public class SpringEventComponent implements ApplicationEventPublisherAware, ApplicationRunner {
    private Logger logger= LoggerFactory.getLogger(SpringEventComponent.class);

    private ApplicationEventPublisher publisher;
    
    @EventListener
    public void annoEventListener(PayloadApplicationEvent event){
        Object payload = event.getPayload();
        logger.info("@EventListener接收到payload事件:"+event+",msg: "+payload);
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher=applicationEventPublisher;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //
        this.publisher.publishEvent("payload事件发布ing,这是一个消息");
    }
}

事件监听结果如下

2024-01-15  15:02:40.463 [main] INFO  top.sunyog.spring.notes.event.SpringEventComponent - @EventListener接收到payload事件:org.springframework.context.PayloadApplicationEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e35bc3d, started on Mon Jan 15 15:02:39 CST 2024],msg: payload事件发布ing,这是一条消息

除了 ApplicationEventPublisher之外,在Spring中还可以通过 ApplicationEventMulticaster广播器来发布事件,广播器是一个内置的SpringBean,可以通过依赖查找或依赖注入的方式获得,广播器发布事件的方式如下

public class SpringEventComponent implements ApplicationRunner {
    @Resource
    private ApplicationEventMulticaster multicaster;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.multicaster.multicastEvent(new MyCustomEvent("自定义事件广播ing"));
    }
}

事件监听结果

2024-01-15  14:20:38.008 [main] INFO  top.sunyog.spring.notes.event.SpringEventComponent - @Bean接收到事件:top.sunyog.spring.notes.event.MyCustomEvent[source=自定义事件广播ing]

这里的时间广播器 ApplicationEventMulticaster是一个接口,在Spring中,所有的事件都通过它来广播出来,具体的类继承关系图包括

SimpleApplicationEventMulticaster
Executor taskExecutor
ErrorHandler errorHandler
AbstractApplicationEventMulticaster
-DefaultListenerRetriever defaultRetriever
Map retrieverCache
ConfigurableBeanFactory beanFactory
?interface?
ApplicationEventMulticaster

AbstractApplicationEventMulticaster类的 retrieverCache属性中保存了所有已注册的 ApplicationListener,在Spring的应用上下文中保存了 SimpleApplicationEventMulticaster,当事件发布时,通过这个类向所有的事件监听器广播事件,即实现了事件的监听。

在Spring4.1版本中,引入了事件异常处理机制,在 SimpleApplicationEventMulticaster类中增加了一个 setErrorHandler方法,可以用来设定事件异常时进行的操作,如事件监听时发生如下异常

@Bean
public ApplicationListener applicationListener(){
    ApplicationListener<MyCustomEvent> listener = new ApplicationListener<MyCustomEvent>(){
        @Override
        public void onApplicationEvent(MyCustomEvent event) {
            throw new RuntimeException("事件异常");
        }
    };

    return listener;
}

这时,可以通过以下方式设置事件异常处理

@Component
public class SpringEventComponent implements ApplicationEventPublisherAware, ApplicationRunner {
    private Logger logger= LoggerFactory.getLogger(SpringEventComponent.class);

    private ApplicationEventPublisher publisher;

    @Resource
    private ApplicationEventMulticaster multicaster;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //设置事件异常处理器
        if (this.multicaster instanceof SimpleApplicationEventMulticaster){
            ((SimpleApplicationEventMulticaster) multicaster).setErrorHandler(handler->{
                String message = handler.getMessage();
                logger.info("事件发生异常:"+message);
            });
        }
        this.publisher.publishEvent(new MyCustomEvent("自定义事件发布ing"));
    }
}

发生事件异常时,的打印结果

2024-01-15  15:25:34.890 [main] INFO  top.sunyog.spring.notes.event.SpringEventComponent - 事件发生异常:事件异常

总结

本文主要介绍了Spring中的事件机制,包括事件监听、事件发布、自定义事件等。Spring中的事件实际上是对jdk提供的观察者模式的深入扩展,结合Spring容器实现了事件的深度扩展。在Spring框架项目的开发过程中,我们可以应用事件机制,实现一些常用的功能,如对容器的不同行为(启动、停止、刷新等)进行扩展。


📩 联系方式
邮箱: qijilaoli@foxmail.com
掘金: 我的掘金
CSDN: 我的CSDN

?版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问我的博客首页

文章来源:https://blog.csdn.net/qq_43408971/article/details/135603791
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。