【问题标题】:Invoke private method in arbitrary scala object在任意 scala 对象中调用私有方法
【发布时间】:2020-10-07 17:58:49
【问题描述】:

假设我有一个 Scala 对象:

object SomeObject {
  private def someMethod(msg: String): Unit = println(msg)
}

我可以使用以下代码调用someMethod

import scala.reflect.runtime.{universe => ru}
import scala.reflect.ClassTag

def invokeObjectPrivateMethod[R](methodName: String, args: AnyRef*): R = {
  val rm = ru.runtimeMirror(getClass.getClassLoader)
  val instanceMirror = rm.reflect(SomeObject)
  val methodSymbol = ru.typeOf[SomeObject.type].decl(ru.TermName(methodName)).asMethod
  val method = instanceMirror.reflectMethod(methodSymbol)
  method(args: _*).asInstanceOf[R]
}
invokeObjectPrivateMethod("someMethod", "it works")

但在上面我已经将SomeObject 硬编码到函数中。我真正想要的是传递任意对象名称/类标签/任何东西,这样我就可以在任何对象中通用地调用私有函数。

这些尝试没有奏效:

// With explicit ClassTag parameter
def invokeObjectPrivateMethod2[R](classTag: ClassTag[_], methodName: String, args: AnyRef*): R = {
  val rm = ru.runtimeMirror(getClass.getClassLoader)
  val instanceMirror = rm.reflect(classTag)
  val methodSymbol = ru.typeOf[classTag.type].decl(ru.TermName(methodName)).asMethod
  val method = instanceMirror.reflectMethod(methodSymbol)
  method(args: _*).asInstanceOf[R]
}
// This fails at runtime with: `scala.ScalaReflectionException: <none> is not a method`
invokeObjectPrivateMethod2[Unit](ClassTag(SomeObject.getClass), "someMethod", "it doesn't work")

// With implicit ClassTag/TypeTag
def invokeObjectPrivateMethod3[T: ClassTag, S: ru.TypeTag, R](methodName: String, args: AnyRef*)(implicit ct: ClassTag[T]): R = {
  val rm = ru.runtimeMirror(getClass.getClassLoader)
  val instanceMirror = rm.reflect(ct)
  val methodSymbol = ru.typeOf[S].decl(ru.TermName(methodName)).asMethod
  val method = instanceMirror.reflectMethod(methodSymbol)
  method(args: _*).asInstanceOf[R]
}
// This fails at compile time: `not found: type SomeObject`
invokeObjectPrivateMethod3[SomeObject, SomeObject, Unit]("someMethod", "it also doesn't work")

我有点不知所措,所以我尝试使用第 3 个选项可能甚至没有意义。

谢谢!

【问题讨论】:

  • 您确定需要这样做吗?为什么要访问任意对象的私有值?
  • @LuisMiguelMejíaSuárez 现在更多的是好奇,但它会用于测试。就我而言,出于安全原因,我们有一些对象的方法必须保持私有。某些方法可能会从磁盘加载文件,但对于单元测试,我们希望能够将默认值换成测试文件(在生产中,我们可以阻止使用反射,这样用户就不能做同样的事情) .这种方法可以避免潜在的大型重构。
  • Java 反射比 Scala 反射容易得多。 Java 反射在这里可能是一条更简单的路径吗?
  • @SethTisue 但是我们应该准备好使用像str.replace('.', '$')这样的字符串,反之亦然,str + "$"等。
  • @ishovelwell 对象的实例在静态字段MODULE$ 中。我在答案中添加了 Java 反射代码。

标签: scala reflection scala-reflect


【解决方案1】:

如果您有 String 的对象名称,请尝试以下操作

def invokeObjectPrivateMethod[R](objectName: String, methodName: String, args: AnyRef*): R = {
  val rm = scala.reflect.runtime.currentMirror
  val moduleSymbol = rm.staticModule(objectName)
  val classSymbol = moduleSymbol.moduleClass.asClass
  val moduleMirror = rm.reflectModule(moduleSymbol)
  val objectInstance = moduleMirror.instance
  val objectType = classSymbol.toType
  val methodSymbol = objectType.decl(ru.TermName(methodName)).asMethod
  val instanceMirror = rm.reflect(objectInstance)
  val methodMirror = instanceMirror.reflectMethod(methodSymbol)
  methodMirror(args: _*).asInstanceOf[R]
}

invokeObjectPrivateMethod("com.example.App.SomeObject", "someMethod", "it works") //it works

或者如果你有对象ClassTag

def invokeObjectPrivateMethod[R](classTag: ClassTag[_], methodName: String, args: AnyRef*): R = {
  val rm = scala.reflect.runtime.currentMirror
  val clazz = classTag.runtimeClass
  val classSymbol = rm.classSymbol(clazz)
  val moduleSymbol = classSymbol.owner.info.decl(classSymbol.name.toTermName).asModule //see (*)
  val moduleMirror = rm.reflectModule(moduleSymbol)
  val objectInstance = moduleMirror.instance
  val objectType = classSymbol.toType
  val methodSymbol = objectType.decl(ru.TermName(methodName)).asMethod
  val instanceMirror = rm.reflect(objectInstance)
  val methodMirror = instanceMirror.reflectMethod(methodSymbol)
  methodMirror(args: _*).asInstanceOf[R]
}
invokeObjectPrivateMethod(classTag[SomeObject.type], "someMethod", "it works") //it works

