• [spring源码学习]九、IOC源码-applicationEventMulticaster事件广播


    一、代码实例

      回到第IOC的第七章context部分,我们看源码分析部分,可以看到在spring的bean加载之后的第二个重要的bean为applicationEventMulticaster,从字面上我们知道它是一个事件广播器。在第8和9部分,详细描述了广播器的初始化:

      1、查找是否有name为applicationEventMulticaster的bean,如果有放到容器里,如果没有,初始化一个系统默认的放入容器

      2、查找手动设置的applicationListeners,添加到applicationEventMulticaster里

      3、查找定义的类型为ApplicationListener的bean,设置到applicationEventMulticaster

      4、初始化完成、对earlyApplicationEvents里的事件进行通知(此容器仅仅是广播器未建立的时候保存通知信息,一旦容器建立完成,以后均直接通知)

      5、在系统操作时候,遇到的各种bean的通知事件进行通知

      以上流程我们在第七章源码部分都已经分析过了,所以不再赘述。可以看到的是applicationEventMulticaster是一个标准的观察者模式,对于他内部的监听者applicationListeners,每次事件到来都会一一获取通知。

      我们来进行实例展示:

    1、定义一个ApplicationEvent,

    package com.zjl;
    
    import org.springframework.context.ApplicationEvent;
    
    public class MyApplicationEvent extends ApplicationEvent {
    
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
    
        public MyApplicationEvent(Object source) {
            super(source);
        }
    
    }

    2、定义一个事件处理器MyApplicationListener,仅仅监听我们自定义的事件,注:此处的泛型如果不指定或者指定ApplicationEvent,可以监听所有spring发出的监听

    package com.zjl;
    
    import org.springframework.context.ApplicationListener;
    
    public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
    
        @Override
        public void onApplicationEvent(MyApplicationEvent event) {
            
            System.out.println(event.getSource()+"==== "+event.getTimestamp());
        }
    
    }

    3、测试代码

    public class Test {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
            context.publishEvent(new MyApplicationEvent("init-bean"));
            Person person=(Person)context.getBean("person");
            person.sayHello();
        }
    }

    4、结果(成功的打印出事件的内容)

    init-bean==== 1462785633657
    hello zhangsan

    二、源码分析

    1、执行publishEvent,支持两种事件1、直接继承ApplicationEvent,2、其他时间,会被包装为PayloadApplicationEvent,可以使用getPayload获取真实的通知内容

    如果广播器未生成,先存起来,已经生成的调用multicastEvent进行发送

    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);
            }
    
            // Decorate event as an ApplicationEvent if necessary
            ApplicationEvent applicationEvent;
            //支持两种事件1、直接继承ApplicationEvent,2、其他时间,会被包装为PayloadApplicationEvent,可以使用getPayload获取真实的通知内容
            if (event instanceof ApplicationEvent) {
                applicationEvent = (ApplicationEvent) event;
            }
            else {
                applicationEvent = new PayloadApplicationEvent<Object>(this, event);
                if (eventType == null) {
                    eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
                }
            }
    
            // Multicast right now if possible - or lazily once the multicaster is initialized
            if (this.earlyApplicationEvents != null) {
                //如果有预制行添加到预制行,预制行在执行一次后被置为null,以后都是直接执行
                this.earlyApplicationEvents.add(applicationEvent);
            }
            else {
                //广播event时间
                getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
            }
    
            // Publish event via parent context as well...
            //父bean同样广播
            if (this.parent != null) {
                if (this.parent instanceof AbstractApplicationContext) {
                    ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
                }
                else {
                    this.parent.publishEvent(event);
                }
            }
        }

    2、查找所有的监听者,依次遍历,如果有线程池,利用线程池进行发送,如果没有则直接发送,如果针对比较大的并发量,我们应该采用线程池模式,将发送通知和真正的业务逻辑进行分离

        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) {
                    executor.execute(new Runnable() {
                        @Override
                        public void run() {
                            invokeListener(listener, event);
                        }
                    });
                }
                else {
                    invokeListener(listener, event);
                }
            }
        }

     3、在获取监听器的过程中调用,,可以看到监听器根据两种类型判断是否需要处理:

    a)、继承SmartApplicationListener的情况下,根据supportsEventType返回结果判断

    b)、根据监听器的泛型判断(示例采用第二种方式),比较泛型的代码很复杂,暂时就不关心了

        public boolean supportsEventType(ResolvableType eventType) {
            //判断监听器是否可以监听事件有两种方式:
            //1、如果监听器是SmartApplicationListener的实现,那么会重写supportsEventType,返回true就是支持
            if (this.delegate instanceof SmartApplicationListener) {
                Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.getRawClass();
                return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass);
            }
            else {
                //根据泛型内容与事件类型是否一致
                return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
            }
        }

    3、调用invokeListener,如果有errorHandler会有errorHandler处理异常

    protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
            ErrorHandler errorHandler = getErrorHandler();
            if (errorHandler != null) {
                try {
                    listener.onApplicationEvent(event);
                }
                catch (Throwable err) {
                    errorHandler.handleError(err);
                }
            }
            else {
                try {
                    listener.onApplicationEvent(event);
                }
                catch (ClassCastException ex) {
                    // Possibly a lambda-defined listener which we could not resolve the generic event type for
                    LogFactory.getLog(getClass()).debug("Non-matching event type for listener: " + listener, ex);
                }
            }
        }

    4、到此为止,我们完成了广播器的代码跟踪

    三、总结

      广播器的代码不算复杂,使用了一个标准的观察者模式,系统在初始化的时候会在context中注册一个applicationListeners,如果系统有需要广播的情况下,会发送一个applicationEvent事件,注册的listener会根据自己关心的类型进行接收和解析。

      在我们之前的实力中,我们需要补充的主要有三点:

      1、监听器对于事件的处理,系统中有两种方式:

        a)、继承SmartApplicationListener的情况下,根据supportsEventType返回结果判断

        b)、根据监听器的泛型判断

        c)、我们很容易想到,如果没有继承父类,也没有泛型的情况下,我们可以在广播器的onApplicationEvent方法中获取到event,然后自行过滤

      2、事件通知往往是独立于整个程序运行之外的一个补充,所以另外开启线程进行工作是个不错的办法,listener中提供了executor的注入来实现多线程

      3、事件的类型不仅仅支持ApplicationEvent类型,也支持其他类型,spring会自动转化为PayloadApplicationEvent,我们调用它的getPayload将获取到具体的数据

      4、可以注入一个errorHandler,完成对异常的处理

    四、示例修改

      1、修改配置文件

        <bean name="MyApplicationListener" class="com.zjl.MyApplicationListener">
        </bean>
        <!-- 定义一个固定大小的线程,采用factory-method和静态方法的形式,参数注入使用构造函数注入 -->
        <bean name="executor" class="java.util.concurrent.Executors" factory-method="newFixedThreadPool">
                <constructor-arg index="0"><value>5</value></constructor-arg>
        </bean>
        <!-- 定义applicationEventMulticaster,注入线程池和errorHandler,此处使用系统自带的广播器,也可以注入其他广播器, -->
        <bean name="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
            <property name="taskExecutor" ref="executor"></property>
            <property name="errorHandler" ref="errorHandler"></property>
        </bean>
        <!-- 定义一个errorHandler,统一处理异常信息 -->
        <bean name="errorHandler" class="com.zjl.MyErrorHandler"></bean>

      2、异常处理的errorHandler

    public class MyErrorHandler implements ErrorHandler {
    
        @Override
        public void handleError(Throwable t) {
            System.out.println("捕获到了异常:"+t.getMessage());
        }
    
    }

      3、修改MyApplicationListener ,使它继承SmartApplicationListener,主要做以下处理:

      a)supportsSourceType-判断广播来源类的类型,全部返回true,表示接收所有类的广播

      b)supportsEventType-接收PayloadApplicationEvent类型,也就是非event类型的广播;自定义的广播

      c)为了使errorHandler起作用,用了一个1/0,使系统抛出一个异常,注:此处由于onApplicationEvent本身接口没有抛出异常,所以显式的异常系统编译都会有问题,所以感觉并不好用

    public class MyApplicationListener implements SmartApplicationListener{
    
    
        @Override
        public void onApplicationEvent(ApplicationEvent event){
            System.out.println(Thread.currentThread().getName()+"-"+event.getSource()+"===="+event.getTimestamp());
            if(event.getSource().equals("person-sayhello")){
                int num=1/0;
            }
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    
        @Override
        public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){
            if(PayloadApplicationEvent.class.isAssignableFrom(eventType)){
                System.out.println("非ApplicationEvent的广播");
                return true;
            }else if(MyApplicationEvent.class.isAssignableFrom(eventType)) {
                System.out.println("自定义广播");
                return true;
            }
            return false;
            
        }
    
        @Override
        public boolean supportsSourceType(Class<?> sourceType) {
            System.out.println("sourceType====="+sourceType);
            return true;
        }
    
    }

      4、bean内容,因为在bean中发出广播,需要使用context,此处直接使用ApplicationContextAware接口形式注入bean

        public class Person implements ApplicationContextAware {
        private ApplicationContext applicationContext;
        private String name;
        public boolean running;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void sayHello(){
            applicationContext.publishEvent(new MyApplicationEvent("person-sayhello"));
            System.out.println("hello "+this.name);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext=applicationContext;
        }
    }

      5、测试程序

    public class Test {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
            context.publishEvent("init-bean");
            Person person=(Person)context.getBean("person");
            person.sayHello();
        }
    }

      6、运行结果我们可以看到不同的广播按照规则进行了过滤,而且最终是使用不同的线程进行发送。如果有异常,errorHandler顺利捕捉到了异常。那么这个例子基本涵盖了广播的所有点。

    非ApplicationEvent的广播 //非系统广播自定义广播可以通过
    sourceType=====class  org.springframework.context.support.ClassPathXmlApplicationContext //来自context的广播可以通过
    自定义广播 //自定义广播可以通过
    sourceType=====class java.lang.String //来自主程序的广播可以通过
    hello zhangsan
    pool-1-thread-2-person-sayhello====1462862846700 //非event广播内容
    捕获到了异常:/ by zero//异常
    pool-1-thread-1-org.springframework.context.support.ClassPathXmlApplicationContext@48503868: startup date [Tue May 10 14:47:25 CST 2016]; root of context hierarchy====1462862846699 //自定义广播
        

      

  • 相关阅读:
    【转】36个经典的JQuery导航菜单演示+下载
    【转】ASP.NET 3.5 开发范例精讲精析读书笔记
    【转】js弹出框详解
    【转】谈谈三层架构中MODEL的作用
    【转】制作.net实体类生成器(1)
    ASP.NET开发实战宝典.pdf配套视频源码iso| 开发实战宝典pdf和配套视频源码iso
    【转】if (!IsPostBack)
    jquery
    取web.config 连接字符串
    js接收传递过来的参数
  • 原文地址:https://www.cnblogs.com/jyyzzjl/p/5476546.html
Copyright © 2020-2023  润新知