Spring中的事件

转载自本人博客:https://chenjiabing666.github.io/2019/06/30/Spring%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6/

简介

  • 学过编程语言的肯定知道事件,在JS中事件,Android中的事件,大多是鼠标点击,键盘事件,手指滑动事件等等。在Spring中也有一些事件,比如容器启动、容器关闭、容器刷新都是一个事件。
  • 既然有了事件,自然少不了事件监听器,事件分发器等,后续会详细介绍

事件

Spring中内置的事件

  • ContextStartedEvent:容器启动的时候触发(start方法)
  • ContextRefreshedEvent:容器刷新的时候触发(onRefresh,在finisRefresh中调用)
  • ContextStoppedEvent:容器停止的时候触发(stop方法)
  • ContextClosedEvent:容器关闭的时候触发(close方法)

自定义事件

  • Spring中自定义事件只需要继承ApplicationEvent即可完成一个自定义的Spring事件
/**
 * 自定义事件,继承ApplicationEvent
 */
@Data
public class FirstEvent extends ApplicationEvent {
    /**
     * 需要携带的消息,可以是任意类型的数据,相当于传递数据
     */
    private String message;

    /**
     * 构造方法
     * @param source 事件发生的类
     * @param message 携带的消息
     */
    public FirstEvent(Object source,String message) {
        super(source);
        this.message=message;
    }
}

监听器

  • 监听器用来监听事件触发,一旦事件触发了,监听器会执行相应的操作。
  • 监听器的实现有两种方式:
    • 实现ApplicationListener接口
    • 使用@EventListener注解

实现ApplicationListener接口

  • 创建监听器需要两个条件:
    • 实现ApplicationListener接口
    • 将该自定义的监听器注入到ioc容器中
/**
 * 自定义一个监听器,实现ApplicationListener,指定的泛型就是需要监听的事件
 * 监听ContextRefreshedEvent,当容器完成刷新的时候该监听器就会监听到并执行onApplicationEvent方法
 */
@Component
public class FirstListener implements ApplicationListener<ContextRefreshedEvent> {
    /**
     * 重载方法,被监听的事件触发了就会调用这个方法
     * @param event 触发事件的对象
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("容器刷新的监听器启动了........");
        System.out.println(event.getSource()+"---->"+event.getTimestamp()+"----"+event.getApplicationContext());
        System.out.println(".........................................");
    }
}
  • 此时只要启动容器,自定义的监听器就会起作用,当然我们监听的是Spring内置的事件,在容器启动的时候Spring会使用事件发布器发布事件,此时才是真正的触发事件,我们自定义的事件并不能被监听,除非被事件发布器发布。

使用@EventListener注解

  • 常见的属性:
    • classes:Class数组,指定需要监听的事件
    • condition:指定条件,默认监听
/**
 * 注解方式实现事件监听器
 */
@Component
public class CustomEventListener {
    /**
     * 使用@EventListener监听事件
     * @param event 传入的事件源
     */
    @EventListener(classes = {ApplicationEvent.class})
    public void handlerContextRefreshEvent(ApplicationEvent event){
        if (event instanceof ContextRefreshedEvent) {
            ContextRefreshedEvent e=(ContextRefreshedEvent)event;
            System.out.println("ContextRefreshedEvent启动了........");
            System.out.println(event.getSource() + "---->" + event.getTimestamp() + "----" + e.getApplicationContext());
            System.out.println(".........................................");
        }else if(event instanceof ContextStartedEvent){
            ContextStartedEvent e=(ContextStartedEvent)event;
            System.out.println("ContextStartedEvent启动了........");
            System.out.println(event.getSource() + "---->" + event.getTimestamp() + "----" + e.getApplicationContext());
            System.out.println(".........................................");
        }else if(event instanceof ContextStoppedEvent){
            ContextStoppedEvent e=(ContextStoppedEvent)event;
            System.out.println("ContextStoppedEvent启动了........");
            System.out.println(event.getSource() + "---->" + event.getTimestamp() + "----" + e.getApplicationContext());
            System.out.println(".........................................");
        }else if(event instanceof ContextClosedEvent){
            ContextClosedEvent e=(ContextClosedEvent)event;
            System.out.println("ContextClosedEvent启动了........");
            System.out.println(event.getSource() + "---->" + event.getTimestamp() + "----" + e.getApplicationContext());
            System.out.println(".........................................");
        }
    }


    /**
     * 可以不指定classes,默认监听的是方法参数中的事件
     * @param event 事件源
     */
    @EventListener
    public void handleFirstEvent(FirstEvent event){
        System.out.println("firstEvent事件启动了,。。。。。。。。。");
        System.out.println(event.getSource()+"---->"+event.getMessage());
    }
}

事件发布

  • Spring中发布事件的接口是ApplicationEventPublisher,我们可以自定义自己的类,当然也可以使用spring现成的类

Spring的事件发布类

  • ApplicationContext
  • AnnotationConfigApplicationContext

直接注入

  • 在容器启动刷新的时候已经注入了ApplicationEventPublisher的实现,我们可以直接注入使用。如下:
/**
 * 自定义的事件发布器
 */
@Component
public class CustomPublisher  {
    /**
     * 直接注入ApplicationEventPublisher
     */
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 发布事件
     * @param event 指定的事件
     */
    public void publishEvent(ApplicationEvent event){
        applicationEventPublisher.publishEvent(event);
    }
}
  • 测试
    @Test
    public void test1(){
        CustomPublisher customPublisher = applicationContext.getBean(CustomPublisher.class);
        customPublisher.publishEvent(new FirstEvent(this,"启动自定义事件"));
    }

使用ApplicationEventPublisherAware注入

/**
 * 自定义的事件发布器,实现ApplicationEventPublisherAware接口
 */
@Component
public class CustomPublisher implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 发布事件
     * @param event 指定的事件
     */
    public void publishEvent(ApplicationEvent event){
        applicationEventPublisher.publishEvent(event);
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher=applicationEventPublisher;
    }
}

