一个模块事件监听分发的解决方案

2022-06-30 16:06:14 浏览数 (2)

一、背景

在我们项目组件化的过程中,将功能模块拆分成了不同的module,每个module都有自己对外的事件回调,比如DataModule(数据模块)有数据更新回调,通知其他模块有数据更新了,其他模块可以通过addListener方法注册监听,DataModule维护一个监听列表,当数据更新的时候循环回调.

二、痛点

在原有的方案中,回调列表由module自己维护,在需要回调事件的地方循环列表逐个回调事件.这里有以下2个痛点

  • 每次回调事件的时候都需要手动循环列表逐个回调事件,有N个事件回调就会有N个循环回调代码,编写十分繁琐且不复用.
  • 不能发送粘性事件,如果注册监听需要响应之前的回调事件,只能自己维护历史数据,在注册的时候回调一次事件.

三、思考

基于上述的2个痛点,对原有方案重新进行了思考.

  • 使用动态代理,在代理中维护监听列表,在代理内部处理循环分发,减少代码冗余.
  • 用map缓存回调事件的参数,添加事件回调时可以选择注册粘性事件回调,从缓存中取到最新的参数触发事件回调.
  • 在多线程环境下操作回调事件列表且多查询少修改,使用CopyOnWriteArrayList来存储.

四、方案

1、定义接口动态代理类ModuleListenerProxy

使用动态代理模式,定义ModuleListenerProxy类,UML图如下:

ModuleListenerProxy1.pngModuleListenerProxy1.png

属性名

说明

clazz

被代理的事件监听Class对象

proxy

事件监听代理

listeners

注册的事件监听列表

方法名

说明

getProxy

获取事件代理对象,如果为空则创建一个

invoke

实现的动态代理接口方法

addListener

添加一个事件监听

removeListener

删除一个事件监听

release

清空事件监听列表,并将代理对象置空

动态代理类ModuleListenerProxy实现了InvocationHandler接口,在invoke方法中分发事件,并且定义一个事件监听泛型,用于规范事件监听类型,在构造函数中接收事件监听的class对象,用于后续创建代理对象.

代码语言:txt复制
class ModuleListenerProxy<Listener : BaseModuleListener>(private val clazz: Class<Listener>) :  InvocationHandler {

    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        method ?: return null
        listeners.forEach {
            doInvoke(it, method, args)
        }
        return null
    }
}

模块在使用的时候就可以直接通过getProxy()获取到代理对象,回调事件.由于getProxy()方法可能会在模块中被多次调用,为减少代码冗余,getProxy()中创建创建代理对象proxy所需的clazz在初始化时赋值.

代码语言:txt复制
/**
 * 事件监听代理
 */
@Volatile
private var proxy: Listener? = null

/**
 * 获取代理对象
 */
@Synchronized  
@Suppress("UNCHECKED_CAST")  
fun getProxy(): Listener {
    if (proxy == null) {
        proxy = Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz), this) as Listener
    }
    return proxy!!
}

讲完了事件的回调和分发,在看看事件监听的添加和删除,维护一个CopyOnWriteArrayList监听列表,支持多线程操作事件监听列表.

代码语言:txt复制
/**
 * 事件监听数组
 */
private val listeners = CopyOnWriteArrayList<Listener>()

 /**
 * 添加事件监听
 */
fun addListener(listener: Listener, sticky: Boolean) {
    listeners.add(listener)
 }

 /**
 * 删除事件监听
 */
fun removeListener(listener: Listener) {
    listeners.remove(listener)
}

提供一个release()方法在模块生命周期结束的时候可以释放资源,防止内存泄露.

代码语言:txt复制
/**
 * 释放
 */
fun release() {
    listeners.clear()
    proxy = null
}
2、添加粘性事件监听

需要在原来的ModuleListenerProxy类中维护一个事件参数缓存,修改之后的ModuleListenerProxy类UML图如下:

