Spring Boot:Spring Event的初步讲解

Spring提供了Event的功能,只要添加了spring-context依赖就可以引入,简单好用。

要使用Event只要准备三个部分:

  • 事件类:定义事件,继承ApplicationEvent的类成为一个事件类。

  • 发布者:发布事件,通过ApplicationEventPublisher发布事件。

  • 监听者:监听并处理事件,实现ApplicationListener接口或者使用@EventListener注解

举个例子

定义发布者

事件源通常是某个业务类,当它处理完自身的业务逻辑后,向外某个事件发布事件。


//业务类
public void produceData{
  ...
  springContextHolder.publishEvent(new DataEvent("数据产生了"))
  ...
}

定义事件

事件源中发布的事件DateEvent是一个继承了applicationEvent的自定义类


public class DataEvent extends ApplicationEvent{
  String field1;
  public DataEvent(String msg){
    super("DataEvent");
    this.field1=msg;
  }
  	....
}

定义监听器

事件、发布事件的事件源都定义好了,那么由谁来发出事件呢?DataEvent事件的监听器DataEventListener是一个实现了ApplicationListener的类,通过重写onApplicationEvent方法,可以实现监听器接收到相应事件后的处理逻辑。

为了简单起见,这里就直接输出dataEvent事件中携带的msg。

public class DataEventListener implements ApplicationListener<DataEvent>
{
   @Override
    public void onApplicationEvent(DataEvent dataEvent)
    {
     	//这里可以写自己的逻辑
      System.out.print(dataEvent.getField1());
    }
}

测试

//业务类执行produceData方法
produceData();
//控制台会输出
"数据产生了"

原理分析

springContextHolder

springContextHolder.publishEvent向外推送了数据。springContextHolder是自己定义的类,它实现了ApplicationContextAware接口。

public class SpringContextHolder implements ApplicationContextAware {
  private ApplicationContext applicationContext;
  
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)throws BeansException {
        this.applicationContext=applicationContext;
    }

    public ApplicationContext getApplicationContext()
    {
        return  applicationContext;
    }
  
    /**
     * 发布事件
     * @param applicationEvent
     */
    public void publishEvent(ApplicationEvent applicationEvent)
    {
        applicationContext.publishEvent(applicationEvent);
    }
  
  	//这里省略无关代码
}

第一:springContextHolder继承了ApplicationContextAware接口。

第二:springContextHolder也不是真正发送事件的对象,而是委托了applicationContext发布事件。

ApplicationContextAware接口

Spring提供了大量的aware接口,spring的aware接口赋予bean获得spring容器服务的能力。

aware接口

作用

BeanNameAware

可以获取容器中bean的名称

BeanFactoryAware

获取当前bean factory这也可以调用容器的服务

MessageSourceAware

获得message source,这也可以获得文本信息

ResourceLoaderAware

获得资源加载器,可以获得外部资源文件的内容

applicationEventPulisherAware

应用事件发布器,可以发布事件

ApplicationContextAware

这也可以调用容器的服务

如果一个bean想要在程序中获取spring管理的bean,常见做法是:new ClassPathXmlApplication("application.xml").getBean("xxx"),这样相当于重新启动一次spring容器,效率不高。

ApplicationContextAware是spring提供的众多接口中的一个。当一个bean实现ApplicationContextAware接口时,必须重写它的setApplicationContext方法。

bean的实例化时,会设置对象属性,检查aware相关接口并设置依赖。此时就是aware被调用的时候。spring容器会自动调用ApplicationContextAware实现类中的setApplicationContext方法。因此,bean通过setApplicationContext方法可以获得容器的context。下边是bean实例生命周期的执行过程:

  • Spring对bean进行实例化,默认bean是单例;

  • Spring对bean进行依赖注入;

  • 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;

  • 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;

  • 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;

  • 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;

  • 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;

  • 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;

( 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;)

  • 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;

 

SpringContextHolder拿到applicationContext后,调用applicationContext的publishEvent方法发布事件。

ApplicationContext

打开ApplicationContext的源码,我们发现其内部并没有publishEvent方法。经过一番查找,发现Application的publishEvent方法继承自ApplicationEventPublisher。

@FunctionalInterface
public interface ApplicationEventPublisher {
	/*这里定义了default方法,关于default的问题可以看我的另外一篇博文*/
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}
  /*这里定义了publishEvent的接口*/
	void publishEvent(Object event);
}

AbstractApplicationContext

ApplicationEventPublisher的具体实现在抽象类AbstractApplicationContext中,为了方便起见,下文将这个称为“抽象上下文“。

