Spring 学习笔记Spring-BootJava学习笔记

Spring Event事件通知机制 源码学习

2018-03-24  本文已影响164人  jwfy

笔记简述
本学习笔记主要是介绍Spring中的事件通知是如何实现的,同步和异步事件通知的用法和实现细节以及Spring提供的常见的Event,如果实际开发中需要根据事件推送完成相应的功能,该如何选择Event
Spring更多可查看Spring 源码学习

目录

Spring Event事件通知机制 源码学习
1、监听者模式
2、DEMO(同步)
3、Spring实现细节
4、Spring Event
4.1 ContextRefreshedEvent
4.2 ServletRequestHandledEvent
5、异步Pushlish以及DEMO

1、监听者模式

学习spring的事件通知机制肯定要先了解监听者模式(监听者模式和观察者模式有什么区别?

监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法

观察者模式是一对多的模式,一个被观察者Observable和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,就会遍历广播推算消息给所有的观察者
例如日常生活中的订阅报纸,报纸老板A,现在小明和老板打招呼说我要订报纸(这个过程就相当于观察者的注册),老板A就会拿出自己的小本本记下小明,下次小王、小芳也是类似的操作,现在老板A就有了三个观察者了,然后老板会自动的把报纸送到三位的家里,突然有一天小明说不想订报纸了,老板就在自己的小本本上划掉小明的名字(观察者的取消注册),等到送报纸的时候就不再送到小明家里。

2、DEMO(同步)

事件定义

public class EventDemo extends ApplicationEvent {
    private String message;
    public EventDemo(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

事件监听者

@Component
public class EventDemoListern implements ApplicationListener<EventDemo> {

    @Override
    public void onApplicationEvent(EventDemo event) {
        System.out.println("receiver " + event.getMessage());
    }
}

事件发布

@Component
public class EventDemoPublish {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(String message){
        EventDemo demo = new EventDemo(this, message);
        applicationEventPublisher.publishEvent(demo);
    }

}

最后获取到EventDemoPublish这个bean,直接publish就可以完成操作了。

3、Spring实现细节

在spring中由于类的细节太多,参数方法也非常的多,不太建议通篇的一个一个看,优先关注我们关注的点,然后打断点的方式具体了解需要的参数和必备的方法等

AbstractApplicationContext类就有publishEvent()方法,先分析下。

protected void publishEvent(Object event, ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }

    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
           // 如果是ApplicationEvent对象
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        applicationEvent = new PayloadApplicationEvent<Object>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
        }
        // 否则就包装为PayloadApplicationEvent时间,并获取对应的事件类型
    }

    if (this.earlyApplicationEvents != null) {
           //初始化时候的事件容器,默认为null的
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        //  监听者容器?大概的就这个意思,推送消息给监听器
    }

    // 如果当前命名空间还有父亲节点,也需要给父亲推送该消息
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

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;
}

通过分析,又回到了refresh这个核心函数中

image
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

// 初始化监听器容器
protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
           // 手动实现了名称为applicationEventMulticaster的bean
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        // 还必须得是ApplicationEventMulticaster类
        if (logger.isDebugEnabled()) {
            logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
        }
    }
    else {
           // 否则就默认自定义一个名称为SimpleApplicationEventMulticaster的监听器容器
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
                    APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                    "': using default [" + this.applicationEventMulticaster + "]");
        }
    }
}

private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
// 默认是一个空列表,而不是null

// 注册监听器,并且加入到监听器容器中
protected void registerListeners() {
    // Register statically specified listeners first.
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
        // 把提前存储好的监听器添加到监听器容器中
    }

    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    // 获取类型是ApplicationListener的beanName集合,此处不会去实例化bean
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    // 初始化事件为null
    if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

这样就很清楚了,在spring进行refresh的时候就完成了监听器容器和监听器的初始化工作(可以很方便的注册自己需要的监听器或者自定义的监听器容器对象),只需要获取到容器就可以直接publish事件了。

4、Spring Event

前面已经说了监听器一般和具体的某一个事件绑定的(这点就和观察者模式非常不一样了),那么就来看看spring已经帮我们实现了哪些Event,具体如下图

image

下面就ContextRefreshedEvent和ServletRequestHandledEvent具体分析下如何使用以及其实现的原理

4.1 ContextRefreshedEvent

image

在refresh结束之后推送的一个事件,这个时候大部分bean的实例化已经完成了,并且传递了this这个数据,那么如果有需要的话,可以在这个地方实现对Spring上下文数据的统计或者监控,简直不能爽歪歪,但是个人开发目前还没遇到具体的使用场景

4.2 ServletRequestHandledEvent

Spring MVC 中的事件,直接跳到DispatcherServlet类,后来到了FrameworkServlet 抽象类

image

如果可以推送事件的话,则利用web的上下文推送事件,其中包含了一个请求的基本信息,如果需要定制化统计整个HTTP请求的情况,完全可以通过这个事件推送实现

不过这里有一点需要注意到,还有个publishEvents字段,如果需要使用,需要设置为true,如下图web.xml配置即可

image

5、异步Pushlish以及DEMO

异步推送采用的是多线程的方法,具体看SimpleApplicationEventMulticaster类,也就是上面说的监听器容器

public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
           // 获取符合事件类型的监听器集合
        Executor executor = getTaskExecutor();
        if (executor != null) {
              // 如果线程池不为null,则提交一个新任务交由线程池运行
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    invokeListener(listener, event);
                }
            });
        }
        else {
              // 否则就是同步执行
            invokeListener(listener, event);
        }
    }
}

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
          // 错误处理器不为null
        try {
            listener.onApplicationEvent(event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
            // 当推送消息出现异常,利用错误处理器去处理该错误
        }
    }
    else {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || msg.startsWith(event.getClass().getName())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }
}

那么问题来了,如何注入一个线程池呢?往哪里注入呢?

只需要手动实现applicationEventMulticaster的bean,并且利用Set注入的方法注入了一个线程池,线程池也需要实例化,就直接使用了spring自带的简单异步任务线程池

image

从同步改成异步,代码不需要修改,直接注入这么一个bean即可(当然了XML配置和Config配置均可)

上一篇下一篇

猜你喜欢

热点阅读