事件多播器

  • 何为事件多播器【ApplicationEventMulticaster】?
    • 简单的说事件多播器就是一个管理事件监听器并且广播事件【根据指定的事件调用指定的监听器而已】
  • spring中两个实现类分别为AbstractApplicationEventMulticasterSimpleApplicationEventMulticaster
  • 如何广播事件?【如何通过指定的事件调用指定的监听器】
    • 真正的实现在org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)这个方法中,如下:
@Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        //遍历事件监听器
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            //判断是否设置了Executor,如果存在,那么就异步执行
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                //否则的话,同步执行,调用invokeListener
                invokeListener(listener, event);
            }
        }
    }


/****************************************invokeListener******************************/
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        //如果有异常处理器,就try-catch执行
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                //如果有异常了,执行异常处理器的handleError方法
                errorHandler.handleError(err);
            }
        }
        else {
            //没有异常处理器直接执行
            doInvokeListener(listener, event);
        }
    }

/*******************************doInvokeListener****************************/
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            //此时真正调用监听器中的方法
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                // -> let's suppress the exception and just log a debug message.
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

异步事件

  • 前面创建的事件和监听器都是同步进行,我们可以使用异步事件

使用@Async实现异步

  • Spring中可以使用@Async注解标注方法异步执行,不过需要在配置类上开启异步功能,使用@EnableAsync注解,如下:
@Configuration
@ComponentScan(value = {"cn.tedu.demo"})
@EnableAsync
public class FirstConfig {
    
}
  • 此时可以在监听方法上标注@Async注解,使得事件异步执行
/**
 * 注解方式实现事件监听器
 */
@Component
public class CustomEventListener {
    /**
     * 可以不指定classes,默认监听的是方法参数中的事件
     * @Async : 指定这个方法异步执行
     * @param event 事件源
     */
    @EventListener
    @Async
    public void handleFirstEvent(FirstEvent event){
        System.out.println("firstEvent事件启动了,。。。。。。。。。");
        System.out.println(event.getSource()+"---->"+event.getMessage());
    }
}

自定义事件多播器

  • 从源码我们可以知道,spring容器加载的时候先获取的是ioc容器中的,如果不存在,那么才会新建一个SimpleApplicationEventMulticaster,我们可以自己注入一个多播器直接使用即可。
  • 源码如下:
protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        //判断ioc容器中是否存在id为applicationEventMulticaster事件多播器
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            //直接使用ioc容器中的
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            //新建一个,不过没有设置TaskExector
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }
  • 从源码中【org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)】我们可以看出监听器的执行是先判断多播器中是否存在Executor,如果存在,那么就单独开启一个线程执行,否则就同步执行,我们在初始化多播器的时候,可以为其设置一个Executor,那么就可以异步执行了。
@Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
  • 实现:在配置类注入一个多播器即可,bean的id一定要是applicationEventMulticaster,同时为其设置一个executor
        /**
         * 自定义一个事件多播器,用来管理监听器和执行监听器
         * @return
         */
        @Bean(name = "applicationEventMulticaster")
        public ApplicationEventMulticaster applicationEventMulticaster(){
            //事件多播器
            SimpleApplicationEventMulticaster simpleApplicationEventMulticaster=new SimpleApplicationEventMulticaster();
            //设置executor
            SimpleAsyncTaskExecutor executor=new SimpleAsyncTaskExecutor();
            simpleApplicationEventMulticaster.setTaskExecutor(executor);
            //设置一个事件异常处理器,当监听器执行出现错误了会进行补救
            simpleApplicationEventMulticaster.setErrorHandler(t->{
                //这里可以针对不同的异常进行处理,在监听器中trycatch,不同执行抛出不同异常即可分类处理
                System.out.println("监听事件执行报错了");
                System.out.println(t.getMessage());
            });
            return simpleApplicationEventMulticaster;
        }

源码解析

  • 在spring源码中和事件涉及到的主要概念如下:
    • 事件(ApplicationEvent)
    • 监听器(ApplicationEventListener)
    • 事件发布器(ApplicationEventPublisher)
    • 事件多播器(ApplicationEventMulticaster)
  • 具体源码层面的涉及如下:
    • 容器刷新refresh方法中:
      • initApplicationEventMulticaster():初始化事件多播器
      • registerListeners();:注册事件监听器
      • finishRefresh()方法中调用publishEvent(new ContextRefreshedEvent(this))方法发布容器刷新事件。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 我的情人节必须有火锅和巧克力,玫瑰花没有出现。二月十日的结婚三周年纪念日在焦头烂额中渡过了,今天和情人节一起庆祝。...
    tangmorton阅读 179评论 0 0
  • 目录 1.初始化,建立会话,获取摄像头 2.嵌入实时预览层 3.相关设置 4.拍摄获取照片 5.View层UI及交...
    海边的遐想阅读 8,573评论 1 1
  • java中有哪几种锁? 1.有synchronized和lock两种锁,synchronized是java的...
    Amusia_7f42阅读 84评论 0 0
  • 概念理解:推荐:在数据挖掘中,推荐包括相似推荐及系统过滤推荐。1,相似推荐:指当用户表现出对某人或某物的兴趣是,为...
    阿达t阅读 1,033评论 0 3