下边是AbstractApplicationContext对publishEvent的具体实现。

 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Publishing event in "+getDisplayName()+": "+event);
		}

		// 将事件装饰成ApplicationEvent
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
			}
		}

		// 广播事件
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType);
		}
  
		// 如果有父容器,则在父容器内也进行广播
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event,eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

可以看出,AbstractApplicationContext中对publishEvent的实现一共分为以下几个步骤。

一、首先将传入的事件装饰成ApplicationEvent,如果本身已经是ApplicationEvent,则无需处理。直接到第二步。

二、这里分为两种情况:

  1. 如果earlyApplicationEvents不为空,那么将当前事件加入earlyApplicationEvents,第二步结束。(下文会说earlyApplicationEvents是什么东西)

  2. 如果earlyApplicationEvents为空,则通过getApplicationEventMulticaster拿到事件广播器,然后将事件广播出去。(这里后文也会详细交代)

三、如果有父容器,比如springMVC有父容器spring等。那么要再父容器内也将此事件进行广播。

事件广播

//大部分时间会走else分支(看下文解释)
if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
			getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType);
	}

earlyApplicationEvents

earlyApplicationEvents是AbstractApplicationContext中定义的一个字段,代码如下所示。

@Nullable
private Set<ApplicationEvent> earlyApplicationEvents;

这里可以看出两点:

  1. earlyApplicationEvents是一个set

  2. @Nullale说明它可以为空

通过查阅代码,知道earlyApplicationEvents会在容器准备启动时进行初始化,具体的初始化过程如下:

protected void prepareRefresh() {
		//省略无关代码
  	....
    //对earlyApplicationEvents进行初始化
		this.earlyApplicationEvents = new LinkedHashSet<>();
	}

earlyApplicationEvents用来存放容器启动后需要发布的事件。它会在容器启动的prepareRefresh环节初始化为一个LinkedHashSet。(如果对spring的初始化流程不熟悉,请参考附录中的内容)

即放在earlyApplicationEvents事件不会立刻发布,而是在容器启动的某一个环节进行发布。在哪一个环节?

protected void registerListeners() {
		// 省略无关代码
		....
		// 发布earlyApplicationEvents中的时间,并且让earlyApplicationEvents为空
		Set<ApplicationEvent> earlyEventsToProcess =this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

earlyApplicationEvents会在容器启动的registerListeners环节进行发布。并且在预置事件发布后,earlyApplicationEvents会被销毁(this.earlyApplicationEvents = null;)

总结一下earlyApplicationEvents。它是一个set,用来存放一些容器启动时需要发布的事件。在earlyApplicationEvents中的事件被发布、容器彻底启动后,它将被置空

因此,再回到这一小节最初,我们看到当容器彻底启动后if (this.earlyApplicationEvents != null) 这个判断肯定是false。即我们自定义事件的发布会走getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType)。

ApplicationEventMulticaster

getApplicationEventMulticaster实际上返回的是一个事件广播器。

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;
	}

同earlyApplicationEvents一样,applicationEventMulticaster也是抽象上下文(AbstractApplicationContext)中的一个属性。它会在spring容器启动的initApplicationEventMulticaster环节进行初始化(spring容器启动流程参见附录)

protected void initApplicationEventMulticaster() {
  //步骤A
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if(beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) 			{
    this.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME,ApplicationEventMulticaster.class);
			//省略无关代码
			}
		}
  //步骤B
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
    beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME,this.applicationEventMulticaster);
				//省略无关代码
		}
	}

对applicationEventMulticaster的初始化分为两种情况

一、当beanFactory中存在名为"ApplicationEventMulticaster"的bean时,直接取beanFactory中的bean对applicationEventMulticaster进行赋值。

二、如果beanFactory中没有对应的bean,则new SimpleApplicationEventMulticaster()赋值给applicationEventMulticaster,并将此类注册至beanFactory中。

总结一下,最终替你自定义类进行事件发布的类是:SimpleApplicationEventMulticaster。

SimpleApplicationEventMulticaster

