听到监听这个词,不难理解,一个事物根据另一个事物的变化自发的作出响应,而且每次都作出同样的响应。就像点击按钮一样。每次点击登入按钮,都会访问登入接口url,这就是监听。
那么监听需要哪些条件呢。三要素,1.事件2.监听器3.触发动作。点击按钮就是事件,点击之后要怎么处理,就是监听器的事了。那么问题来了,监听器肯定有很多个,每个监听器职责不一样,它们怎么知道要监听哪个事件的。当然是事先就告诉它们了。也就是说在发布事件的时候,所有监听器就已经准备就绪,然后根据事件类型匹配对应监听器。
事实上,spring就是这么干的。
有几个问题:
1.spring的监听器是怎么注册的?在何时注册的?
2.这些事件是如何发布的?
3.事件是怎么找到对应监听器的?
带着这几个问题往下看。。。
先看最简单的一种实现方式。
1.创建一个监听器,实现ApplicationListener接口,泛型中指定事件类型
public class PrintListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("调用DemoEvent的print方法输出其内容:");
event.print();
}
}
2.创建一个事件,继承ApplicationEvent抽象类
public class DemoEvent extends ApplicationEvent {
private String text;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public DemoEvent(Object source) {
super(source);
}
public DemoEvent(Object source, String text) {
super(source);
this.text = text;
}
public void print() {
System.out.println("print event content:" + this.text);
}
}
3.注册监听器到容器中,发布事件。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//注册监听器
context.addApplicationListener(new PrintListener());
//发布事件
context.publishEvent(new DemoEvent(new Object(),"hello world."));
}
}
接下来到源码中去看下。
我们在执行scontext.addApplicationListener的时候,最终走到了一个处理事件的广播类。如下。其实就是把所有的listener加到ListenerRetriever类下的Set集合applicationListeners
public abstract class AbstractApplicationEventMulticaster
implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
@Nullable
private ClassLoader beanClassLoader;
@Nullable
private BeanFactory beanFactory;
private Object retrievalMutex = this.defaultRetriever;
/***************************省略一堆代码*************************/
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) {
// 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);
}
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}
是不是第一个问题就已经解决了。
我们来看第二个问题和第三个问题,在执行context.publishEvent之后同样走到了AbstractApplicationEventMulticaster类下,你会发现它从我们之前的容器中取出了目前已经注册的所有监听器。也就是上面提到的ListenerRetriever类下的Set集合applicationListeners,然后遍历所有监听器,一个个判断和当前事件是否匹配。
/**
* Actually retrieve the application listeners for the given event and source type.
* @param eventType the event type
* @param sourceType the event source type
* @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
* @return the pre-filtered list of application listeners for the given event and source type
*/
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.retrievalMutex) {
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
}
for (ApplicationListener<?> listener : listeners) {
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
allListeners.add(listener);
}
}
/***************************省略,只关注主要的*************************/
注意这里有个supportsEvent方法,匹配对应监听器就是在这里做的。
protected boolean supportsEvent(
ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}
注意这里在new GenericApplicationListenerAdapter(listener),还记得我们上面自定义的监听器PrintListener吗,把它传进去,目的就是获取泛型实际类型信息,也就是具体监听的事件类型。获取泛型用的是Spring4自带的工具类ResolvableType,这里不作过多描述,感兴趣的自己去了解。
但是,这是比较简单一种使用方式。通常我们不会这么做。
为什么呢?带着这个问题往下看。
我们一般使用@EventListener注解,如图,创建一个事件处理类,并交给spring容器管理,在要监听事件处理的方法上加@EventListener注解即可。
@Component
public class DemoEventHandler {
@EventListener
public void handle(DemoEvent event){
System.out.println("调用MsgEvent的print方法输出其内容handler:");
event.print();
}
}
我们知道有一个EventListenerMethodProcessor类,这个类是application启动时自动注册执行的。该类的功能是扫描@EventListener注解并生成一个ApplicationListener实例。
其中有个这样的方法:就是用来扫描容器中bean的方法上所有的@EventListener,循环创建ApplicationListener。
protected void processBean(
final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
if (!this.nonAnnotatedClasses.contains(targetType)) {
Map<Method, EventListener> annotatedMethods = null;
try {
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
}
}
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
}
else {
// Non-empty set of methods
ConfigurableApplicationContext context = getApplicationContext();
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
context.addApplicationListener(applicationListener);
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
beanName + "': " + annotatedMethods);
}
}
}
}
这样做有什么好处你应该知道了。如果要监听多个事件,你大可不必针对每个事件都写一个监听类。并且,要在多处监听同一个事件。这种方式灵活方便的多
优化完了监听的实现方式,同样可以优化发布事件。我们上面讲的发布事件是在项目启动的时候做的。那如果是在代码里,例如我们经常用到的异步事件编程。假设在我们在登入网站后,要经过一些与登入无关的操作,比如记录登入时间,地点信息。
在要发时间的地方实现ApplicationContextAware接口,获得ApplicationContext实例,通过context发事件。因为ApplicationContext实现了ApplicationEventPublisher接口,所以它有发事件的功能。
@Service("userService")
public class UserServiceImpl implements UserService,ApplicationContextAware{
private ApplicationContext applicationContext;
@Override
public void login(User user) {
//TODO 登入校验操作。。。。
publish(new LoginEvent(new Object(),"我要登入了"));
}
public void publish(LoginEvent loginEvent){
applicationContext.publishEvent(loginEvent);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
@Component
public class UserEventHandler {
@EventListener
public void handleLoginEvent(LoginEvent loginEvent){
System.out.println("监听登入成功");
//TODO 调用统计接口记录时间,地点
}
}
...
阅读本文最重要的不是掌握怎么使用,我们应该去理解这种思想。CQRS架构中,读写分离中的写就是发布一个个命令来达到解耦的目的。消息机制中的mq,redis的发布订阅模式,webscoket的广播等等都是类似。同时,通过阅读源码我们也学会了如何获取标示该注解的所有方法,还有在初始化加载的时候,很多数据不是每次都要获取一遍,可以用map作缓存。
最后,提醒一下,不要看到事件就是异步的,要异步可以在处理方法上直接加@Async注解,如果同一个事件的监听器非常多。可以用线程池方式处理(SimpleApplicationEventMulticaster类下有个Executor变量,在广播事件方法multicastEvent中使用)