报错信息如下:

Caused by: java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: Root WebApplicationContext: startup date [Sat Dec 14 15:02:30 CST 2019]; root of context hierarchy
	at org.springframework.context.support.AbstractApplicationContext.getApplicationEventMulticaster(AbstractApplicationContext.java:344)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:331)
	at wang.miansen.roothub.common.dao.jdbc.spring.DataSourceConfiguration.afterPropertiesSet(DataSourceConfiguration.java:163)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
	... 132 more

抛异常的地方:

可以看到 applicationEventMulticaster 为 null 就抛出了异常。

代码如下

package wang.miansen.roothub.common.dao.jdbc.spring;

public class DataSourceConfiguration implements FactoryBean<DataSource>, ApplicationContextAware, InitializingBean {

	private static final Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);

	/**
	 * 数据库基本配置
	 */
	private DataSourceProperties dataSourceProperties;

	/**
	 * 数据源初始化器
	 */
	private DataSourceInitializer dataSourceInitializer;

	/**
	 * 数据源
	 */
	private DataSource dataSource;

	/**
	 * 上下文容器,主要的作用是防止重复初始化数据源。
	 */
	private ApplicationContext applicationContext;

	/**
	 * 创建数据源
	 * @param type
	 * @return
	 */
	@SuppressWarnings("unchecked")
	protected <T> T createDataSource(Class<? extends DataSource> type) {
		return (T) dataSourceProperties.initializeDataSourceBuilder().type(type).build();

	}

    /**
     * 最终注入的数据源
     */
	@Override
	public DataSource getObject() throws Exception {
		return null;
	}

	@Override
	public Class<DataSource> getObjectType() {
		return DataSource.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	
	public void setDataSourceProperties(DataSourceProperties dataSourceProperties) {
		this.dataSourceProperties = dataSourceProperties;
	}

	public DataSourceInitializer getDataSourceInitializer() {
		if (this.dataSourceInitializer == null) {
			this.dataSourceInitializer = new DataSourceInitializer(this.dataSource, this.dataSourceProperties);
		}
		return this.dataSourceInitializer;
	}

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

	@Override
	public void afterPropertiesSet() throws Exception {
		// 只存在父容器时才初始化数据源,防止重复初始化。
		if (this.applicationContext.getParent() == null) {
			DataSourceInitializer initializer = getDataSourceInitializer();
			this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer));
		}
	}

}

DataSourceConfiguration 类实现了 ApplicationContextAware 和 InitializingBean 接口,本来想在 afterPropertiesSet 方法里发布初始化数据源的事件,结果就报了上面那个错误。

分析一下原因:

在 afterPropertiesSet() 方法中发布事件时,ApplicationContext 的 applicationEventMulticaster 属性为 null,导致抛出异常

跟一下 ApplicationContext 初始化过程看看为什么 applicationEventMulticaster 会是 null。

Spring 中 AbstractApplicationContext 抽象类的 refresh() 方法是用来刷新 Spring 的应用上下文的,所以在 refresh() 方法处打个断点。

afterPropertiesSet() 方法也打上一个断点,然后启动项目。

进入了 refresh() 方法

image

其它的方法先不看,重点是 registerBeanPostProcessors(beanFactory) 方法。

这个方法是用来注册 BeanPostProcessor 的,需要在所有的 application bean 初始化之前调用,拦截 Bean 的创建。

进去看看

调用了一个静态方法,继续往下走。

这里是找出 BeanPostProcessor 接口的所有实现类。就不仔细看了,直接看这里:

可以看到这里调用了 beanFactory.getBean() 方法,触发了 bean 实例的创建。从而触发 setApplicationContext() 和 afterPropertiesSet() 方法。

但是初始化事件广播器的方法 initApplicationEventMulticaster() 是在 registerBeanPostProcessors() 方法之后。

所以导致在 afterPropertiesSet() 方法中发布事件时,ApplicationContext 的 applicationEventMulticaster 属性为 null,导致抛出异常。

解决方法:

既然执行 afterPropertiesSet() 方法时,ApplicationContext 的 applicationEventMulticaster 属性还没准备好,那么可以等 initApplicationEventMulticaster() 方法执行完之后才能发布事件了。

继续往下看,注册监听器的方法是 registerListeners();

那么可以换一种思路,当监听器注册完毕之后在发布事件,因为注册监听器的方法是在初始化事件广播器之后,可以保证在发布事件的时候事件广播器是可用的。

可以这么做,新建一个类,实现 BeanPostProcessor 和 ApplicationContextAware 接口,在 postProcessAfterInitialization() 方法里判断 bean 的类型是否为 DataSourceInitializerListener,如果是就发布事件,具体代码如下:

public class DataSourceInitializedPublisher implements BeanPostProcessor, ApplicationContextAware {

	/**
	 * 上下文容器,可以使用该对象来发布事件。
	 */
	private ApplicationContext applicationContext;

	/**
	 * 数据源
	 */
	private DataSource dataSource;

	/**
	 * 数据库基本配置
	 */
	private DataSourceProperties dataSourceProperties;

	/**
	 * 数据源初始化器
	 */
	private DataSourceInitializer dataSourceInitializer;
	
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		// 当事件监听器初始化好之后发布初始化数据源的事件。
		if (bean instanceof DataSourceInitializerListener) {
			// 只存在父容器时才发布,防止重复初始化。
			if (this.applicationContext.getParent() == null) {
				DataSourceInitializer initializer = getDataSourceInitializer();
				this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer));
			}
		}
		return bean;
	}

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

	/**
	 * 实例化 DataSourceInitializer
	 * @return DataSourceInitializer
	 */
	public DataSourceInitializer getDataSourceInitializer() {
		if (this.dataSourceInitializer == null) {
			this.dataSourceInitializer = new DataSourceInitializer(this.dataSource, this.dataSourceProperties);
		}
		return this.dataSourceInitializer;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public void setDataSourceProperties(DataSourceProperties dataSourceProperties) {
		this.dataSourceProperties = dataSourceProperties;
	}

}

xml 配置:

<!-- 数据源初始化事件监听器 -->
<bean id="dataSourceInitializerListener" class="wang.miansen.roothub.common.dao.jdbc.spring.DataSourceInitializerListener" />
	
<!-- 发布初始化数据源事件 -->
<bean id="dataSourceInitializedPublisher" class="wang.miansen.roothub.common.dao.jdbc.spring.DataSourceInitializedPublisher" >
		<property name="dataSourceProperties" ref="dataSourceProperties" />
		<property name="dataSource" ref="dataSource" />
</bean>

原文链接:https://miansen.wang/2019/12/16/ApplicationEventMulticaster-not-initialized/