看现象

maven依赖

我们只测试IOC容器,因此只需要引入spring-context即可

    <dependencies>
        <!--测试框架-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!--假数据生成-->
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>1.0.2</version>
        </dependency>

        <!--IOC-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>RELEASE</version>
        </dependency>
        
    </dependencies>
  

测试用例


/**
 * 测试spring的事件发布,参考:
 * https://www.jianshu.com/p/97aed52f1d10
 * https://www.cnblogs.com/wuzhenzhao/p/12859876.html
 * 一般采用线程池处理事件发布,这样避免阻塞主线程
 *
 */
public class TestSpringEvent {

    @Test
    public void test(){
        // create and configure beans
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ioc/event.xml");

        /*当然也可以手动添加事件*/
        //context.addApplicationListener(new TomApplicationListener());
        //context.addApplicationListener(new JerryApplicationListener());

        //广播事件
        context.publishEvent(new MyEvent("我的事件"));
        System.out.println(Thread.currentThread().getName()+" has finish!");

    }
}

测试用例里面引入了配置文件:event.xml,看看里面写了啥

  • event.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--定义事件监听器-->
    <bean name="jerryApplicationListener" class="com.spring.event.JerryApplicationListener"/>
    <bean name="tomApplicationListener" class="com.spring.event.TomApplicationListener"/>
    <bean name="contextRefreshApplicationListener" class="com.spring.event.ContextRefreshApplicationListener"/>

    <!-- 定义一个固定大小的线程,采用factory-method和静态方法的形式,参数注入使用构造函数注入 -->
    <bean name="executor" class="java.util.concurrent.Executors" factory-method="newFixedThreadPool">
        <constructor-arg index="0"><value>5</value></constructor-arg>
    </bean>

    <!-- 定义一个errorHandler,统一处理异常信息 -->
    <bean name="errorHandler" class="com.spring.event.MyEventErrorHandler"></bean>

    <!-- 定义applicationEventMulticaster,注入线程池和errorHandler,此处使用系统自带的广播器,也可以注入其他广播器, -->
    <bean name="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
        <property name="taskExecutor" ref="executor"></property>
        <property name="errorHandler" ref="errorHandler"></property>
    </bean>

</beans>
  • JerryApplicationListener

public class JerryApplicationListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        String name = Thread.currentThread().getName();
        System.out.println("(线程"+name+")我是Jerry监听器,我监听到了事件:" + event);
        throw new RuntimeException("Jerry发出异常...");
    }
}

  • TomApplicationListener

public class TomApplicationListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        String name = Thread.currentThread().getName();
        System.out.println("(线程"+name+")我是Tom监听器,我监听到了事件:" + event);
    }
}

  • MyEvent

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

运行结果

(线程pool-1-thread-1)我是上下文完成refresh的监听器,我监听到了事件:org.springframework.context.event....
main has finish!
(线程pool-1-thread-2)我是Jerry监听器,我监听到了事件:com.spring.event.MyEvent[source=我的事件]
(线程pool-1-thread-2)捕获到了异常:Jerry发出异常...
(线程pool-1-thread-3)我是Tom监听器,我监听到了事件:com.spring.event.MyEvent[source=我的事件]

思考

自定义事件监听器是怎么添加到容器中的呢?为什么发布事件就会被事件监听器调用?为什么会调用自定义的线程池执行?

如果你是设计者,你会怎么来设计事件监听?最简单的就是创建事件监听器,监听指定事件,放到一个Set里面,当有指定事件发生时,调用线程池里面的线程去异步执行事件监听器里面定义好的方法,这就是所谓的观察者模式。

事件监听器怎么添加到容器?

我们是这么定义事件广播器的

<bean name="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
        <property name="taskExecutor" ref="executor"></property>
        <property name="errorHandler" ref="errorHandler"></property>
</bean>

看一眼继承关系

image-20210311101341477

它的父类有一个有一个方法:AbstractApplicationEventMulticaster#addApplicationListener

@Override
	public void addApplicationListener(ApplicationListener<?> listener) {
		synchronized (this.defaultRetriever) {
			// Explicitly remove target for a proxy, if registered already,
			// in order to avoid double invocations of the same listener.
			Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
			if (singletonTarget instanceof ApplicationListener) {
				this.defaultRetriever.applicationListeners.remove(singletonTarget);
			}
             //重点就是这句话,添加监听器到applicationListeners里面
			this.defaultRetriever.applicationListeners.add(listener);
			this.retrieverCache.clear();
		}
	}

看下this.defaultRetriever.applicationListeners是个啥?就是一个Set,和我们预想的一样

public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

在Spring IOC容器初始化的时候就会将我们自己定义的Tom和Jerry事件监听器放入到这个Set里面,那么具体是哪个环节的时候调用的呢?看一眼调用堆栈(DEBUG的时候看线程栈可以看到)

addApplicationListener
applyBeanPostProcessorsAfterInitialization
initializeBean
doCreateBean
createBean
getSingleton
doGetBean
getBean
preInstantiateSingletons
finishBeanFactoryInitialization
refresh

又看到了熟悉的调用堆栈,这不是bean的创建过程么?是的,没错,事件监听器也是一种bean,是一种特殊的bean,ApplicationListenerDetector会检测如果当前bean是事件监听器的话,就将它加入上面的Set里面

//在bean初始化完成之后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean instanceof ApplicationListener) {
        // potentially not detected as a listener by getBeanNamesForType retrieval
        Boolean flag = this.singletonNames.get(beanName);
        if (Boolean.TRUE.equals(flag)) {
            // singleton bean (top-level or inner): register on the fly
		   //重点是这句,如果是事件监听器,则放到对应的Set里面
            this.applicationContext
                .addApplicationListener((ApplicationListener<?>) bean);
        }
        ...
    }
    return bean;
}

为什么发布事件就会被调用?

我们在广播事件之后,发现我们自定义的事件监听器都监听到了该事件

context.publishEvent(new MyEvent("我的事件"));

直接追踪publishEvent方法源码

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");

    // Decorate event as an ApplicationEvent if necessary
    // 强制转换
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent) event;
    }...


        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
    else {
        //重点关注这句,或得事件广播器并且调用它的multicastEvent
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }
}
  • getApplicationEventMulticaster:这个方法很简单,直接返回了我们上文配置的SimpleApplicationEventMulticaster实例(被上下文引用)
/**
* Return the internal ApplicationEventMulticaster used by the context.
* @return the internal ApplicationEventMulticaster (never {@code null})
* @throws IllegalStateException if the context has not been initialized yet
*/
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;
}
  • SimpleApplicationEventMulticaster#multicastEvent
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //获取线程池
    Executor executor = getTaskExecutor();
    //获取我们自定义的事件监听器
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            //有线程池直接用
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //没有线程池的话就自己调用
            invokeListener(listener, event);
        }
    }
}

这个方法就是重点了,getApplicationListeners就是去上文提到的applicationListeners去取事件监听器了,如果有我们自定义线程池就直接用线程池调用,相当于异步调用,否则的话就是当前线程来调用,相当于同步调用!