一、背景
在我们项目组件化的过程中,将功能模块拆分成了不同的module,每个module都有自己对外的事件回调,比如DataModule(数据模块)有数据更新回调,通知其他模块有数据更新了,其他模块可以通过addListener方法注册监听,DataModule维护一个监听列表,当数据更新的时候循环回调.
二、痛点
在原有的方案中,回调列表由module自己维护,在需要回调事件的地方循环列表逐个回调事件.这里有以下2个痛点
- 每次回调事件的时候都需要手动循环列表逐个回调事件,有N个事件回调就会有N个循环回调代码,编写十分繁琐且不复用.
- 不能发送粘性事件,如果注册监听需要响应之前的回调事件,只能自己维护历史数据,在注册的时候回调一次事件.
三、思考
基于上述的2个痛点,对原有方案重新进行了思考.
- 使用动态代理,在代理中维护监听列表,在代理内部处理循环分发,减少代码冗余.
- 用map缓存回调事件的参数,添加事件回调时可以选择注册粘性事件回调,从缓存中取到最新的参数触发事件回调.
- 在多线程环境下操作回调事件列表且多查询少修改,使用
CopyOnWriteArrayList
来存储.
四、方案
1、定义接口动态代理类ModuleListenerProxy
使用动态代理模式,定义ModuleListenerProxy
类,UML图如下:
属性名 | 说明 |
---|---|
clazz | 被代理的事件监听Class对象 |
proxy | 事件监听代理 |
listeners | 注册的事件监听列表 |
方法名 | 说明 |
---|---|
getProxy | 获取事件代理对象,如果为空则创建一个 |
invoke | 实现的动态代理接口方法 |
addListener | 添加一个事件监听 |
removeListener | 删除一个事件监听 |
release | 清空事件监听列表,并将代理对象置空 |
动态代理类ModuleListenerProxy
实现了InvocationHandler
接口,在invoke
方法中分发事件,并且定义一个事件监听泛型,用于规范事件监听类型,在构造函数中接收事件监听的class
对象,用于后续创建代理对象.
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
在初始化时赋值.
/**
* 事件监听代理
*/
@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
监听列表,支持多线程操作事件监听列表.
/**
* 事件监听数组
*/
private val listeners = CopyOnWriteArrayList<Listener>()
/**
* 添加事件监听
*/
fun addListener(listener: Listener, sticky: Boolean) {
listeners.add(listener)
}
/**
* 删除事件监听
*/
fun removeListener(listener: Listener) {
listeners.remove(listener)
}
提供一个release()
方法在模块生命周期结束的时候可以释放资源,防止内存泄露.
/**
* 释放
*/
fun release() {
listeners.clear()
proxy = null
}
2、添加粘性事件监听
需要在原来的ModuleListenerProxy
类中维护一个事件参数缓存,修改之后的ModuleListenerProxy
类UML图如下:
属性名 | 说明 |
---|---|
methodArgsMap | 缓存的事件回调参数map |
方法名 | 说明 |
---|---|
invoke | 实现的动态代理接口方法 |
addListener | 添加一个事件监听,并设置是否接收粘性消息 |
getMethodKey | 获取事件回调方法的方法名和参数作为唯一标识符 |
新增的事件参数缓存数据在invoke
方法中缓存,缓存时以方法的唯一标识符为key,在添加事件监听时设置是否接收粘性消息,接收粘性消息则通过反射取出事件监听中的所有事件回调方法,找到对应的参数,触发一次事件回调.
/**
* 事件监听数据缓存
*/
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 ...)
/**
* 获取方法的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()
获取事件监听代理,执行事件监听的各个方法.
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
}
}
五、总结
在这个解决方案中,主要用到了动态代理、发射和泛型的知识,有效的解决了代码冗余和粘性消息的痛点.后续在实际应用中如果还有修改和补充,我再回来更新.