5

我正在玩反思,以对一个特征进行深入分析。我想得到的一件事是设置为成员字段的初始值。例如,在特征中:

trait A {
  val x: Int = 3
  val y: String = "y"
}

很高兴知道 3 和“y”。我在 API 中没有找到与此任务相关的任何内容,并且由于以下输出(由 scalac -Xprint 生成):

abstract trait A extends Object {
  <accessor> def com$hablapps$A$_setter_$x_=(x$1: Int): Unit;
  <accessor> def com$hablapps$A$_setter_$y_=(x$1: String): Unit;
  <stable> <accessor> def x(): Int;
  <stable> <accessor> def y(): String
};
abstract trait A$class extends  {
  def /*A$class*/$init$($this: com.hablapps.A): Unit = {
    $this.com$hablapps$A$_setter_$x_=(3);
    $this.com$hablapps$A$_setter_$y_=("y");
    ()
  }
}

恐怕很难访问它们,因为它们保存在 $init$ 方法的主体中。有没有(简单的)方法可以通过反射获得这些值?

4

3 回答 3

2

您必须反汇编字节码:

trait A { val x: Int = 3 }

public abstract class A$class extends java.lang.Object{
public static void $init$(A);
  Code:
   0:    aload_0
   1:    iconst_3
   2:    invokeinterface #12,  2; //InterfaceMethod A.A$_setter_$x_$eq:(I)V
   7:    return

请参见第 1 行——该值存在的唯一位置是 init 方法的字节码!

您无法以任何其他方式达到此目的,因为如果您有

trait A { val x: Int = 3 }
trait B extends A { override val x = 7 }
class C extends B {}

你会发现,CextendsA$_setter_$x_$eq根本没有做任何事情——使A$class.$init$调用成为无操作并使值无法检索。

证明:

public class C extends java.lang.Object implements B,scala.ScalaObject{
public void A$_setter_$x_$eq(int);
  Code:
   0:    return

public void B$_setter_$x_$eq(int);
  Code:
   0:    aload_0
   1:    iload_1
   2:    putfield #11; //Field x:I
   5:    return
于 2012-07-19T18:10:23.800 回答
1

我怀疑你能反省到这一点。这不是关于类型的信息,而是代码。如果您有该特征的树,则可以找到它,但是,否则,我对此表示怀疑。

但是,您可以使用类文件解析器来进一步调查。我假设这些将作为类的常量出现,可以读取。我不确定您是否可以将它们与变量相关联,但是...

我对类文件解析器不太熟悉,但我认为一个名为“asm”的工具可以做到这一点。

于 2012-07-19T16:01:39.440 回答
1

您可以使用 Java 的 Proxy 创建一个 trait 实例,该实例从对 trait 的 ...$ setter $... 方法的所有调用中收集值。这是一个骇人听闻的例子:

object Reflection {
  def traitInits(clazz : Class[_]) : Map[String, Object] = {
    var cl = clazz.getClassLoader
    if (cl == null) {
      cl = ClassLoader.getSystemClassLoader
    }

    var init : Option[java.lang.reflect.Method] = None
    try {
      for (m <- cl.loadClass(clazz.getName + "$class").getMethods if init.isEmpty)
        if (m.getName == "$init$")
          init = Some(m)
    } catch {
      case e : Exception =>
    }

    if (init.isEmpty) return Map()

    var encodedToDecodedSetterNameMap = Map[String, String]()

    for (m <- clazz.getDeclaredMethods()) {
      val setterPrefix = clazz.getName.replace('.', '$') + "$_setter_$"
      val encoded = m.getName
      if (encoded.startsWith(setterPrefix)) {
        val decoded = encoded.substring(setterPrefix.length, encoded.length - 4)
        encodedToDecodedSetterNameMap += (encoded -> decoded)
      }
    }

    var result = Map[String, Object]()

    import java.lang.reflect.InvocationHandler
    import java.lang.reflect.Proxy

    init.get.invoke(null, Proxy.newProxyInstance(cl, Array[Class[_]](clazz),
      new InvocationHandler {
        def invoke(proxy : Object,
                   method : java.lang.reflect.Method,
                   args : Array[Object]) = {
          encodedToDecodedSetterNameMap.get(method.getName) match {
            case Some(decodedName) => result += (decodedName -> args(0))
            case _                 =>
          }
          null
        }
      }))

    result
  }                                               //> traitInits: (clazz: Class[_])Map[String,Object]

  trait A {
    val x : Int = 3
    val y : String = "y"
  }

  traitInits(classOf[A])                          //> res0: Map[String,Object] = Map(x -> 3, y -> y)
}
于 2013-04-05T14:11:18.100 回答