Spring编程常见错误–Spring Core篇-07 |Spring事件常见错误

七、Spring事件常见错误

一.试图处理并不会抛出的事件(并不是所有的事件都执行了start方法,只有执行了start方法的事件,才可以被监听到)

Spring的事件模型
在这里插入图片描述

1、Spring 事件的三大组件

1.事件(Event)

用来区分和定义不同的事件,在 Spring 中,常见的如 ApplicationEvent 和 AutoConfigurationImportEvent,它们都继承于 java.util.EventObject。

2.事件广播器(Multicaster)

负责发布上述定义的事件。例如,负责发布 ApplicationEvent 的 ApplicationEventMulticaster 就是 Spring 中一种常见的广播器。

3.事件监听器(Listener)

负责监听和处理广播器发出的事件,例如 ApplicationListener 就是用来处理 ApplicationEventMulticaster 发布的 ApplicationEvent,它继承于 JDK 的 EventListene。

2、代码

@Slf4j
@Component
public class MyContextStartedEventListener implements ApplicationListener<ContextStartedEvent> {

    public void onApplicationEvent(final ContextStartedEvent event) {
        log.info("{} received: {}", this.toString(), event);
    }

}

这段代码定义了一个监听器 MyContextStartedEventListener,试图拦截 ContextStartedEvent。
ContextStartedEvent的抛出只发生在一处,位于方法AbstractApplicationContext.start()之中

	@Override
	public void start() {
		getLifecycleProcessor().start();
		publishEvent(new ContextStartedEvent(this));
	}

也就是说,只有上述方法被调用,才会抛出 ContextStartedEvent。

Spring 启动方法中围绕 Context 的关键方法调用,代码如下:

public ConfigurableApplicationContext run(String... args) {
//省略非关键代码
		ConfigurableApplicationContext context = null;
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);此处知识进行了刷新操作
			afterRefresh(context, applicationArguments);
//省略非关键代码

Spring 启动最终调用的是 AbstractApplicationContext.refresh,并不AbstractApplicationContext.start。在这样的残酷现实下,ContextStartedEvent 自然不会被抛出,不抛出,自然也不可能被捕获。所以这样的错误也就自然发生了。

3、问题修正

1. 误读事件(监听错了事件)

误以为,想要监听的事件是自己想要的事件,比如关于Context,关于context的真是调用其实是刷新操作,因此我们真正需要调用的不是start,而是refresh.


@Component
public class MyContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {

  public void onApplicationEvent(final ContextRefreshedEvent event) {
    log.info("{} received: {}", this.toString(), event);
  }

}

监听 ContextRefreshedEvent 而非 ContextStartedEvent。ContextRefreshedEvent 的抛出可以参考方法 AbstractApplicationContext.finishRefresh,它本身正好是 Refresh 操作中的一步。


protected void finishRefresh() {
   //省略非关键代码
   initLifecycleProcessor();
   // Propagate refresh to lifecycle processor first.
   getLifecycleProcessor().onRefresh();
   // Publish the final event.
   publishEvent(new ContextRefreshedEvent(this));
   //省略非关键代码
}
2. 需要处理该事件(就是要监听这个是事件,哪怕是进行手动的调用)

这种情况下,我们真的需要去调用 AbstractApplicationContext#start 方法。

@RestController
public class HelloWorldController {
    @Autowired
    private AbstractApplicationContext applicationContext;
    @RequestMapping(path = "publishEvent", method = RequestMethod.GET)
    public String notifyEvent(){
        applicationContext.start();       
        return "ok";
    };
}

当一个事件拦截不了时,我们第一个要查的是拦截的事件类型对不对,执行的代码能不能抛出它。

二.监听事件的体系不对

1、代码:处理ApplicationEnvironmentPreparedEvent 事件

@Slf4j
@Component
public class MyApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent > {

    public void onApplicationEvent(final ApplicationEnvironmentPreparedEvent event) {
        log.info("{} received: {}", this.toString(), event);
    }
}

经过案例一的经验,首先看这个事件的抛出有没有问题。如果抛出有问题,那么必然是无法监听到的。

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
   this.initialMulticaster
         .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

2、案例分析(定义的监听器并不能监听到 initialMulticaster 广播出的 ApplicationEnvironmentPreparedEvent)

这是在 Spring 事件处理上非常容易犯的一个错误,即监听的体系不一致。

关于 ApplicationEnvironmentPreparedEvent 的处理,它相关的两大组件是什么?

1.广播器

这个事件的广播器是 EventPublishingRunListener 的 initialMulticaster,代码参考如下:


public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
   //省略非关键代码
   private final SimpleApplicationEventMulticaster initialMulticaster;

   public EventPublishingRunListener(SpringApplication application, String[] args) {
      //省略非关键代码
      this.initialMulticaster = new SimpleApplicationEventMulticaster();
      for (ApplicationListener<?> listener : application.getListeners()) {
         this.initialMulticaster.addApplicationListener(listener);
      }
   }
 }
2.监听器
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

个事件的监听器就存储在 SpringApplication#Listeners 中,调试下就可以找出所有的监听器,但是自定义的监听器并不存在在其中。

3、问题修正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值