使用 javassist anonymous inner class 时如何访问 outer class 的实例变量?

When using javassist anonymous inner class how to access instance variables of outer class?

下面是匿名内class定义:

package com.demo;

public class OuterClass {

    private static int staticNum = 1;

    private int instanceNum  = 2;

    public Runnable redefineMe() {
        return new Runnable() {
            @Override
            public void run() {
                System.out.printf("staticNum %d, instanceNum %d \n", staticNum, instanceNum);
            }
        };
    }
}

下面是运行宁的例子:

package com.demo;

import javassist.*;

public class Test {
    public static void main(String[] args) throws NotFoundException, CannotCompileException {
        ClassPool pool = ClassPool.getDefault();
        CtClass outerClass = pool.get("com.demo.OuterClass");
        CtClass[] nestedClasses = outerClass.getNestedClasses();
        CtMethod run = nestedClasses[0].getDeclaredMethod("run");

        run.setBody("{" +
                "System.out.println(\"staticNum: \" + com.demo.OuterClass.staticNum);" +  // print: staticNum: 1
                // I tried to use the following code to access instance variables, but a compilation error occurred
                // "System.out.println(\"staticNum: \" + instanceNum);" +  // [source error] no such field: instanceNum
                // "System.out.println(\"staticNum: \" + com.demo.OuterClass.this.instanceNum);" +  // [source error] missing member name
                // "System.out.println(\"staticNum: \" + com.demo.OuterClass.access0(com.demo.OuterClass.this));" +  // [source error] missing member name
                "}");
        nestedClasses[0].toClass();
        outerClass.toClass();
        new OuterClass().redefineMe().run();
    }
}

我想重新定义运行方法的主体,但是在主体

中无法访问外部class的实例变量

根据manual,Javassist不支持内class生成,但声称支持读取和修改它们:

  • Inner classes or anonymous classes are not supported. Note that this is a limitation of the compiler only. It cannot compile source code including an anonymous-class declaration. Javassist can read and modify a class file of inner/anonymous class.

我想编译器支持在您想要在 Javassist 编译的源代码中使用内部 class 特定语法糖的地方结束。一个简单的解决方法是使用“神圣知识”,如下所示:

$ javap classes/com/demo/OuterClass$1.class
Compiled from "OuterClass.java"
class com.demo.OuterClass implements java.lang.Runnable {
  final com.demo.OuterClass this[=10=];
  com.demo.OuterClass(com.demo.OuterClass);
  public void run();
}

哦,看,this[=13=]!让我们试试看:

run.setBody("{" +
  "System.out.println(\"staticNum: \" + com.demo.OuterClass.staticNum);" +
  "System.out.println(\"instanceNum: \" + this[=11=].instanceNum);" +
  "}");

现在我们得到控制台输出:

staticNum: 1
instanceNum: 2

我不知道,此解决方法在 Java 版本和编译器风格中的稳定性和可靠性如何。

P.S.: 如果将匿名内部 class 更改为 lambda,class 文件看起来完全不同,您是又输了我在 Javassist 存储库中没有发现任何提及 lambda 的内容,只有几个未解决的问题报告问题。


更新: 我创建了 Javassist issue #358 以跟踪它。