原 荐 SpringBoot | 第三十二章:事件的发布和监听

栏目: Java · 发布时间: 6年前

内容简介:今天去官网查看示例前,我们来了解下相关知识点。java中的事件机制一般包括3个部分:

前言

今天去官网查看 spring boot 资料时,在特性中看见了 系统的事件及监听 章节。想想, spring 的事件应该是在 3.x 版本就发布的功能了,并越来越完善,其为 beanbean 之间的消息通信提供了支持。比如,我们可以在 用户注册成功后,发送一份注册成功的邮件至用户邮箱或者发送短信 。使用事件其实最大作用,应该还是为了 业务解耦 ,毕竟用户注册成功后,注册服务的事情就做完了,只需要发布一个用户注册成功的事件,让其他监听了此事件的业务系统去做剩下的事件就好了。对于事件发布者而言,不需要关心谁监听了该事件,以此来解耦业务。今天,我们就来讲讲 spring boot 中事件的使用和发布。当然了,也可以使用像 guavaeventbus 或者异步框架 Reactor 来处理此类业务需求的。本文仅仅谈论 ApplicationEvent 以及 Listener 的使用。

一点知识

示例前,我们来了解下相关知识点。

Java的事件机制

java中的事件机制一般包括3个部分: EventObjectEventListenerSource

EventObject

java.util.EventObject是事件状态对象的基类,它封装了事件源对象以及和事件相关的信息。所有 java 的事件类都需要继承该类。

EventListener

java.util.EventListener是一个标记接口,就是说该接口内是没有任何方法的。所有事件监听器都需要实现该接口。事件监听器注册在事件源上,当事件源的属性或状态改变的时候,调用相应监听器内的回调方法。

Source

事件源不需要实现或继承任何接口或类,它是事件最初发生的地方。因为事件源需要注册事件监听器,所以事件源内需要有相应的盛放事件监听器的容器。

java 的事件机制是一个观察者模式。大家可以根据这个模式,自己实现一个。可以看看这篇博文:《 java事件机制 》一个很简单的实例。

Spring的事件

ApplicationEvent 以及 ListenerSpring 为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

  • ApplicationEvent 就是 Spring 的事件接口
  • ApplicationListener 就是 Spring 的事件监听器接口,所有的监听器都实现该接口
  • ApplicationEventPublisherSpring 的事件发布接口, ApplicationContext 实现了该接口
  • ApplicationEventMulticaster 就是 Spring 事件机制中的事件广播器,默认实现 SimpleApplicationEventMulticaster

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

其执行的流程大致为:

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

Spring 中,使用注册监听接口,除了继承 ApplicationListener 接口外,还可以使用注解 @EventListener 来监听一个事件,同时该注解还支持 SpEL 表达式,来触发监听的条件,比如只接受编码为 001 的事件,从而实现一些个性化操作。下文示例中会简单举例下。

简单来说,在Java中,通过java.util. EventObject来描述事件,通过java.util. EventListener来描述事件监听器,在众多的框架和组件中,建立一套事件机制通常是基于这两个接口来进行扩展。

SpringBoot的默认启动事件

SpringBoot1.5.x 中,提供了几种事件,供我们在开发过程中进行更加便捷的扩展及差异化操作。

  • ApplicationStartingEvent :springboot启动开始的时候执行的事件

  • ApplicationEnvironmentPreparedEventspring boot 对应 Enviroment 已经准备完毕,但此时上下文 context 还没有创建。在该监听中获取到 ConfigurableEnvironment 后可以对配置信息做操作,例如:修改默认的配置信息,增加额外的配置信息等等。

  • ApplicationPreparedEventspring boot 上下文 context 创建完成,但此时 spring 中的 bean 是没有完全加载完成的。在获取完上下文后,可以将上下文传递出去做一些额外的操作。值得注意的是: 在该监听器中是无法获取自定义bean并进行操作的。

  • ApplicationReadyEventspringboot 加载完成时候执行的事件。

  • ApplicationFailedEventspring boot 启动异常时执行事件。

原 荐 SpringBoot | 第三十二章:事件的发布和监听

从官网文档中,我们可以知道,由于一些事件实在上下文为加载完触发的,所以无法使用注册 bean 的方式来声明,文档中可以看出,可以通过 SpringApplication.addListeners(…​) 或者 SpringApplicationBuilder.listeners(…​) 来添加,或者添加 META-INF/spring.factories 文件z中添加监听类也是可以的,这样会自动加载。

org.springframework.context.ApplicationListener=com.example.project.MyListener

启动类中添加:

@SpringBootApplication
public class Application {

