美文网首页
Spring的Event事件处理使用详解

Spring的Event事件处理使用详解

作者: Joshua1919 | 来源:发表于2020-06-08 10:00 被阅读0次

    Spring中使用事件非常简单,只需要以下的几个步骤:

    • 1.定义事件,继承ApplicationEvent
    • 2.定义监听,要么实现ApplicationListener接口,要么在方法上添加@EventListener注解
    • 3.发布事件,调用ApplicationContext.publishEvent()或者ApplicationEventPublisher.publishEvent();
      下面我们就用一个例子来说下具体的用法:
      比如用户注册成功以后,系统要给用户发送一封邮件,同时还要给用户发放优惠券,为了跟注册流程解耦,可以在注册成功以后发出一个事件,让其他服务来监听。
      定义用户注册成功事件:
    //自定义事件需要继承ApplicationEvent
    public class UserRegisterEvent extends ApplicationEvent {
        private String username;
        public UserRegisterEvent(Object source, String username) {
            super(source);
            this.username = username;
        }
        public String getUsername() {
            return username;
        }
    }
    

    定义发送邮件和发放优惠券的监听:

    //可以实现ApplicationListener接口监听事件
    @Component
    @Order(2)//可以使用order指定顺序,越小优先级越高
    public class MailUserRegisterListener implements ApplicationListener<UserRegisterEvent> {
        @Override
        public void onApplicationEvent(UserRegisterEvent event) {
            System.out.println(Thread.currentThread().getName()+"-给用户"+event.getUsername()+"发送邮件!");
        }
    }
    //也可以使用@EventListener监听事件
    @Component
    @Order(1)
    public class CouponUserRegisterListener {
        @EventListener
        public void sendCoupon(UserRegisterEvent event) {
            System.out.println(Thread.currentThread().getName()+"-给用户"+event.getUsername()+"发送优惠券!");
        }
    }
    

    用户注册成功以后发送事件出来:

    @Service
    public class UserRegisterService implements ApplicationContextAware, ApplicationEventPublisherAware {
        private ApplicationContext applicationContext;
        private ApplicationEventPublisher applicationEventPublisher;
        public boolean userRegister(String username){
            System.out.println("用户"+username+"注册成功");
             //可以用applicationContext发送事件
            applicationContext.publishEvent(new UserRegisterEvent(this, username));
            //也可以用applicationEventPublisher,二者等价
            //applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
            return true;
        }
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher){
            this.applicationEventPublisher = applicationEventPublisher;
        }
    }
    

    运行下:

    public class AsyncMain {
        public static void main(String[] args)throws Exception {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
            UserRegisterService service = ctx.getBean(UserRegisterService.class);
            service.userRegister("xjs");
        }
    }
    

    假如我们想把监听线程跟主线程异步,该如何做呢?根据上一篇文章,只需要启用@EnableAsync,然后在监听的方法上加上@Async即可。

    @EnableAsync //启用异步
    @Configuration
    @ComponentScan
    public class AppConfig {
    }
    @Component
    @Order(3)
    public class AsyncUserRegisterListener implements ApplicationListener<UserRegisterEvent> {
        @Override
        @Async  //注意这个注解,这样就可以在子线程执行监听
        public void onApplicationEvent(UserRegisterEvent event) {
            System.out.println(Thread.currentThread().getName()+"-收到用户注册事件,异步执行任务");
        }
    }
    

    看下事件处理相关的源码处理:
    在容器启动的时候AbstractApplicationContext#refresh()里面会执行:AbstractApplicationContext#initApplicationEventMulticaster();

    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() + "]");
            }
        }
    }
    

    这里首先是从容器中去取名字是applicationEventMulticaster并且类型是ApplicationEventMulticaster的bean,如果取不到就创建一个SimpleApplicationEventMulticaster。实际上,Spring中仅有一个ApplicationEventMulticaster,那就是SimpleApplicationEventMulticaster。
    发送事件的时候AbstractApplicationContext#publishEvent,最终还是交给SimpleApplicationEventMulticaster#multicastEvent:

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
    

    如果能获取到executor就异步执行,否则就同步执行。
    因此,我们也可以通过给SimpleApplicationEventMulticaster设置executor来做异步处理,比如:

    @EnableAsync
    @Configuration
    @ComponentScan
    public class AppConfig {
        @Bean //另一种异步的方式,这样即使不加Async也可以异步,注意bean的名字
        public ApplicationEventMulticaster applicationEventMulticaster(){
            SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
            eventMulticaster.setTaskExecutor(this.taskExecutor().getObject());
            return eventMulticaster;
        }
        /**
         * 注意这个bean的名字,如果是taskExecutor,那么@Async也会使用这个线程池,否则@Async不会使用,原因请参考前一篇
         * */
        @Bean
        public ThreadPoolExecutorFactoryBean taskExecutor(){
            ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
            result.setThreadNamePrefix("event-pool-");
            result.setCorePoolSize(5);
            return result;
        }
    }
    

    完整的源码下载:https://github.com/xjs1919/enumdemo 下面的event-demo。

    转载请标明出处,欢迎扫码加关注。


    扫一扫关注微信公众号

    相关文章

      网友评论

          本文标题:Spring的Event事件处理使用详解

          本文链接:https://www.haomeiwen.com/subject/mzxltktx.html