SimpleApplicationEventMulticaster是替你进行事件发布的最终“幕后帮手”。其核心方法是:multicastEvent

	@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType)
  {
		ResolvableType type = (eventType != null ? eventType :resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener :getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

其中的for循环就是将容器中所有的监听器全部拿出来,挨个执行各个监听器的onApplicationEvent方法,然后对应事件类型的监听器就会进行响应。需要注意的是,当容器中有线程池时,会通过线程池多线程调用onApplicationEvent,这样保证了效率,确保不会因为事件太多而发生阻塞现象。

那么你可能会有疑问,for循环遍历所有的监听器,它怎么知道所有的监听器分布在哪里呢?其中的关键在getApplicationListeners。

getApplicationListeners方法不是SimpleApplicationEventMulticaster的方法,而是继承于其父类AbstractApplicationEventMulticaster的方法

AbstractApplicationEventMulticaster是一个管理监听器的“罐子”,它存储监听器,并且提供了管理监听器的各种方法,当然也包括getApplicationListeners

SimpleApplicationEventMulticaster中有两个属性,Executor taskExecutor和ErrorHandler errorHandler。前者可以定义所有监听器是否异步执行,默认为null,等价于同步执行的SyncTaskExecutor,你也可以使用SimpleAsyncTaskExecutor将所有监听器设置为异步执行。但这里有一点非常重要,如果你设置了Executor为异步的,那么所有的监听器都会异步执行,监听器和调用类会处于不同的上下文,不同的事务中,除非你有办法让TaskExecutor支持。其实我们完全不用通过修改广播器taskExecutor的方式来让监听器异步,可以通过@EnableAsync启动异步,并@Async将监听器设置为异步执行。通过@Async的方式,可以自由地决定任意一个监听器是否为异步,而非暴力地让所有的监听器都异步化。

而ErrorHandler errorHandler则定义了在监听器发生异常之后的行为,在这里你可以看到,如果没有定义errorHandler的话,会直接抛到上一层。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
            // Possibly a lambda-defined listener which we could not resolve the generic event type for
            // -> let's suppress the exception and just log a debug message.
            Log logger = LogFactory.getLog(getClass());
            if (logger.isTraceEnabled()) {
                logger.trace("Non-matching event type for listener: " + listener, ex);
            }
        }
        else {
            throw ex;
        }
    }
}

总结

  1. 在spring中,我们可以通过实现ApplicationContextAware接口,获取spring容器的ApplicationContext实例

  2. ApplicationContext接口中有publishEvent方法,可以发布事件。但是并非直接发布,而是将其委托给其父接口ApplicationEventPublisher的实现类AbstractApplicationContext。

  3. AbstractApplicationContext中对publishEvent方法进行了实现,发布事件的核心方法的原理是:获取spring容器中管理的监听器,然后for循环容器中的listener,对应事件的listener实现类的onApplication方法会被调用,实现对事件的响应。

  4. spring容器的监听器会在容器初始化时被注册进来,放在AbstractApplicationEventMulticaster。AbstractApplicationEventMulticaster提供了众多方法,实现了对listener的管理。

附录

异步

Listener默认都是同步的,publishEvent会执行完所有的同步监听器之后才返回。

上面已经讲到,同步的listener如果发生异常,而且没有被ErrorHandler拦截的话,是会往上抛出的,可以直接在publishEvent方法调用处捕获。

在同步的场景下,listener的执行,其实就是普通方法的调用。那么事务的控制也是和普通的方法调用是一样的。如果想要让监听器在事务中,那么就在监听器方法上添加事务注解@Transational就可以了。具体的分析见Spring的事务传播行为。

既然前面讲到,监听器的执行其实就是一个普通方法的执行,那么将监听器声明为异步的方法也会和将一个普通方法声明为异步的方法一样,使用@Async。

需要明确的一点是,当监听器设置为异步之后,会导致该监听器方法和调用publishEvent的方法处于不同的事务中。其实就和普通异步没有太多区别啦。

使用@Async实现异步

启动异步

@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(1000);
        executor.setKeepAliveSeconds(300);
        executor.setThreadNamePrefix("dspk-Executor-");
        // 拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 异常处理器
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

默认的AsyncConfigurer是AsyncConfigurerSupport,两个方法均返回了null。

设置一个监听器为异步

@Component
public class AccountListener {

    @Async
    @EventListener
    public void sendEmailOnAccountCreatedEvent(AccountCreateEvent event) {
        // 发送邮件
        // do something else
    }
}

使用ApplicationEventMulticaster实现异步

为ApplicationEventMulticaster指定一个异步的taskExecutor,将会让所有的监听器都变成异步执行。真心不推荐这种做法。

public class AsyncConfig {

