简体   繁体   中英

Invoke private method in arbitrary scala object

Let's say I have a Scala object:

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

I can invoke someMethod with the following code:

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")

But above I've hardcoded SomeObject into the function. What I'd really like is to pass an arbitrary object name/classtag/whatever so I can invoke a private function generically in ANY object.

These attempts have NOT worked:

// 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")

I'm somewhat out of my depth, so what I'm trying with the 3rd option might not even make sense.

Thanks!

Try the following if you have String of object name

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

or if you have object 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

or if you have object 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

or if you have object 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

or if you have object 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?

I assume that the method didn't have overloaded versions, otherwise you should work with typ.decl(...).alternatives.find(...).get.asMethod or typ.decl(...).alternatives.head.asMethod .

Using Java reflection

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

Using Java method handles

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM