背景
继第一版实现Processable 宏之后,各方面已经满足需求,也正常使用,并引入进bitlap,使得我们能以方法的形式管理所有Processor对象,而不需要创建太多的类文件。 但是细心的人会发现,为了实现这个小小的功能,我们在scala-macro-tools 中引入了重量级包"com.alipay.sofa" % "jraft-core" % "1.3.9" 。同时为了测试,我们还引入了protobuf-java 。
特别是sofa这个包,场景有限,当我们不需要使用Processable 宏,却被迫引入了这么大的jar,着实是划不来的。所以接下来优化目的是减小包的依赖。
之所以我们需要依赖jraft,是因为我们期望通过泛型参数限定符([_ <: Rpcxxx] )来自动使用编译期的类型检测,现在我们可以换个思路,能不能既保留类型检查又不需要引入包?如果我们在宏的实现中自己检查类型呢?
答案当然有,那就是类似gradle的API依赖,将依赖的导入转交给使用者决定。在宏中这种方式更为彻底,甚至都不需要引入包,只需在代码中直接用全类名即可,因为宏没有调用前不会类型检查。
使用该宏的人不引入jraft则会编译不了
重新设计Processable 宏
为此需要新增泛型参数,同时在宏里面处理类型检查。该实现和上篇文章一样,有三种不同定义,下面先给出最简洁的一种定义:
一 简单粗暴
只有泛型和2个函数参数
宏定义如下:
def apply[Service, RRC, RRP[_ <: Req], RC, Req, Resp]
(
processRequest: (Service, RRC, Req) ? Resp,
processException: (Service, RC, Exception) ? Resp
): RRP[Req] = macro ProcessorCreatorMacro.OnlyWithFunctionalParameters[Service, RRC, RRP[_ <: Req], RC, Req, Resp]
}
宏实现如下:
def OnlyWithFunctionalParameters[Service: c.WeakTypeTag, RRC: c.WeakTypeTag, RRP: c.WeakTypeTag, RC: c.WeakTypeTag, Req: c.WeakTypeTag, Resp: c.WeakTypeTag]
(c: blackbox.Context)(
processRequest: c.Expr[(Service, RRC, Req) ? Req],
processException: c.Expr[(Service, RC, Exception) ? Req]
): c.Expr[RRP] = {
import c.universe._
checkTree[RRC, RRP, RC, Service](c)
val serviceType = weakTypeOf[Service]
val className = TypeName(classNamePrefix + MacroCache.getIdentityId)
val reqProtoType = weakTypeOf[Req]
val rpcRequestClosureType = weakTypeOf[RRC]
val rpcContextType = weakTypeOf[RC]
val respProtoType = weakTypeOf[Resp]
val respProtoCompanionType = weakTypeOf[Resp].companion
val processor =
q"""
// 前面使用了通用的父类对RpcRequestProcessor做了一层简单包装,这里已经不需要了。
class $className(private val service: $serviceType, executor: java.util.concurrent.Executor = null)
// getDefaultInstance是Resp的静态方法,所以使用respProtoCompanionType,这个是Resp的伴生对象
extends com.alipay.sofa.jraft.rpc.RpcRequestProcessor[$reqProtoType](executor, $respProtoCompanionType.getDefaultInstance)
with com.typesafe.scalalogging.LazyLogging {
override def handleRequest(rpcCtx: com.alipay.sofa.jraft.rpc.RpcContext, request: $reqProtoType) {
try {
val msg = processRequest(request, new com.alipay.sofa.jraft.rpc.RpcRequestClosure(rpcCtx, this.defaultResp))
if (msg != null) {
rpcCtx.sendResponse(msg)
}
} catch {
case e: Exception =>
logger.error("handleRequest" + request + "failed", e)
rpcCtx.sendResponse(processError(rpcCtx, e))
}
}
override def interest(): String = classOf[$reqProtoType].getName
def processRequest(request: $reqProtoType, done: $rpcRequestClosureType): $respProtoType = {
$processRequest(service, done, request)
}
def processError(rpcCtx: $rpcContextType, exception: Exception): $respProtoType = {
$processException(service, rpcCtx, exception)
}
}
//运行时反射service对象,使用无参构造函数
val service = new io.github.dreamylost.macros.Creator[$serviceType].createInstance(null)(0)
new $className(service)
"""
printTree[RRP](c)(processor)
}
这样就完成了新的宏设计,来看怎么使用:
val openSession = ProcessorCreator[NetService, RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp](
(service, rpc, req) => {
import scala.jdk.CollectionConverters.MapHasAsScala
val username = req.getUsername
val password = req.getPassword
val configurationMap = req.getConfigurationMap
val ret = service.openSession(username, password, configurationMap.asScala.toMap)
BOpenSessionResp.newBuilder().setSessionHandle(ret).build()
},
(service, rpc, exception) => {
BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build()
}
)
二 灵活拓展
灵活又不失优雅
宏定义如下:
def apply[RRC, RRP[_ <: Req], RC, Req, Resp, Service, E <: Executor]
(
defaultResp: Resp,
processRequest: (Service, RRC, Req) ? Resp,
processException: (Service, RC, Exception) ? Resp
)(implicit service: Service, executor: E): RRP[Req] = macro ProcessorCreatorMacro.SimpleImpl[RRC, RRP[_ <: Req], RC, Req, Resp, Service, E]
由于service 和executor 通常是固定的,所以我们使用implicit 传入,既支持了自定义参数,又能方便的被别的Processor复用。
宏的使用如下:
implicit val service = new NetService
implicit val executor: Executor = new Executor {
override def execute(command: Runnable): Unit = ()
}
val openSession = ProcessorCreator[RpcRequestClosure, RpcRequestProcessor, RpcContext, BOpenSessionReq, BOpenSessionResp, NetService, Executor](
BOpenSessionResp.getDefaultInstance,
(service, rpcRequestClosure, req) => {
import scala.jdk.CollectionConverters.MapHasAsScala
val username = req.getUsername
val password = req.getPassword
val configurationMap = req.getConfigurationMap
val ret = service.openSession(username, password, configurationMap.asScala.toMap)
BOpenSessionResp.newBuilder().setSessionHandle(ret).build()
},
(service, rpcContext, exception) => {
BOpenSessionResp.newBuilder().setStatus(exception.getLocalizedMessage).build()
}
)
宏定义就不贴了,感兴趣可以看源码。
|