SpringBoot事件的发布和监听

Spring的事件

对于SpringApplicationContext(BeanFactory)而言,在整个应用运行过程中(包括应用的启动、销毁), 会发布各种应用事件。开发者也可以实现自己的事件, 从而起到扩展spring框架的作用 。

Spring的事件(Application Event)为 Bean与 Bean之间的消息通信提供了支持。当一个Bean处理完一个任务之后,希望另外一个 Bean知道并能做相应的处理, 这时我们就需要让另外一个 Bean监听当前 Bean所发送的事件。

sprjng借助于 org.springframewofk.context.event.ApplicationEvent抽象类及其子类实现事件的发布,与此同时, 借助于 org.springframework.context.ApplicationListener接口及其实现者实现事件的监听,这两者构成了观察者 ( observer) 模式 。

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

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

其执行的流程大致为:

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

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

SpringBoot的默认启动事件

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

  • ApplicationStartingEvent:springboot启动开始的时候执行的事件
  • ApplicationEnvironmentPreparedEvent:spring boot对应Enviroment已经准备完毕,但此时上下文context还没有创建。在该监听中获取到ConfigurableEnvironment后可以对配置信息做操作,例如:修改默认的配置信息,增加额外的配置信息等等。
  • ApplicationPreparedEvent:spring boot上下文context创建完成,但此时spring中的bean是没有完全加载完成的。在获取完上下文后,可以将上下文传递出去做一些额外的操作。值得注意的是:在该监听器中是无法获取自定义bean并进行操作的。
  • ApplicationReadyEvent:springboot加载完成时候执行的事件。
  • ApplicationFailedEvent:spring boot启动异常时执行事件。

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

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

来用代码说明一切,我们创建一个ApplicationStartingEvent事件监听类。

@Slf4j
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());
    }
}

 

启动类中添加:

@SpringBootApplication
public class Application {

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

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

 

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

自定义事件发布和监听

为实现 ApplicationEvent事件的发布,开发者需要借助于 ApplicationContext,它提供了 publishEvent方法。Spring 的事件需要遵循如下流程:

(1) 自定义事件, 继承 ApplicaltionEvent。

(2) 定义事件监听器,实现ApplicationListener。

(3) 使用容器发布事件。

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

自定义事件源

public class DemoEvent extends ApplicationEvent{
    private static final long serialVersionUID = 1L;
    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;
    }

}

当然实际应用中,我们的消息可能不仅是字符串,所以我们也可以定义一个消息实体

spring提供了如下三种常见 ApplicationEvent事件实现,它们的含义如下:

  • org.sprjngframework.web.context.support.RequestHandledEvent: 开发者必须注意到,在 spring 的 webApplicationContext 中, 一旦客户请求处理完成, 将发布RequestHandledEvent事件。 当然, 如果需要, 开发者也可以在企业应用代码中抛出这种事件。
  • org.springframework,context.event.ContextRefreshedEvent: 开发者必须注意到, 在spring Applicationcontext 容器初始化完成或刷新时, Spring 框架本身将发布ContextRefreshedEvent事件。
  • org.springframework.context.event.ContextClosedEvent:开发者必须注意到,在关闭spring Appljcatjoncontext容器时, Spring框架本身将发布 ContextRefreshedEvent事件 。

事件监听器

1、继承ApplicationListener接口

/**
 * 事件监听器
 * 实现 ApplicationListener接口, 并指定监听的事件类型
 */
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
   /**
    * @param event 
    * 使用 onApplicationEvent方法对消息进行接受处理
    */
   public void onApplicationEvent(DemoEvent event) {     
      String msg = event.getMsg();     
      System.out.println("我(bean-demoListener)接受到了bean-demoPublisher发布的消息:"
            + msg);
   }

}

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

2、使用@EventListener方式

/**
 * 监听配置类
 * @author oKong
 */
