Вызвать частный метод в произвольном объекте scala

Допустим, у меня есть объект 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-м вариантом, может даже не иметь смысла.

Спасибо!


person ishovelwell    schedule 07.10.2020    source источник
comment
Вы действительно уверены, что вам нужно это сделать? Почему вы хотите получить доступ к частным значениям любого произвольного объекта?   -  person Luis Miguel Mejía Suárez    schedule 07.10.2020
comment
@LuisMiguelMejíaSuárez сейчас это скорее любопытство, но это будет для тестирования. В моем случае у нас есть некоторые объекты, методы которых должны быть закрытыми из соображений безопасности. Некоторые методы могут загружать файл с диска, но для модульного тестирования мы хотели бы иметь возможность заменить файл по умолчанию тестовым файлом (в рабочей среде мы можем заблокировать использование отражения, чтобы пользователи не могли делать то же самое). . Такой подход позволит избежать потенциально большого рефакторинга.   -  person ishovelwell    schedule 07.10.2020
comment
Отражение Java намного проще, чем отражение Scala. Может ли рефлексия Java быть здесь более простым путем?   -  person Seth Tisue    schedule 08.10.2020
comment
@SethTisue, но тогда мы должны быть готовы работать со строками типа str.replace('.', '$') или наоборот, str + "$" и т. д.   -  person Dmytro Mitin    schedule 08.10.2020
comment
@SethTisue Насколько я понимаю, это не работает с Java, потому что у вас нет экземпляра объекта для передачи в invoke(instance, "arg"). Прохождение null не работает.   -  person ishovelwell    schedule 08.10.2020
comment
@ishovelwell Экземпляр объекта находится в статическом поле MODULE$. Я добавил код отражения Java в свой ответ.   -  person Dmytro Mitin    schedule 08.10.2020


Ответы (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

(*) Получите символ модуля, учитывая, что у меня есть класс модуля, макрос scala

(**) Как получить ClassTag из формы TypeTag или и то, и другое одновременно?

Я предполагаю, что у метода не было перегруженных версий, иначе вам следует работать с typ.decl(...).alternatives.find(...).get.asMethod или typ.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
person Dmytro Mitin    schedule 07.10.2020
comment
Работает! Спасибо за исчерпывающий ответ - person ishovelwell; 08.10.2020