Event事件机制

Spring事件机制基于观察者模式实现了事件发布和监听的功能,Spring有一些框架内部的事件,我们也可以自定义事件。使用事件机制的好处是能够进一步解耦业务逻辑,实现低耦合、无侵入式的业务逻辑处理,也可以实现异步处理等高级功能。

基本概念

事件:Spring中的事件ApplicationEvent继承JDK的java.util.EventObject抽象类,该类包含了一个source属性保存了事件源对象的引用,以及时间发生的时间戳,Spring内置的事件和我们自定义的事件需要继承ApplicationEvent这个类来实现。

事件发布者:用于发布事件的接口,Spring框架中我们可以调用applicationContext.publishEvent()发布事件。

事件监听器:事件监听器可以使用@EventListener注解或实现ApplicationListener接口来定义,注意它必须是一个Spring的Bean,否则Spring框架是无法找到指定的监听器对象的。

事件机制的使用

监听Spring内置事件

Spring内置的事件和发布者都是Spring实现的,因此我们编写一个事件监听器即可。

package com.gacfox.demo.demoboot;

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

@Component
public class DemoComponent {
    @EventListener
    public void onStarted(ApplicationStartedEvent event) {
        System.out.println("Spring启动完成");
    }
}

代码中我们使用了@EventListener注解标注了一个方法,Spring会根据方法的参数类型,在对应事件发生时回调方法。如果参数类型使用ApplicationEvent,则所有类型的事件该方法都会接收到。

此外要注意,如果有多个方法监听同一个事件,事件发生时所有方法都会被回调,不会出现只有一个方法被调用的情况。

Spring中常用的内置事件有如下几种:

事件类型 说明
ContextRefreshedEvent 当Spring容器初始化或刷新时触发,即applicationContext的refresh()方法被调用后。
ContextStartedEvent 当Spring容器停止时触发,即applicationContext的start()方法被调用后。
ContextStoppedEvent 当Spring容器关闭时触发,即applicationContext的stop()方法被调用后。
ContextClosedEvent 当Spring容器启动时触发,即applicationContext的close()方法被调用后。
RequestHandledEvent 用于SpringMVC环境,当DispatcherServlet处理请求完成后触发。

自定义事件

自定义事件和使用内置事件类似,只不过需要我们手动创建EventObject和发射事件,下面是一个自定义事件的例子。

RegistrationEvent.java

package com.gacfox.demo.demoboot;

import org.springframework.context.ApplicationEvent;

public class RegistrationEvent extends ApplicationEvent {
    public RegistrationEvent(Object source) {
        super(source);
    }
}

DemoComponent.java

package com.gacfox.demo.demoboot;

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

@Component
public class DemoComponent {
    @EventListener
    public void onRegistration(RegistrationEvent event) {
        System.out.println("用户注册了");
    }
}

IndexController.java

package com.gacfox.demo.demoboot;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @GetMapping("/")
    public void register() {
        applicationContext.publishEvent(new RegistrationEvent(this));
    }
}

代码中,RegistrationEvent是我们自定义的事件,它继承了ApplicationEventDemoComponent是我们的事件监听器,最后IndexController是事件是发射器,该类实现了ApplicationContextAware接口用来获取applicationContext对象,在控制器方法register()被触发时就会通过applicationContext对象发射事件,并由DemoComponentonRegistration()方法接收。

异步事件

上面介绍的自定义事件监听例子其实还是一个同步式的代码,如果我们的EventListener处理事件的耗时较长,我们的IndexController也必须等待它处理完成后才能返回。实际上,Spring的事件机制也支持异步方式进行处理。

以SpringBoot为例,使用异步事件首先我们需要在启动类上标注@EnableAsync开启异步支持,此时在我们的EventListener方法上标注@Async,它就可以异步执行了。

@Async
@EventListener
public void onRegistration(RegistrationEvent event) {
    // ...
}

我们可以通过打印线程ID和线程名来验证是否实现了异步执行,如果异步执行,事件发布的线程和处理事件的线程应该并非同一个。

注意:由于Java的ServletAPI从设计之初就采用的是多线程并发模型,我们对请求进行的处理都是串行的,发生在同一个线程中,因此许多API都是依赖ThreadLocal构建的,比如SpringMVC的RequestContextHolder、日志框架的MDC等。如果引入异步处理这些API可能就会失效,我们需要考虑到这些潜在的问题。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。