本专栏的项目代码在Github上,如果感兴趣的话,麻烦点点Star,谢谢啦。
在写业务逻辑时,我们希望以一种简单的方式就可以处理新增的消息:
class TestHandler : MessageHandler {
@Handle
fun handleTestReq(player: PlayerActor, testReq: TestReq) {
player.send(testResp { })
}
}
我们只需要定义一个 MessageHandler
,然后通过注解的方式,标记里面的某些方法是消息处理方法,然后就可以专心的编写业务逻辑了。为了实现如上的效果,我们需要写一个 MessageDispatcher
,在启动的时候,通过反射扫描所有实现了 MessageHandler
类,然后查找里面含有 @Handle
注解的方法,构建一个以消息为 Key 的消息处理 Map,然后根据消息类型调用不同的处理方法就行了。
定义接口
interface MessageHandler
这个接口也只是一个标记作用,并不提供实际的方法。
定义注解
定义注解用来标记消息的处理方法
/**
* @param message 消息类型,当且仅当需要处理此类型的消息,但是并不关心消息里面的内容时,才使用,这个时候handle function的参数应该只有一个
* 如果handle function的参数有两个,那么[message]无效,会使用handle function的第二个参数的类型作为消息类型
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Handle(val message: KClass<out Any> = Any::class)
使用反射扫描 MessageHandler
internal typealias K = KClass<out MessageHandler>
class MessageHandlerReflect(vararg packages: String) : Map<K, MessageHandler> {
val logger = logger()
@Volatile
private var handlers: Map<K, MessageHandler>
init {
val handlers = mutableMapOf<K, MessageHandler>()
Reflections(packages).getSubTypesOf(MessageHandler::class.java).forEach { clazz ->
val kClass = clazz.kotlin
if (!kClass.isAbstract) {
val primaryConstructor = requireNotNull(kClass.primaryConstructor) {
"$kClass has no primary constructor"
}
handlers[kClass] = primaryConstructor.call()
}
}
this.handlers = handlers
}
}
反射使用的是 Maven 上已有的库。这里把 MessageHandler
单独提取出来是因为 MessageDispatcher
可能会有多个,处理不同子类的消息,但是这些消息可以写到同一个 MessageHandler
中,避免重复反射 MessageHandler
。
private data class MessageHandle(
val clazz: K,
val handle: KFunction<Unit>,
val size: Int,
) : KFunction<Unit> by handle
class MessageDispatcher<M : Any>(private val message: KClass<M>, private val handlerReflect: MessageHandlerReflect) {
companion object {
fun <M : Any> processFunction(message: KClass<M>, kFunction: KFunction<*>, block: (KClass<out M>) -> Unit) {
val handle = kFunction.findAnnotation<Handle>()
if (handle != null) {
when (kFunction.parameters.size) {
2 -> {
//仅需要消息类型,不需要消息内容 查询Handle注解中的message属性
if (handle.message.isSubclassOf(message)) {
@Suppress("UNCHECKED_CAST")
block(handle.message as KClass<out M>)
}
}
3 -> {
//需要消息类型和消息内容
if (kFunction.parameters[2].type.isSubtypeOf(message.createType())) {
@Suppress("UNCHECKED_CAST")
block(kFunction.parameters[2].type.classifier as KClass<out M>)
}
}
else -> {
error("message handle function:${kFunction.name} parameter count must be 1 or 2")
}
}
}
}
}
private val logger = logger()
// <MessageKClass, MessageHandle>
private val handles: Map<KClass<out M>, MessageHandle>
init {
handles = initHandles()
}
private fun initHandles(): Map<KClass<out M>, MessageHandle> {
val messages = mutableMapOf<KClass<out M>, MessageHandle>()
handlerReflect.keys.forEach { clazz ->
clazz.declaredMemberFunctions.forEach { kFunction ->
processFunction(message, kFunction) { message ->
check(!messages.containsKey(message)) { "duplicate message handle function for message:${message}" }
@Suppress("UNCHECKED_CAST")
val handle = MessageHandle(clazz, kFunction as KFunction<Unit>, kFunction.parameters.size)
messages[message] = handle
logger.debug("add message handle function:{}", message)
}
}
}
return messages
}
fun dispatch(messageClazz: KClass<out M>, receiver: Any, message: Any) {
val messageHandle = handles[messageClazz]
if (messageHandle != null) {
val handler = requireNotNull(handlerReflect[messageHandle.clazz]) {
"handler for ${messageHandle.clazz} not found"
}
if (messageHandle.size == 3) {
messageHandle.call(handler, receiver, message)
} else {
messageHandle.call(handler, receiver)
}
} else {
logger.error("no message handler for:{} was found", messageClazz)
}
}
}
代码比较简单,通过反射,扫描 MessageHandler
中的 declaredMemberFunctions
,如果含有 @Handle
注解,则将此 KFunction
和消息类型关联起来,放入 Map 中。
使用
这个 MessageDispatcher
放到 Node
中就好,所有的 Actor 都可以通过 Node
引用获取到。
private val handlerReflect = MessageHandlerReflect("com.mikai233.player.handler")
val protobufDispatcher = MessageDispatcher(GeneratedMessage::class, handlerReflect)
val internalDispatcher = MessageDispatcher(Message::class, handlerReflect)
private fun handleProtobufEnvelope(envelope: ProtobufEnvelope) {
val message = envelope.message
try {
node.protobufDispatcher.dispatch(message::class, this, message)
} catch (e: Exception) {
logger.error(e, "player:{} handle protobuf message:{} failed", playerId, message)
}
}
private fun handlePlayerMessage(message: Message) {
try {
node.internalDispatcher.dispatch(message::class, this, message)
} catch (e: Exception) {
logger.error(e, "player:{} handle message:{} failed", playerId, message)
}
}