    @Bean
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

事件事务管理 @TransactionalEventListener

使用@TransactionalEventListener是@EventListener的拓展,可以指定监听器和发布事件的方法的事务隔离级别。隔离级别确保数据的有效性。@TransactionalEventListener注解会将监听器声明为一个事务管理器。

当一个监听器方法被@TransactionalEventListener注解时,那么这个监听器只会在调用方为事务方法时执行,如果调用方是非事务方法,则无法该监听器不会被通知。值得注意的是,虽然@TransactionalEventListener带有Transaction关键字,但这个方法并没有声明监听器为Transactional的。

@TransactionalEventListener
public void afterRegisterSendMail(MessageEvent event) {
    mailService.send(event);
}

配置 @TransactionalEventListener

除了含有与@EventListener相同的属性(classes, condition),该注解还提供了另外的两个属性fallbackExecution和phase。

@TransactionalEventListener注解属性:fallbackExecution

定义:fallbackExecution 设置Listener是否要在没有事务的情况下处理event。

默认为false,表示当publishEvent所在方法没有事务控制时,该监听器不监听事件。通过设置fallbackExecution=true,可以让Listener在任何情况都可以执行。

@Transactional
public void txMethod() {
    publisher.publishEvent(new MessageEvent());
}

public void nonTxMethod() {
    publisher.publishEvent(new MessageEvent());
}

// txMethod: 执行
// nonTxMethod: 不执行
@TransactionalEventListener
public void afterRegisterSendMail(MessageEvent event) {
    mailService.send(event);
}

// txMethod: 执行
// nonTxMethod: 执行
@TransactionalEventListener(fallbackExecution = true)
public void afterRegisterSendMail(MessageEvent event) {
    mailService.send(event);
}

@TransactionalEventListener注解属性:phase

  • AFTER_COMMIT - (default) 在事务完成之后触发事件。此时事务已经结束,监听器方法将找不到上一个事务

  • AFTER_ROLLBACK – 在事务回滚之后触发事件

  • AFTER_COMPLETION – 在事务完成之后触发事件 (含有 AFTER_COMMIT和AFTER_ROLLBACK)

  • BEFORE_COMMIT - 在事务提交之前触发事件,此时调用方方法的事务还存在,监听器方法可以找到该事务

当你尝试在@TransactionEventListener方法中执行JPA的save方法时,你会得到如下错误:

@EventListener
@TransactionalEventListener
public void doOnNormalEvent3(NormalAccountEvent event) {
    Account account = new Account();
    account.setPassword("33333");
    account.setFirstName("333");
    account.setLastName("333");
    account.setEmail("33333@gr.com");

    accountRepository.save(account);
}
Participating transaction failed - marking existing transaction as rollback-only
Setting JPA transaction on EntityManager [SessionImpl(1991443937<open>)] rollback-only
...
org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress

原因是@TransactionalEventListener默认是AFTER_COMMIT,也就是当前事务已经结束了,所以无法找到所在事务,只能执行rollback,因而无法成功将数据保存到数据库中。但如果你希望执行findAll()方法,那么会拿到调用方提交到数据库中的数据,但也拿不到该Listener中save的数据。也许你会想,我在这个方法上@Transactional不就可以了吗?目前的测试结果是也不行。具体原因会在写事务的文章中再另外讲解,这里暂不拓展。可以通过设置phase为TransactionPhase.BEFORE_COMMIT进行解决,这样的话调用方的事务就还没有结束。

@Transactional
@TransactionalEventListener(fallbackExecution = true, phase = TransactionPhase.BEFORE_COMMIT)
public void doOnNormalEvent3(NormalAccountEvent event) {
    ...
}

 

参考资料

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot 中,我们可以通过使用 ApplicationEventPublisher 接口的 publishEvent() 方法来发布事件。默认情况下,该方法是同步执行的,即事件发布后会等待所有监听器处理完事件后才会返回。 如果我们想要异步执行事件处理,可以使用 @Async 注解来实现。具体步骤如下: 1.在配置类上添加 @EnableAsync 注解启用异步执行。 2.在对应的事件监听器方法上添加 @Async 注解。 这样,当事件发布时,对应的监听器方法会在一个新线程中异步执行,从而不会阻塞主线程的执行。 示例代码如下: ```java @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(100); executor.setQueueCapacity(10); executor.setThreadNamePrefix("AsyncThread-"); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new SimpleAsyncUncaughtExceptionHandler(); } } @Component public class MyEventListener { @Async @EventListener public void handleEvent(MyEvent event) { // 处理事件 } } @Component public class MyEventPublisher { @Autowired private ApplicationEventPublisher publisher; public void publishEvent(MyEvent event) { publisher.publishEvent(event); } } ``` 在上面的示例代码中,我们先定义了一个 AsyncConfig 配置类,通过实现 AsyncConfigurer 接口来配置线程池等参数。然后在 MyEventListener 类中,我们使用 @Async 注解来标识 handleEvent() 方法是一个异步方法。最后,在 MyEventPublisher 类中,我们调用 ApplicationEventPublisher 的 publishEvent() 方法来发布事件。 这样,当我们调用 MyEventPublisher 的 publishEvent() 方法时,MyEventListener 中对应的 handleEvent() 方法就会在一个新线程中异步执行,不会阻塞主线程的执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值