    public static void main(String[] args){
        SpringApplication app =new SpringApplication(Application.class);
        app.addListeners(new MyApplicationStartingEventListener());//加入自定义的监听类
        app.run(args);
    }
}

所以在需要的时候,可以通过适当的监听以上事件,来完成一些业务操作。

自定义事件发布和监听

通过以上的介绍,我们来定义一个自定义事件的发布和监听。

0.加入POM依赖,这里为了演示加入了 web 依赖。事件相关类都在 spring-context 包下。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

1.自定义事件源和实体。

MessageEntity.java

/**
 * 消息实体类
 * @author oKong
 *
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageEntity {

    String message;
    
    String code;
}

CustomEvent.java

/**
 * 编写事件源
 * @author oKong
 *
 */
@SuppressWarnings("serial")
public class CustomEvent extends ApplicationEvent{

    private MessageEntity messageEntity;
    
    public CustomEvent(Object source, MessageEntity messageEntity) {
        super(source);
        this.messageEntity = messageEntity;
    }
    
    public MessageEntity getMessageEntity() {
        return this.messageEntity;
    }
}

2.编写监听类

使用 @EventListener 方式。

/**
 * 监听配置类
 * 
 * @author oKong
 *
 */
@Configuration
@Slf4j
public class EventListenerConfig {

    @EventListener
    public void handleEvent(Object event) {
        //监听所有事件 可以看看 系统各类时间 发布了哪些事件
        //可根据 instanceof 监听想要监听的事件
//        if(event instanceof CustomEvent) {
//            
//        }
        log.info("事件:{}", event);
    }
    
    @EventListener
    public void handleCustomEvent(CustomEvent customEvent) {
        //监听 CustomEvent事件
        log.info("监听到CustomEvent事件,消息为:{}, 发布时间:{}", customEvent.getMessageEntity(), customEvent.getTimestamp());
    }
    
    /**
     * 监听 code为oKong的事件
     */
    @EventListener(condition="#customEvent.messageEntity.code == 'oKong'")
    public void handleCustomEventByCondition(CustomEvent customEvent) {
        //监听 CustomEvent事件
        log.info("监听到code为'oKong'的CustomEvent事件,消息为:{}, 发布时间:{}", customEvent.getMessageEntity(), customEvent.getTimestamp());
    }
    
    @EventListener 
    public void handleObjectEvent(MessageEntity messageEntity) {
        //这个和eventbus post方法一样了
        log.info("监听到对象事件,消息为:{}", messageEntity);
        
    }
}

** 注意: Spring 中,事件源不强迫继承 ApplicationEvent 接口的,也就是可以直接发布任意一个对象类。但内部其实是使用 PayloadApplicationEvent 类进行包装了一层。这点和 guavaeventBus 类似。**

而且,使用 @EventListenercondition 可以实现更加精细的事件监听, condition 支持 SpEL 表达式,可根据事件源的参数来判断是否监听。

使用 ApplicationListener 方式。

@Component
@Slf4j
public class EventListener implements ApplicationListener<CustomEvent>{

    @Override
    public void onApplicationEvent(CustomEvent event) {
        //这里也可以监听所有事件 使用  ApplicationEvent 类即可
        //这里仅仅监听自定义事件 CustomEvent
        log.info("ApplicationListener方式监听事件:{}", event);
    }
}

3.编写控制类,示例发布事件。

/**
 * 模拟触发事件
 * @author oKong
 *
 */
@RestController
@RequestMapping("/push")
@Slf4j
public class DemoController {

    /**
     * 注入 事件发布类
     */
    @Autowired
    ApplicationEventPublisher eventPublisher;
    
    @GetMapping
    public String push(String code,String message) {
        log.info("发布applicationEvent事件:{},{}", code, message);
        eventPublisher.publishEvent(new CustomEvent(this, MessageEntity.builder().code(code).message(message).build()));
        return "事件发布成功!";
    }
    
    @GetMapping("/obj")
    public String pushObject(String code,String message) {
        log.info("发布对象事件:{},{}", code, message);
        eventPublisher.publishEvent(MessageEntity.builder().code(code).message(message).build());
        return "对象事件发布成功!";
    }
}

4.编写启动类。

/**
 * 事件监听
 * 
 * @author oKong
 *
 */
@SpringBootApplication
@Slf4j
public class EventAndListenerApplication {
    public static void main(String[] args) throws Exception {

        SpringApplication app =new SpringApplication(EventAndListenerApplication.class);
        app.addListeners(new MyApplicationStartingEventListener());//加入自定义的监听类
        app.run(args);
        log.info("spring-boot-event-listener-chapter32启动!");
    }
}

这里,创建了个 ApplicationStartingEvent 事件监听类。

