2021-07-22:Spring IOC 之 事件与监听器源码分析&实战

1、SpringFramework 对于层次性事件的处理策略是什么?是如何实现的?

1.1 分析

SpringFrameword 的层次性事件基于 BeanFactory 的层次性完成,即 BeanFactory 的父子结构;其中 BeanFactory 的 HierarchicalBeanFactory 子接口的 getParentBeanFactory 方法表示获取父Bean工厂。

ApplicationContext 实现了 HierarchicalBeanFactory,则拥有了父子结构;继承了 ApplicationEventPublsher,则拥有了发布事件的能力;而 ApplicationContext 的第一个抽象类 AbstractApplicationContext 组合了 ApplicationEventMulticaster,则拥有了事件派发的能力。

所以我们可以从 AbstractApplicationContext 的源码分析,SpringFramework 对于层次性事件是如何处理的。

上源码:

/**
	 * Publish the given event to all listeners.
	 * @param event the event to publish (may be an {@link ApplicationEvent}
	 * or a payload object to be turned into a {@link PayloadApplicationEvent})
	 * @param eventType the resolved event type, if known
	 * @since 4.2
	 */
	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		//...... 先忽略

		// Publish event via parent context as well...
		// 判断是否有父容器,如果有,通知父容器发布事件
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

所以 SpringFramework 对于层次性事件,是直接判断是否存在父容器,如果存在则调用父容器的 publishEvent() 方法来发布事件;所以如果是子容器发布事件,会通知到父容器;但是如果是父容器发布事件,和子容器就没有一丁点关系了。

1.2 实战

看完源码,还是得来个例子来实战一下。

自定义事件:

public class TestApplicationEvent extends ApplicationEvent {
    /**
     * Create a new {@code ApplicationEvent}.
     * @param source the object on which the event initially occurred or with
     * which the event is associated (never {@code null})
     */
    public TestApplicationEvent(Object source) {
        super(source);
    }
}

自定义监听器:

@AllArgsConstructor
public class TestApplicationListener implements ApplicationListener<TestApplicationEvent> {

    private String name;

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(TestApplicationEvent event) {
        System.out.println(name+" 监听到的事件:"+event.toString());
    }
}

测试应用:

public class HierarchicalEventApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
        AnnotationConfigApplicationContext son = new AnnotationConfigApplicationContext();
        son.setParent(parent);
        parent.addApplicationListener(new TestApplicationListener("父容器监听器"));
        son.addApplicationListener(new TestApplicationListener("子容器监听器"));
        // 需父容器先 refresh 再到子容器
        parent.refresh();
        son.refresh();
        // 父子容器的监听器都接收到
        son.publishEvent(new TestApplicationEvent("子容器发布的事件"));
        // 只有父容器的监听器接收到
        parent.publishEvent(new TestApplicationEvent("父容器发布的事件"));
    }
}

打印结果:

子容器监听器 监听到的事件:com.github.howinfun.demo.ioc.event_listener.TestApplicationEvent[source=子容器发布的事件]
父容器监听器 监听到的事件:com.github.howinfun.demo.ioc.event_listener.TestApplicationEvent[source=子容器发布的事件]
父容器监听器 监听到的事件:com.github.howinfun.demo.ioc.event_listener.TestApplicationEvent[source=父容器发布的事件]

2、payload 事件与普通事件有什么区别?SpringFramework 在底层是如何兼容它的?

2.1 分析&源码

我们看到上面的事件发布例子,可以看到,如果有新增的事件,我们都需要创建自定义类去继承 ApplicationEvent(抽象类),然后接着才能继续编写对应的 ApplicationListener。但是这样会非常的麻烦,如果我们只需关注事件里面的内容就好了,即 source 值。

那么是否能像 JDK 提供的 ArrayList 一样,可以让开发者指定范型,那么就只需要一个 ApplicationEvent 就可以了。

刚才,SpringFramework 在 4.2 版本中提供了:PayloadApplicationEvent<T>。它继承了 ApplicationEvent ,并且实现了 ResolvableTypeProvider 接口,可以提供当前范型的查询方法。

详情看下面源码:

public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

	private final T payload;


	/**
	 * Create a new PayloadApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})
	 * @param payload the payload object (never {@code null})
	 */
	public PayloadApplicationEvent(Object source, T payload) {
		super(source);
		Assert.notNull(payload, "Payload must not be null");
		this.payload = payload;
	}


	@Override
	public ResolvableType getResolvableType() {
		return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getPayload()));
	}

	/**
	 * Return the payload of the event.
	 */
	public T getPayload() {
		return this.payload;
	}

}

我们看完源码再实战一波,最后再看看 payload 事件和普通事件的区别。

2.2 实战例子

先看看 PayloadApplicationEvent 如何使用:

自定义 Listener:

监听内容类型为Integer的事件

public class IntegerPayloadApplicationEventListener implements ApplicationListener<PayloadApplicationEvent<Integer>> {
    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(PayloadApplicationEvent<Integer> event) {
        System.out.println("接收到数字型Payload事件,数字为:"+event.getPayload());
    }
}

应用:

public class Application {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.addApplicationListener(new IntegerPayloadApplicationEventListener());
        applicationContext.refresh();
        // 发布Integer类型的事件内容
        applicationContext.publishEvent(12334);
        applicationContext.publishEvent(56578);
        applicationContext.close();
    }
}

