美文网首页Java服务端面试
Spring源码探究:事件机制

Spring源码探究:事件机制

作者: Fooisart | 来源:发表于2018-06-01 23:15 被阅读259次

    结合Spring源码分析Spring事件机制


    问题

    问题描述:项目中配置事件监听,监听当容器加载完成之后,做一些初始化工作。项目运行之后,发现初始化工作被重复做了两次。为了便于分析,去掉代码中的业务逻辑,只留下场景。

    配置监听器
    /**
     * @author jiangwang3
     * @date 2018/6/1.
     */
    @Component
    public class FreshListener implements ApplicationListener<ContextRefreshedEvent>{
        private final Logger logger = LoggerFactory.getLogger(getClass());
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            //业务代码
            logger.error("将有权限人员放入缓存。。。。");
        }
    }
    

    配置FreshListener监听器,监听当容器加载完成之后,将管理员名单加入缓存。却发现,名单被加载了两次。WHY???


    从源码的角度探究该问题

    由于源码中的个方法较长,所以只贴出重点且与主题相关的代码。建议结合本地源码一起看。

    为了说清楚这个问题,咱们需要具备两个知识点
    1. jdk事件机制
    2. Spring事件机制

    jdk事件机制

    User实体类
    public class User {
        private String username;
        private String password;
        private String sms;
        public User(String username, String password, String sms) {
            this.username = username;
            this.password = password;
            this.sms = sms;
        }  
    }
    
    用户监听器
    /**
     * @author jiangwang
     * @date 21:37 2018/6/1
     */
    public interface UserListener extends EventListener {
        void onRegister(UserEvent event);
    }
    
    发送短信监听器
    /**
     * @author jiangwang
     * @date 21:38 2018/6/1
     */
    public class SendSmsListener implements UserListener {
        @Override
        public void onRegister(UserEvent event) {
            if (event instanceof SendSmsEvent) {
                Object source = event.getSource();
                User user = (User) source;
                System.out.println("send sms to " + user.getUsername());
            }
        }
    }
    
    User事件
    /**
     * @author jiangwang
     * @date 21:39 2018/6/1
     */
    public class UserEvent extends EventObject {
        public UserEvent(Object source){
            super(source);
        }
    }
    
    发送短信事件
    /**
     * @author jiangwang
     * @date 21:40 2018/6/1
     */
    public class SendSmsEvent extends UserEvent {
        public SendSmsEvent(Object source) {
            super(source);
        }
    }
    
    服务类,用于存放事件监听,类比容器
    public class UserService {
        private List<UserListener> listenerList = new ArrayList<>();
        //当用户注册的时候,触发发送短信事件
        public void register(User user){
            System.out.println("name= " + user.getUsername() + " ,password= " + 
                                          user.getPassword() + " ,注册成功");
            publishEvent(new SendSmsEvent(user));
        }
        public void publishEvent(UserEvent event){
            for(UserListener listener : listenerList){
                listener.onRegister(event);
            }
        }
        public void addListeners(UserListener listener){
            this.listenerList.add(listener);
        }
    }
    
    测试类
    /**
     * @author jiangwang
     * @date 21:35 2018/6/1
     */
    public class EventApp {
        public static void main(String[] args) {
            UserService service = new UserService();
            service.addListeners(new SendSmsListener());
            //添加其他监听器 ...
            User user = new User("foo", "123456", "注册成功啦!!");
            service.register(user);
        }
    }
    
    运行结果
    result.png
    启动项目,模拟用户注册,触发了短信发送事件。从上述简单的模拟事件代码中,可以归结出三个名词,事件(SendSmsEvent)监听器(SendSmsListener)事件源(用户注册)。可以将上述流程描述为:用户注册==>触发发送短息事件==>短信监听器监听到消息。
    上述代码有两个重要接口:
    事件监听器接口
    /**
     * A tagging interface that all event listener interfaces must extend.
     * @since JDK1.1
     */
    public interface EventListener {
    }
    

    该接口为标识接口

    事件接口
    /**
     * <p>
     * The root class from which all event state objects shall be derived.
     * <p>
     * All Events are constructed with a reference to the object, the "source",
     * that is logically deemed to be the object upon which the Event in question
     * initially occurred upon.
     * @since JDK1.1
     */
    public class EventObject implements java.io.Serializable {
        private static final long serialVersionUID = 5516075349620653480L;
        /**
         * The object on which the Event initially occurred.
         */
        protected transient Object  source;
        /**
         * Constructs a prototypical Event.
         * @param    source    The object on which the Event initially occurred.
         * @exception  IllegalArgumentException  if source is null.
         */
        public EventObject(Object source) {
            if (source == null)
                throw new IllegalArgumentException("null source");
            this.source = source;
        }
        /**
         * The object on which the Event initially occurred.
         * @return   The object on which the Event initially occurred.
         */
        public Object getSource() {
            return source;
        }
        /**
         * Returns a String representation of this EventObject.
         * @return  A a String representation of this EventObject.
         */
        public String toString() {
            return getClass().getName() + "[source=" + source + "]";
        }
    }
    

    该接口中仅有source参数,无特殊含义,类似于存放数据源

    Spring事件机制

    对比上面jdk事件的Demo,咱们分析spring源码

    spring源码探究—容器 一文中,我们分析了Spring中bean是如何加载的,并且分析了项目启动的入口,不做赘叙,将其作为已知条件。

    进入AbstractApplicationContext的refresh()方法
    @Override
        public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                prepareRefresh();
                // Tell the subclass to refresh the internal bean factory.
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);
                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);
                    // Initialize message source for this context.
                    initMessageSource();
                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();
                    // Initialize other special beans in specific context subclasses.
                    onRefresh();
                    // Check for listener beans and register them.
                    registerListeners();
                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);
                    // Last step: publish corresponding event.
                    finishRefresh();
                }
            }
        }
    

    这个方法中有三句话与Spring事件相关,把这三句话分析明白了,Spring事件机制也就了然了。挨个分析:

    1. initApplicationEventMulticaster():初始化事件广播器
    2. registerListeners():监听器注册,类似于上文EventAppservice.addListeners(new SendSmsListener()), 下文重点讲。
    3. finishRefresh():发布事件,类似于上文UserServicepublishEvent(new SendSmsEvent(user)),一会重点讲。
    进入registerListeners()方法
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    

    从容器中的所有bean中获取实现ApplicationListener接口的类。换言之,如果我们想使用Spring 事件机制来为我们项目服务,那我们所写的监听器必须实现ApplicationListener接口。

    进入ApplicationListener接口:
    public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
        /**
         * Handle an application event.
         * @param event the event to respond to
         */
        void onApplicationEvent(E event);
    }
    

    ApplicationListener接口继承自jdk事件机制中的EventListener,可以看出Spring事件机制改编自jdk事件机制。Spring在监听器接口中添加了onApplicationEvent()方法,便于事件被触发时执行任务,类似于上午UserListener中的onRegister()方法。
    回到registerListeners()方法,获取到监听器类之后,存放在了事件广播器(applicationEventMulticaster)中,便于后面使用。

    进入finishRefresh()方法
    publishEvent(new ContextRefreshedEvent(this));
    

    这句话类似于UserService中的publishEvent(new SendSmsEvent(user)),而ContextRefreshedEvent类似于上文中的发送短信事件ContextRefreshedEvent代表的事件是容器初始化完成。如果容器初始化完成了,那么所对应的事件监听器将会被触发。继续层层跟进,来到:

    publishEvent(Object event, ResolvableType eventType)
    
    跟进看重点代码:
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    
    进入multicastEvent:
    getApplicationListeners(event, type)
    

    这句话的意思是根据事件类型获取监听器。因为咱们在项目里面可能会配置很多监听器,每一个监听器都会有自己所对应的事件类型,只有自己所对应的事件发生了,监听器才会被触发。

    继续看multicastEvent中的代码:
    invokeListener(listener, event);
    
    进入invokeListener:
    doInvokeListener(listener, event);
    
    进入doInvokeListener:
    listener.onApplicationEvent(event);
    

    看到了onApplicationEvent在此执行了,类似于UserService中listener.onRegister(event)
    至此,事件机制分析完毕。

    咱们再次回到publishEvent(Object event, ResolvableType eventType)中,有这么一段代码:
    // Publish event via parent context as well...
            if (this.parent != null) {
                if (this.parent instanceof AbstractApplicationContext) {
                    ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
                }
                else {
                    this.parent.publishEvent(event);
                }
            }
    

    判断该容器是否有父容器,若存在入容器,再一次触发父容器中的事件监听器。


    回答为什么事件监听器会被执行两次?

    从上文的源码分析中,咱们知道了ContextRefreshedEvent事件监听器是在refresh()方法内被触发的,更准确地讲,是refresh()方法中的finishRefresh()触发了ContextRefreshedEvent事件监听器。而我们在spring源码探究—容器 一文中,得出一个结论:子容器可以获取父容器bean,反之不行。这里是因为Spring容器初始化执行refresh()方法时,触发了ContextRefreshedEvent事件监听器,而SpringMvc容器初始化时也执行了refresh()方法,当代码执行到

    publishEvent(Object event, ResolvableType eventType);
    

    其中有一段代码判断了是否存在父容器。若存在,会将父容器中的监听器执行一遍。所以再一次触发了ContextRefreshedEvent事件监听器。所以从直观上看,初始化了两次。


    解决方案:

    1. 严格控制有且仅有父容器或子容器执行监听器。举例:
    /**
     * @author jiangwang3
     * @date 2018/6/1.
     */
    @Component
    public class EvolvedFreshListener implements ApplicationListener<ContextRefreshedEvent>{
        private final Logger logger = LoggerFactory.getLogger(getClass());
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            if (event.getApplicationContext().getParent() == null){
                logger.error("进化版====将有权限人员放入缓存。。。。");
            }
    
        }
    }
    
    1. 将bean放在子容器中,例如将其配置在SpringMvc容器中,自行实现。
    2. 监听器方法执行时加锁,举例(伙伴提供):
    /**
     * 实现此类, 可以在Spring容器完全初始化完毕时获取到Spring容器 
     * @author wanghui59@jd.com
     * @since 2017-12-29
     */
    public abstract class ContextRefreshListener implements 
                            ApplicationListener<ContextRefreshedEvent> {
        private volatile boolean initialized = false;  
        @Override
        public synchronized void onApplicationEvent(ContextRefreshedEvent event) {
            if (!initialized) {
                System.out.println("加锁====将有权限人员放入缓存。。。。");
            }
        }
    }
    

    项目完整源码github地址

    相关文章

      网友评论

        本文标题:Spring源码探究:事件机制

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