Akka分布式游戏后端开发11 MessageDispatcher

67 阅读2分钟

本专栏的项目代码在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)
    }
}