/**
 * 示例-启动事件
 * @author oKong
 *
 */
public class MyApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent>{

    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        // TODO Auto-generated method stub
        //由于 log相关还未加载 使用了也输出不了的
//        log.info("ApplicationStartingEvent事件发布:{}", event);
        System.out.println("ApplicationStartingEvent事件发布:" + event.getTimestamp());
    }

}

5.启动应用,控制台可以看出,在启动时,我们监听到了 ApplicationStartingEvent 事件

原 荐 SpringBoot | 第三十二章:事件的发布和监听

首先访问下: http://127.0.0.1:8080/push?code=lqdev&message=趔趄的猿 ,可以看见事件已经被监听到了, 而监听了 codeoKong 的监听未触发。

原 荐 SpringBoot | 第三十二章:事件的发布和监听

然后访问下: http://127.0.0.1:8080/push?code=oKong&message=趔趄的猿 ,可以看见此时 三个监听事件都接收到了事件了

原 荐 SpringBoot | 第三十二章:事件的发布和监听

此时,由于写了一个监听所有事件的方法,可以看见请求结束后,会发布一个事件 ServletRequestHandledEvent ,里面记录了请求的时间、请求url、请求方式等等信息。

事件:ServletRequestHandledEvent: url=[/push]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]

异步监听处理

默认情况下,监听事件都是同步执行的。在需要异步处理时,可以在方法上加上 @Async 进行异步化操作。此时,可以定义一个线程池,同时开启异步功能,加入 @EnableAsync

对于异步处理,可以查看之前发布的文章: 《第二十一章:异步开发之异步调用》 。里面有详细的介绍异步调用,这里就不阐述了。

异步简单示例:

/**
     * 监听 code为oKong的事件
     */
    @Async
    @EventListener(condition="#customEvent.messageEntity.code == 'oKong'")
    public void handleCustomEventByCondition(CustomEvent customEvent) {
        //监听 CustomEvent事件
        log.info("监听到code为'oKong'的CustomEvent事件,消息为:{}, 发布时间:{}", customEvent.getMessageEntity(), customEvent.getTimestamp());
    }

关于事务绑定事件

当一些场景下,比如在用户注册成功后,即数据库事务提交了,之后再异步发送邮件等,不然会发生数据库插入失败,但事件却发布了,也就是邮件发送成功了的情况。此时,我们可以使用 @TransactionalEventListener 注解或者 TransactionSynchronizationManager 类来解决此类问题,也就是:事务成功提交后,再发布事件。当然也可以利用返回上层(事务提交后)再发布事件的方式了,只是不够优雅而已罢了,其实能起作用就好了,是吧~

本例中未使用到数据库,就不示例了,都在 Spring-tx 包下。

原 荐 SpringBoot | 第三十二章:事件的发布和监听

具体可查看文章: Spring Event 事件中的事务控制

原 荐 SpringBoot | 第三十二章:事件的发布和监听

原 荐 SpringBoot | 第三十二章:事件的发布和监听

参考资料

  1. https://docs.spring.io/spring-boot/docs/1.5.15.RELEASE/reference/htmlsingle/#boot-features-application-events-and-listeners

  2. https://blog.csdn.net/eos2009/article/details/77773551

  3. https://www.cnblogs.com/senlinyang/p/8496099.html

总结

本章节主要简单介绍了 spring 的事件机制。感兴趣的同学,可以编写一个监听所有事件的方法,然后看看系统运行各类请求或者相关操作时,系统会发布哪些事件,了解后可以在之后碰见一些特殊业务需求时,可以适当的监听相关的事件来完成特定的业务公共。同时对这种观察者模式,大家还可以看看 eventbusreactor 了。后者没用过,有时间倒是可以看看。最近买了本 RxJava2 书籍,确实要好好补课下了。

最后

目前互联网上很多大佬都有 SpringBoot 系列教程,如有雷同,请多多包涵了。 原创不易,码字不易 ,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。

老生常谈

499452441
lqdevOps

原 荐 SpringBoot | 第三十二章:事件的发布和监听

个人博客: http://blog.lqdev.cn

完整示例: https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-32

原文地址: https://blog.lqdev.cn/2018/11/06/springboot/chapter-thirty-two/

原 荐 SpringBoot | 第三十二章:事件的发布和监听

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms of the Intelligent Web

Algorithms of the Intelligent Web

Haralambos Marmanis、Dmitry Babenko / Manning Publications / 2009-7-8 / GBP 28.99

Web 2.0 applications provide a rich user experience, but the parts you can't see are just as important-and impressive. They use powerful techniques to process information intelligently and offer featu......一起来看看 《Algorithms of the Intelligent Web》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换