我们可以发现,如果是利用 PayloadApplicationEvent 来做事件,我们发布事件时,只需要直接传入事件内容,而无需再新建一个 PayloadApplicationEvent;只需要保证发布的内容的类型和自定义 ApplicationListener<PayloadApplicationEvent<T>> 监听中的 T 泛型对应上即可。

那 ApplicationEventPublisher 是如何支持 PayloadApplicationEvent 的呢,其实在 AbstractApplicationContext#publishEvent() 方法中就可以看到,只不过我们上面放的源码忽略掉而已,下面将忽略的代码再放出来:

ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
	applicationEvent = (ApplicationEvent) event;
}
else {
    //  这里是兼容 PayloadApplicationEvent 的开始
	applicationEvent = new PayloadApplicationEvent<>(this, event);
	if (eventType == null) {
		eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
	}
}

// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
	this.earlyApplicationEvents.add(applicationEvent);
}
else {
	getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}

我们可以看到,调用publishEvent方法后,会先判断事件是否为 ApplicationEvent 类型,如果不是的话会封装成 PayloadApplicationEvent,所以我们才不需要自己传入 PayloadApplicationEvent。

区别:

  1. 使用 PayloadApplicationEvent,我们不再需要创建很多自定义的 ApplicationEvent;只需要直接发布事件内容
  2. 但还是需要根据事件泛型去创建不同的 ApplicationListener 去监听不同泛型的 PayloadApplicationEvent。

兼容

至于 SpringFramework 底层是如何兼容的,相信看完上面的源码大家就清楚了,这里不再做分析。

3、最后的彩蛋

上面说到,我们使用 PayloadApplicationEvent 时需要指定泛型,那假设我们不指定,会发生什么。下面我们直接走源码分析一下 ApplicationListener 是如何兼容 PayloadApplicationEvent 的,主要分析的是获取适配的所有 ApplicationListener。

入口可以直接去到:org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners

发布事件后,会通过派发器(ApplicationEventMulticaster)去派发事件,派发器会根据当前事件去找到所有匹配的 ApplicatioinListener:

  • 判断本地缓存是否有对应监听器缓存的缓存:key:事件类型+容器类型,value:监听器列表
  • 缓存有,直接返回,否则走查询逻辑
  • 遍历当前容器的所有监听器
  • 利用 supportsEvent 方法判读当前监听器是否支持当前事件

supportEvent底层是使用 org.springframework.core.ResolvableType#isAssignableFrom(org.springframework.core.ResolvableType) 方法来判断,那么我们直接看看这个isAssignableFrom的逻辑。

源码:

/**
	 * Determine whether this {@code ResolvableType} is assignable from the
	 * specified other type.
	 * <p>Attempts to follow the same rules as the Java compiler, considering
	 * whether both the {@link #resolve() resolved} {@code Class} is
	 * {@link Class#isAssignableFrom(Class) assignable from} the given type
	 * as well as whether all {@link #getGenerics() generics} are assignable.
	 * @param other the type to be checked against (as a {@code ResolvableType})
	 * @return {@code true} if the specified other type can be assigned to this
	 * {@code ResolvableType}; {@code false} otherwise
	 */
	public boolean isAssignableFrom(ResolvableType other) {
		return isAssignableFrom(other, null);
	}

我们简单看一下注视即可,我们可以看到,这个方法最终的原理是调用 Class#isAssignableFrom 这个方法。

而这个方法是判断当前类是否为其他类的父类或接口。那么到这我们已经很清楚了。如果 PayloadApplicationEvent 没有指定具体泛型,那么就是 Object,而 Object 是所有类的父类,那么监听器将会接收所有类型的事件(PayloadApplicationEvent<?>)。

实战

下面我们用例子来实战一下

我们基于上面的 IntegerPayloadApplicationEventListener 例子继续造东西就行了。

新增 ApplicationListener:

public class ObjectPayLoadApplicationEventListener implements ApplicationListener<PayloadApplicationEvent> {
    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(PayloadApplicationEvent event) {
        System.out.println("ObjectPayLoadApplicationEventListener 接收到数字型Payload事件,数字为:"+event.getPayload());
    }
}

应用添加上面监听器:

public class Application {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.addApplicationListener(new IntegerPayloadApplicationEventListener());
        applicationContext.addApplicationListener(new ObjectPayLoadApplicationEventListener());
        applicationContext.refresh();

        /**
         * 我们可以发现,如果是利用 PayloadApplicationEvent 来做事件,我们发布事件时,只需要直接传入事件内容,而无需再新建一个 PayloadApplicationEvent
         * 只需要保证发布的内容的类型和自定义ApplicationListener<PayloadApplicationEvent<T>>监听中的 T 对应上即可。
         */
        applicationContext.publishEvent(12334);
        applicationContext.publishEvent(56578);
        applicationContext.close();
    }
}

打印结果:

IntegerPayloadApplicationEventListener 接收到数字型Payload事件,数字为:12334
ObjectPayLoadApplicationEventListener 接收到数字型Payload事件,数字为:12334
IntegerPayloadApplicationEventListener 接收到数字型Payload事件,数字为:56578
ObjectPayLoadApplicationEventListener 接收到数字型Payload事件,数字为:56578

最后我们可以发现,ObjectPayLoadApplicationEventListener 也同时收到了事件。

完结~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值