• spring boot 源码赏析之事件监听


    使用spring Boot已经快1年多了,期间一直想点开springboot源码查看,但由于种种原因一直未能如愿(主要是人类的惰性。。。),今天就拿springboot 的监听事件祭刀。

    springboot 中常用的事件监听主要有ApplicationStartedEvent,ApplicationEnviromentPreparedEvent,ApplicationPreparedEvent,ApplicationStoppedEvent等。用于监听springboot生命周期中的各种事件。

    说到监听都会不由自主想到观察者模式,springboot的事件监听实现也没出意外,它的实现机制也是一个典型的观察者模式,只不过稍微复杂点罢了。springboot将观察者放入一个list中委托

     SimpleApplicationEventMulticaster管理。这里有个有意思的地方,srpingboot在实例化SpringApplicationRunListener 时并没有通过正常的途径(比如new,或者其他实例化方法),而是将接口的实现类交由

    SpringFactoriesLoader去 META-INF/spring.factories 下装载:

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            try {
                Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                List<String> result = new ArrayList<String>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                    String factoryClassNames = properties.getProperty(factoryClassName);
                    result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
                }
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                        "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }

    这里通过classLoader的getResources方法将所有的META-INF/spring.factories资源都装载进来。

    springboot为什么要不惜花费这么大代价去装载一个接口的实现呢,稍微分析一下还是很清晰的,首先,将接口和实现分离,这是典型的依赖注入,如果从单个应用程序来看,大不必这样。关键就在SpringFactoriesLoader里,如上所说,SpringFactoriesLoader是将所有的配置都装载进来,也就是说springboot在为以后的扩展留了条后路,当有新的jar包(意味着又升级啦)加进来,并且需要添加新的的监听事件类型时,就不用修改原有的代码。这就是传说中的对修改关闭对扩展开放的典型例子。

    接着说,那么SpringFactoriesLoader装载的实例到底是什么呢, EventPublishingRunListener!

    这货也就是个中间类,承接了事件的管理和事件的广播,正真的主角是SimpleApplicationEventMulticaster类,该类的实例在EventPublishingRunListener实例化时被创建出来。

    里面有个ListenerRetriever内部类,这个类有两个作用,一个default,SimpleApplicationEventMulticaster初始化时被创建,含有所有的观察者,并作为各种操作的同步锁,还有就是将观察者分类,分类的依据有两个,

    一个是事件类型eventType,还有一个是事件原始对象类型sourceType,sourceType是每个事件都会有的,这里是applicationContext。,分类好后,会在事件发生时由最上层SpringApplication发起通知,最后由SimpleApplicationEventMulticaster对象分别调用每个具体的观察者:

    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) {
                    String msg = ex.getMessage();
                    if (msg == null || msg.startsWith(event.getClass().getName())) {
                        // Possibly a lambda-defined listener which we could not resolve the generic event type for
                        Log logger = LogFactory.getLog(getClass());
                        if (logger.isDebugEnabled()) {
                            logger.debug("Non-matching event type for listener: " + listener, ex);
                        }
                    }
                    else {
                        throw ex;
                    }
                }
            }
        }

    至此,一个完整的从事件注册到事件出发再到事件执行完毕的流程结束。山路十八弯,沿途风景多,细细品味还是会有一番收获的。这当中仍有许多细节未能一一道出,比如hashmap的hashCode的重写分析,比如同步锁的使用原因等等。

                                                                                                                                                         转载请注明出处。

  • 相关阅读:
    git在iOS开发中的使用
    搜索联系人是去掉拼音中的空格
    xmPP(即时通讯)向远程服务器请求数据
    使用CFStringTransform进行汉字转拼音(可去掉声调)
    node的模块系统和commonJS规范的关系
    在centos7中通过使用yum安装mongoDB
    vue跨组件通信,简易状态管理的使用
    Linux(centos7) 常用命令
    前端打包后, 路由模式为history时,用express测试服务端能否正常解析路由路径
    几个文件目录树生成工具tree,treer,tree-cli,tree-node-cli的使用配置和对比
  • 原文地址:https://www.cnblogs.com/foreveravalon/p/6974250.html
Copyright © 2020-2023  润新知