@Configuration
@Slf4j
public class EventListenerConfig {
    @EventListener
    public void handleEvent(Object event) {
        //监听所有事件 可以看看 系统各类时间 发布了哪些事件
        //可根据 instanceof 监听想要监听的事件
//        if(event instanceof DemoEvent) {
//        }
        log.info("事件:{}", event);
    }
    @EventListener
    public void handleCustomEvent(DemoEvent demoEvent) {
        //监听 CustomEvent事件
        log.info("监听到CustomEvent事件,消息为:{}, 发布时间:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
    }
    /**
     * 监听 code为oKong的事件
     */
    @EventListener(condition="#demoEvent.msg == 'xmr'")
    public void handleCustomEventByCondition(DemoEvent demoEvent) {
        //监听 CustomEvent事件
        log.info("监听到msg为'xmr'的DemoEvent事件,消息为:{}, 发布时间:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
    }
}

事件发布类

@Component
public class DemoPublisher {
   @Autowired
    ApplicationContext applicationContext;//注入 AppllcationContext用来发布事件

   /**
    * @param msg
    * 使用 AppllicationContext的 publishEvent方法来发布
    */
   public void publish(String msg){
      applicationContext.publishEvent(new DemoEvent(this, msg));
   }

}

Spring中,事件源不强迫继承ApplicationEvent接口的,也就是可以直接发布任意一个对象类。但内部其实是使用PayloadApplicationEvent类进行包装了一层。这点和guava的eventBus类似。而且,使用@EventListener的condition可以实现更加精细的事件监听,condition支持SpEL表达式,可根据事件源的参数来判断是否监听。

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

@RestController
@RequestMapping("/event")
@Slf4j
public class EventController {
    /**
     * 注入事件发布类
     */
    @Autowired
    ApplicationEventPublisher eventPublisher;

    /**
     * 参数默认注解式@RequestParam
     * @param message
     * @return
     */
    @GetMapping("/msg")
    public String push(@RequestParam("message") String message) {
        log.info("发布applicationEvent事件:{}", message);
        eventPublisher.publishEvent(new DemoEvent(this, message));
        return "事件发布成功!";
    }

    @GetMapping("/obj")
    public String pushObject(String message) {
        log.info("发布对象事件:{}", message);
        eventPublisher.publishEvent(message);
        return "对象事件发布成功!";
    }
}

异步监听处理

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

/**
 * 监听msg为xmr的事件
 */
@Async
@EventListener(condition="#demoEvent.msg == 'xmr'")
public void handleCustomEventByCondition(DemoEvent demoEvent) {
    //监听 CustomEvent事件
    log.info("监听到msg为'xmr'的DemoEvent事件,消息为:{}, 发布时间:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}

关于事务绑定事件

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

ApplicationListener和ContextRefreshedEvent

很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用了其他类(可能是更复杂的关联),所以当我们去使用这个类做事情时发现包空指针错误,这是因为我们这个类有可能已经初始化完成,但是引用的其他类不一定初始化完成,所以发生了空指针错误,解决方案如下:

1、写一个类继承spring的ApplicationListener监听,并监控ContextRefreshedEvent事件(容易初始化完成事件)

ApplicationListener和ContextRefreshedEvent一般都是成对出现的

在IOC的容器的启动过程,当所有的bean都已经处理完成之后,spring ioc容器会有一个发布事件的动作。从 AbstractApplicationContext 的源码中就可以看出

当ioc容器加载处理完相应的bean之后,也给我们提供了一个机会(先有InitializingBean,后有ApplicationListener<ContextRefreshedEvent>),可以去做一些自己想做的事。其实这也就是spring ioc容器给提供的一个扩展的地方。

一个最简单的方式就是,让我们的bean实现ApplicationListener接口,这样当发布事件时,[spring]的ioc容器就会以容器的实例对象作为事件源类,并从中找到事件的监听者,此时ApplicationListener接口实例中的onApplicationEvent(E event)方法就会被调用,我们的逻辑代码就会写在此处。这样我们的目的就达到了

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值