或者如果你有对象TypeTag

def invokeObjectPrivateMethod[R](typeTag: TypeTag[_], methodName: String, args: AnyRef*): R = {
  val rm = scala.reflect.runtime.currentMirror
  val objectType = typeTag.tpe
  val clazz = rm.runtimeClass(objectType) // see (**)
  val classSymbol = rm.classSymbol(clazz)
  val moduleSymbol = classSymbol.owner.info.decl(classSymbol.name.toTermName).asModule // see (*)
  val moduleMirror = rm.reflectModule(moduleSymbol)
  val objectInstance = moduleMirror.instance
  val methodSymbol = objectType.decl(ru.TermName(methodName)).asMethod
  val instanceMirror = rm.reflect(objectInstance)
  val methodMirror = instanceMirror.reflectMethod(methodSymbol)
  methodMirror(args: _*).asInstanceOf[R]
}

invokeObjectPrivateMethod(typeTag[SomeObject.type], "someMethod", "it works") //it works

或者如果你有对象Class

def invokeObjectPrivateMethod[R](clazz: Class[_], methodName: String, args: AnyRef*): R = {
  val rm = scala.reflect.runtime.currentMirror
  val classSymbol =  rm.classSymbol(clazz)
  val moduleSymbol = classSymbol.owner.info.decl(classSymbol.name.toTermName).asModule //see (*)
  val moduleMirror = rm.reflectModule(moduleSymbol)
  val objectInstance = moduleMirror.instance
  val objectType = classSymbol.toType
  val methodSymbol = objectType.decl(ru.TermName(methodName)).asMethod
  val instanceMirror = rm.reflect(objectInstance)
  val methodMirror = instanceMirror.reflectMethod(methodSymbol)
  methodMirror(args: _*).asInstanceOf[R]
}

invokeObjectPrivateMethod(SomeObject.getClass, "someMethod", "it works") //it works

或者如果你有对象Type

def invokeObjectPrivateMethod[R](typ: Type, methodName: String, args: AnyRef*): R = {
  val rm = scala.reflect.runtime.currentMirror
  val classSymbol =  typ.typeSymbol.asClass
  val moduleSymbol = classSymbol.owner.info.decl(classSymbol.name.toTermName).asModule //see (*)
  val moduleMirror = rm.reflectModule(moduleSymbol)
  val objectInstance = moduleMirror.instance
  val methodSymbol = typ.decl(ru.TermName(methodName)).asMethod
  val instanceMirror = rm.reflect(objectInstance)
  val methodMirror = instanceMirror.reflectMethod(methodSymbol)
  methodMirror(args: _*).asInstanceOf[R]
}

invokeObjectPrivateMethod(typeOf[SomeObject.type], "someMethod", "it works") //it works

(*)Get the module symbol, given I have the module class, scala macro

(**)How to get ClassTag form TypeTag, or both at same time?

我假设该方法没有重载版本,否则您应该使用typ.decl(...).alternatives.find(...).get.asMethodtyp.decl(...).alternatives.head.asMethod

使用 Java 反射

def invokeObjectPrivateMethod[R](packageName: String, objectName: String, methodName: String, args: AnyRef*): R = {
  val javaClassName = s"$packageName.${objectName.replace('.', '$')}$$"
  val clazz = Class.forName(javaClassName)
  val method = clazz.getDeclaredMethods.find(_.getName == methodName).get
  method.setAccessible(true)
  val field = clazz.getField("MODULE$")
  val instance = field.get(null) // null because field is static
  method.invoke(instance, args: _*).asInstanceOf[R]
}

invokeObjectPrivateMethod("com.example", "App.SomeObject", "someMethod", "it works") //it works

使用 Java 方法句柄

def invokeObjectPrivateMethod[R](packageName: String, objectName: String, methodName: String, args: AnyRef*): R = {
  val javaClassName = s"$packageName.${objectName.replace('.', '$')}$$"
  val clazz = Class.forName(javaClassName)
  val lookup = MethodHandles.lookup
  val field = clazz.getField("MODULE$")
  val fieldMethodHandle = lookup.unreflectGetter(field)
  val instance = fieldMethodHandle.invokeWithArguments()
  val method = clazz.getDeclaredMethods.find(_.getName == methodName).get
  method.setAccessible(true)
  val methodHandle = lookup.unreflect(method)
  methodHandle.invokeWithArguments(instance +: args : _*).asInstanceOf[R]
}

invokeObjectPrivateMethod("com.example", "App.SomeObject", "someMethod", "it works") //it works

【讨论】:

  • 有效!感谢您详尽的回答
猜你喜欢
  • 2016-01-27
  • 1970-01-01
  • 2015-09-16
  • 1970-01-01
  • 2019-01-18
  • 2023-03-26
  • 1970-01-01
  • 2012-02-03
相关资源
最近更新 更多