Scala 2.11.12中用宏实现对象所有方法拦截的动态代理
Scala 2.11 宏实现无接口对象方法拦截解决方案
问题背景
我有多个无trait/接口的不同对象,希望拦截它们的方法以添加调试功能,计划在Scala 2.11.12中用宏实现。由于待拦截方法间会互相调用,无法使用简单Proxy方案;且不想引入AspectJ依赖。尝试了如下代码,但遇到调用待拦截方法的报错:
import scala.language.experimental.macros import scala.reflect.macros.whitebox object MacroProxy { def createProxy[T](original: T): T = macro createProxyImpl[T] def createProxyImpl[T: c.WeakTypeTag](c: whitebox.Context)(original: c.Expr[T]): c.Expr[T] = { import c.universe._ val className = original.getClass.getSimpleName val m = weakTypeOf[T].members.iterator.toList .filter(m => m.isMethod).map(_.asMethod).map { m => q"""override def ${m.name} = $m""" } c.Expr { q"""new $className { ..$m } """ } } }
报错信息
缺少类MyClass中方法mymethod的参数列表
只有当需要函数类型时,未应用的方法才会被转换为函数。
你可以通过写成mymethod _或mymethod(_)来显式完成这个转换,而不是直接写mymethod。
问题分析与修复
原代码存在三个核心问题:
- 错误获取目标类型类名:
original.getClass.getSimpleName获取的是宏运行时Expr的类名,而非目标对象T的类名。 - 方法定义不完整:缺少参数列表,且直接引用方法符号而非调用原始对象的方法。
- 未正确关联原始对象的方法调用,也未添加调试逻辑。
修正后的宏代码
import scala.language.experimental.macros import scala.reflect.macros.whitebox object MacroProxy { def createProxy[T](original: T): T = macro createProxyImpl[T] def createProxyImpl[T: c.WeakTypeTag](c: whitebox.Context)(original: c.Expr[T]): c.Expr[T] = { import c.universe._ val targetType = weakTypeOf[T] // 获取目标类型的类名 val className = targetType.typeSymbol.name.toTypeName // 过滤出可拦截的实例方法(排除私有方法、构造方法) val methods = targetType.members .filter(m => m.isMethod && !m.isPrivate && !m.isConstructor) .map(_.asMethod) .filter(_.isInstanceMethod) .map { method => // 生成方法的参数列表定义 val paramGroups = method.paramLists.map(_.map { param => ValDef(Modifiers(), param.name, Ident(param.typeSignature), EmptyTree) }) // 生成调用原始对象方法的代码 val originalMethodCall = Apply(Select(original.tree, method.name), paramGroups.flatMap(_.map(p => Ident(p.name)))) // 自定义调试逻辑,可根据需求修改 val debugCode = q""" println(s"DEBUG: [${method.name}] 调用参数: ${List(..${paramGroups.flatMap(_.map(p => Ident(p.name)))})}") """ // 生成重写的方法:先执行调试逻辑,再调用原始方法 q""" override def ${method.name}(...$paramGroups): ${method.returnType} = { $debugCode $originalMethodCall } """ } // 生成继承自目标类型的匿名类,实现方法拦截 c.Expr[T] { q""" new $className { ..$methods } """ } } }
关键修复说明
- 正确获取目标类型:通过
weakTypeOf[T].typeSymbol.name获取目标对象的类名,确保生成的匿名类正确继承自原类。 - 完整方法定义:遍历方法的参数组生成参数列表,解决原代码中缺少参数的报错。
- 关联原始方法调用:通过
Select(original.tree, method.name)定位原始对象的方法,再用Apply传递参数,确保方法被正确执行。 - 插入调试逻辑:在方法执行前后添加自定义调试代码,满足需求;同时由于代理类直接继承原类,方法间的互相调用也会被拦截。
测试示例
定义测试类:
class MyClass { def mymethod(a: Int, b: String): String = { println("原始方法执行") s"结果: $a $b" } def anotherMethod(): Unit = { println("另一个方法执行") mymethod(123, "测试") } }
使用代理:
val original = new MyClass() val proxy = MacroProxy.createProxy(original) proxy.mymethod(456, "调试") proxy.anotherMethod()
运行输出:
DEBUG: [mymethod] 调用参数: List(456, 调试) 原始方法执行 DEBUG: [anotherMethod] 调用参数: List() 另一个方法执行 DEBUG: [mymethod] 调用参数: List(123, 测试) 原始方法执行
内容的提问来源于stack exchange,提问作者Ángel Álvarez Pascua
相关产品推荐
相关产品推荐

