zoukankan      html  css  js  c++  java
  • Spring事件机制

    Spring上下文启动的时候将实现ApplicationListener接口的Bean添加到事件监听者列表中,每次使用ApplicationEventPublisher发布ApplicationEvent时,都会通知对该事件感兴趣(监听该事件)的Bean。

    ApplicationContext继承关系图

    ApplicationContext继承了ApplicationEventPublisher接口,从而拥有事件发布的能力。但是实际ApplicationContext事件发布委托给ApplicationEventMulticaster执行。

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        // ...省略
    	if (this.earlyApplicationEvents != null) {
    		this.earlyApplicationEvents.add(applicationEvent);
    	}
    	else {
    		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    	}
        // ...省略
    }
    

    相关关键类或注解

    • ApplicationEventPublisher:发布事件
    • ApplicationListener:事件监听者
    • ApplicationEvent:事件
    • EventListener:事件和事件监听绑定
    • ApplicationEventMulticaster:发布事件

    Spring容器在启动的过程中也会发布各种事件,相应的组件监听到之后完成各自的初始化工作,下面是Spring内置的事件。

    • ContextStartedEvent:Spring上下文启动事件
    • ContextRefreshedEvent:Spring上下文初始化或刷新事件
    • ContextStoppedEvent:Spring上下文停止事件
    • ContextClosedEvent:Spring上下文关闭事件

    一、基于ApplicationListener实现事件监听

    1. 首先定一个事件,事件一定要继承ApplicationEvent
    public class DemoEvent extends ApplicationEvent {
        public DemoEvent(Object source) {
            super(source);
        }
    }
    
    1. 然后定义一个类实现ApplicationListener接口
    @Component
    public class DemoListener implements ApplicationListener<DemoEvent> {
    
        @Override
        public void onApplicationEvent(DemoEvent event) {
            System.out.println("收到消息: " + event);
        }
    }
    
    1. 发布事件
    applicationContext.publishEvent(new DemoEvent(new Object()));
    // 收到消息: com.example.demo.applicationevent.DemoEvent[source=java.lang.Object@3cb173db]
    

    虽然完成了事件监听,但是这种实现方案有点不太好,监听类必须实现特定的ApplicationListener接口,事件也必须继承ApplicationEvent类且一个类只能处理一个事件。接下来介绍第二种方式可以完全避免这种问题。

    二、基于@EventListener实现事件监听

    public class DemoEvent2 {
    }
    
    @Component
    public class DemoListener2 {
    
        @EventListener(DemoEvent2.class)
        public void onDemoEvent(DemoEvent2 demoEvent2) {
            System.out.println("@EventListener: " + demoEvent2);
        }
    }
    
    // 输出日志
    2020-04-28 23:52:59.187  INFO 4425 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
    2020-04-28 23:52:59.187  INFO 4425 --- [           main] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@75d7297d
    

    基于@EventListener的监听类不需要实现ApplicationListener,事件也不需要继承ApplicationEvent类,且可以在类里面声明多个方法处理不同的事件。实际开发中更倾向于这种实现方案。

    三、异步监听-@Async

    从上面的输出日志中可以看出,事件发布和监听是处在同一个线程中,有时候我们可能需要实现异步监听,可以借助@Async和自定义ApplicationEventMulticaster两种方式实现消息的异步监听。

    1. @Async

    修改代码如下

    @Slf4j
    @Component
    @EnableAsync
    public class DemoListener2 {
    
        @Async
        @EventListener(DemoEvent2.class)
        public void onDemoEvent(DemoEvent2 demoEvent2) {
            log.info("@EventListener: " + demoEvent2);
        }
    }
    
    // 输出日志
    2020-04-28 23:56:30.252  INFO 4508 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
    2020-04-28 23:56:30.310  INFO 4508 --- [         task-1] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@184bc84
    

    添加注解启用异步,并在指定方法上面添加@Async注解。从日志中可以看出事件发布和监听已经处在两个不同的线程中。

    1. 自定义ApplicationEventMulticaster
    @Slf4j
    @Component
    //@EnableAsync
    public class DemoListener2 {
    
    //    @Async
        @EventListener(DemoEvent2.class)
        public void onDemoEvent(DemoEvent2 demoEvent2) {
            log.info("@EventListener: " + demoEvent2);
        }
    
        @Bean("applicationEventMulticaster")
        public ApplicationEventMulticaster applicationEventMulticaster() {
            SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
            eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor("async-"));
            // 异常处理
            eventMulticaster.setErrorHandler(new ErrorHandler() {
                @Override
                public void handleError(Throwable t) {
                    log.error("事件监听异常", t);
                }
            });
            return eventMulticaster;
        }
    
    }
    
    // 输出日志
    2020-04-29 00:01:27.733  INFO 4624 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
    2020-04-29 00:01:27.735  INFO 4624 --- [       async-16] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@56051dd7
    

    从日志中可以看出,通过自定义ApplicationEventMulticaster方式也同样完成事件发布和监听处在两个不同的线程。

    为什么声明一个这样的Bean就可以完成异步呢?

    在开始的时候提到过ApplicationContext将事件发布委托给ApplicationEventMulticaster执行,接下来通过源码看ApplicationContext如何委托事件给ApplicationEventMulticaster。

    AbstractApplicationContext获取ApplicationEventMulticaster对象

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

    接下来看AbstractApplicationContext如何初始化applicationEventMulticaster对象

    public void refresh() throws BeansException, IllegalStateException {
    	synchronized (this.startupShutdownMonitor) {
    		try {
                // 省略无关代码
    			// Initialize event multicaster for this context.
    			initApplicationEventMulticaster();		
    		}
    		catch (BeansException ex) {
    		}
    	}
    }
    

    可以看出来在调用refresh的时候会初始化applicationEventMulticaster

    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
    
    protected void initApplicationEventMulticaster() {
    	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
    		this.applicationEventMulticaster =
    				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    		if (logger.isTraceEnabled()) {
    			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
    		}
    	}
    	else {
    		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() + "]");
    		}
    	}
    }
    

    初始化的是首先会尝试从Spring获取指定name的Bean,如果没有获取到,则新建一个实例,并注册到IoC容器中,到这里就明白为什么声明一个Bean之后就完成了异步的操作。因为我们提前声明了一个applicationEventMulticaster Bean对象,所以Spring会把这个对象当成默认的事件发布工具。自定义对象指定了线程池,所以事件发布和监听会处在不同的线程池中。

    这种做法会导致由该对象发布的所有事件都是异步处理,实际开发过程中推荐使用@Async注解实现异步监听逻辑,这样可以针对性对指定事件监听异步处理。

  • 相关阅读:
    System.arraycopy
    关于验证控件和javaSript验证的共存问题
    正则表达式经典
    css的一些基础的东西
    运用JAVASCRIPT,写一个类,类名:student,他的属性:name,age,tall,他的方法:getName,getAge,getTall
    转: ASP.NET 应用程序生命周期概述
    转:关于 Global.asax 文件
    今天又要过去了,学习点东西!
    javaScript对象和属性
    转载:.NET 2005 实现在线人数统计
  • 原文地址:https://www.cnblogs.com/thisismarc/p/12799958.html
Copyright © 2011-2022 走看看