Spring事件机制

Spring上下文启动的时候将实现ApplicationListener接口的Bean添加到事件监听者列表中,每次使用ApplicationEventPublisher发布ApplicationEvent时,都会通知对该事件感兴趣(监听该事件)的Bean。

ApplicationContext继承关系图

ApplicationContext继承了ApplicationEventPublisher接口,从而拥有事件发布的能力。但是实际ApplicationContext事件发布委托给ApplicationEventMulticaster执行。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    // ...省略
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}
    // ...省略
}

相关关键类或注解

  • ApplicationEventPublisher:发布事件
  • ApplicationListener:事件监听者
  • ApplicationEvent:事件
  • EventListener:事件和事件监听绑定
  • ApplicationEventMulticaster:发布事件

Spring容器在启动的过程中也会发布各种事件,相应的组件监听到之后完成各自的初始化工作,下面是Spring内置的事件。

  • ContextStartedEvent:Spring上下文启动事件
  • ContextRefreshedEvent:Spring上下文初始化或刷新事件
  • ContextStoppedEvent:Spring上下文停止事件
  • ContextClosedEvent:Spring上下文关闭事件

一、基于ApplicationListener实现事件监听

  1. 首先定一个事件,事件一定要继承ApplicationEvent
public class DemoEvent extends ApplicationEvent {
    public DemoEvent(Object source) {
        super(source);
    }
}
  1. 然后定义一个类实现ApplicationListener接口
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {

    @Override
    public void onApplicationEvent(DemoEvent event) {
        System.out.println("收到消息: " + event);
    }
}
  1. 发布事件
applicationContext.publishEvent(new DemoEvent(new Object()));
// 收到消息: com.example.demo.applicationevent.DemoEvent[source=java.lang.Object@3cb173db]

虽然完成了事件监听,但是这种实现方案有点不太好,监听类必须实现特定的ApplicationListener接口,事件也必须继承ApplicationEvent类且一个类只能处理一个事件。接下来介绍第二种方式可以完全避免这种问题。

二、基于@EventListener实现事件监听

public class DemoEvent2 {
}

@Component
public class DemoListener2 {

    @EventListener(DemoEvent2.class)
    public void onDemoEvent(DemoEvent2 demoEvent2) {
        System.out.println("@EventListener: " + demoEvent2);
    }
}

// 输出日志
2020-04-28 23:52:59.187  INFO 4425 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
2020-04-28 23:52:59.187  INFO 4425 --- [           main] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@75d7297d

基于@EventListener的监听类不需要实现ApplicationListener,事件也不需要继承ApplicationEvent类,且可以在类里面声明多个方法处理不同的事件。实际开发中更倾向于这种实现方案。

三、异步监听-@Async

从上面的输出日志中可以看出,事件发布和监听是处在同一个线程中,有时候我们可能需要实现异步监听,可以借助@Async和自定义ApplicationEventMulticaster两种方式实现消息的异步监听。

  1. @Async

修改代码如下

@Slf4j
@Component
@EnableAsync
public class DemoListener2 {

    @Async
    @EventListener(DemoEvent2.class)
    public void onDemoEvent(DemoEvent2 demoEvent2) {
        log.info("@EventListener: " + demoEvent2);
    }
}

// 输出日志
2020-04-28 23:56:30.252  INFO 4508 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
2020-04-28 23:56:30.310  INFO 4508 --- [         task-1] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@184bc84

添加注解启用异步,并在指定方法上面添加@Async注解。从日志中可以看出事件发布和监听已经处在两个不同的线程中。

  1. 自定义ApplicationEventMulticaster
@Slf4j
@Component
//@EnableAsync
public class DemoListener2 {

//    @Async
    @EventListener(DemoEvent2.class)
    public void onDemoEvent(DemoEvent2 demoEvent2) {
        log.info("@EventListener: " + demoEvent2);
    }

    @Bean("applicationEventMulticaster")
    public ApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor("async-"));
        // 异常处理
        eventMulticaster.setErrorHandler(new ErrorHandler() {
            @Override
            public void handleError(Throwable t) {
                log.error("事件监听异常", t);
            }
        });
        return eventMulticaster;
    }

}

// 输出日志
2020-04-29 00:01:27.733  INFO 4624 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
2020-04-29 00:01:27.735  INFO 4624 --- [       async-16] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@56051dd7

从日志中可以看出,通过自定义ApplicationEventMulticaster方式也同样完成事件发布和监听处在两个不同的线程。

为什么声明一个这样的Bean就可以完成异步呢?

在开始的时候提到过ApplicationContext将事件发布委托给ApplicationEventMulticaster执行,接下来通过源码看ApplicationContext如何委托事件给ApplicationEventMulticaster。

AbstractApplicationContext获取ApplicationEventMulticaster对象

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
	if (this.applicationEventMulticaster == null) {
		throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
				"call 'refresh' before multicasting events via the context: " + this);
	}
	return this.applicationEventMulticaster;
}

接下来看AbstractApplicationContext如何初始化applicationEventMulticaster对象

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		try {
            // 省略无关代码
			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();		
		}
		catch (BeansException ex) {
		}
	}
}

可以看出来在调用refresh的时候会初始化applicationEventMulticaster

public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
		if (logger.isTraceEnabled()) {
			logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
					"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
		}
	}
}

初始化的是首先会尝试从Spring获取指定name的Bean,如果没有获取到,则新建一个实例,并注册到IoC容器中,到这里就明白为什么声明一个Bean之后就完成了异步的操作。因为我们提前声明了一个applicationEventMulticaster Bean对象,所以Spring会把这个对象当成默认的事件发布工具。自定义对象指定了线程池,所以事件发布和监听会处在不同的线程池中。

这种做法会导致由该对象发布的所有事件都是异步处理,实际开发过程中推荐使用@Async注解实现异步监听逻辑,这样可以针对性对指定事件监听异步处理。

原文地址:https://www.cnblogs.com/thisismartin/p/12799958.html