You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。


问题分析与修复

原代码存在三个核心问题:

  1. 错误获取目标类型类名:original.getClass.getSimpleName获取的是宏运行时Expr的类名,而非目标对象T的类名。
  2. 方法定义不完整:缺少参数列表,且直接引用方法符号而非调用原始对象的方法。
  3. 未正确关联原始对象的方法调用,也未添加调试逻辑。

修正后的宏代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:13:27