ModuleListenerProxy2.pngModuleListenerProxy2.png

属性名

说明

methodArgsMap

缓存的事件回调参数map

方法名

说明

invoke

实现的动态代理接口方法

addListener

添加一个事件监听,并设置是否接收粘性消息

getMethodKey

获取事件回调方法的方法名和参数作为唯一标识符

新增的事件参数缓存数据在invoke方法中缓存,缓存时以方法的唯一标识符为key,在添加事件监听时设置是否接收粘性消息,接收粘性消息则通过反射取出事件监听中的所有事件回调方法,找到对应的参数,触发一次事件回调.

代码语言:txt复制
/**
 * 事件监听数据缓存
 */
private val methodArgsMap = ConcurrentHashMap<String, Array<out Any>>()

override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
    method ?: return null
    val methodKey = getMethodKey(method)
    // 缓存数据
    methodArgsMap[methodKey] = args ?: emptyArray()
    listeners.forEach {
        doInvoke(it, method, args)
    }
    return null
}

/**
 * 添加事件监听
 */
fun addListener(listener: Listener, sticky: Boolean) {
    listeners.add(listener)
    // 粘性消息处理
    // 反射拿到事件监听的方法,查找是否有缓存的数据,有的话执行一次方法
    if (sticky) {
         listener.javaClass.declaredMethods.forEach {
            val methodKey = getMethodKey(it)
            if (methodArgsMap.containsKey(methodKey)) {
                val args = methodArgsMap[methodKey]
                doInvoke(listener, it, args)
            }
        }
    }
}

通过getMethodKey()可以获取方法唯一标识符,即方法名和参数组成的string,格式为方法名(参数1, 参数2 ...)

代码语言:txt复制
/**
 * 获取方法的key
 * 格式: 方法名(参数1, 参数2 ...)
 */
private fun getMethodKey(method: Method): String {
    val name = method.name
    val methodString = method.toString()
    val index = methodString.lastIndexOf(name)
    return methodString.substring(index)
}
3、模块中的使用

ModuleListenerProxy使用起来也十分方便,在模块基类BaseModule中定义一个ModuleListenerProxy对象,通过反射获取模块事件监听class,初始化ModuleListenerProxy对象proxy,后续事件监听的添加和删除都通过proxy执行,定义一个getListenerProxy()方法暴露proxy,这样具体业务模块的子类module就可以通过getListenerProxy()获取事件监听代理,执行事件监听的各个方法.

代码语言:txt复制
abstract class BaseModule<Listener : BaseModuleListener>() : IModule<Listener> {

    /**
     * 事件监听代理
     */
    @Suppress("UNCHECKED_CAST")
    private val proxy by lazy {
        (getListenerClazz() as? Class<Listener>)?.let {
            ModuleListenerProxy(it)
        }
    }

     /**
     * 销毁
     */
    override fun destroy() {
        proxy?.release()
    }

    /**
     * 注册监听
     */
    override fun addListener(listener: Listener, sticky: Boolean) {
        proxy?.addListener(listener, sticky)
    }

    /**
     * 反注册监听
     */
    override fun removeListener(listener: Listener) {
        proxy?.removeListener(listener)
    }

    /**
     * 获取事件监听代理
     */
    override fun getListenerProxy(): Listener? {
        return proxy?.getProxy()
    }

    /**
     * 通过反射获取监听对象class
     */
    private fun getListenerClazz(): Type? {
        val superClass = this.javaClass.genericSuperclass
        if (superClass is ParameterizedType) {
            val actualTypeArguments = superClass.actualTypeArguments
            if (actualTypeArguments.isNotEmpty()) {
                return actualTypeArguments[0]
            }
        }
        return null
    }

}

五、总结

在这个解决方案中,主要用到了动态代理、发射和泛型的知识,有效的解决了代码冗余和粘性消息的痛点.后续在实际应用中如果还有修改和补充,我再回来更新.

0 人点赞