2

问题

一位朋友提出了一个有趣的问题。给定以下代码:

public class OuterClass {

    private String message = "Hello World";

    private class InnerClass {
        private String getMessage() {
            return message;
        }
    }

}

从外部类,我如何打印message变量内容?当然,不允许更改方法或字段的可访问

这里的来源,但它是一个法国博客)


解决方案

解决这个问题的代码如下:

try {
    Method m = OuterClass.class.getDeclaredMethod("access$000", OuterClass.class);
    OuterClass outerClass = new OuterClass();
    System.out.println(m.invoke(outerClass, outerClass));
} catch (Exception e) {
    e.printStackTrace();
}

请注意,access$000方法名称并不是真正标准的(即使这种格式是强烈推荐的格式),并且某些 JVM 会命名此方法access$0。因此,更好的解决方案是检查合成方法:

Method method = null;
int i = 0;
while ((method == null) && (i < OuterClass.class.getDeclaredMethods().length)) {
    if (OuterClass.class.getDeclaredMethods()[i].isSynthetic()) {
        method = OuterClass.class.getDeclaredMethods()[i];
    }
    i++;
}
if (method != null) {
    try {
        System.out.println(method.invoke(null, new OuterClass()));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

所以这个问题的有趣之处在于突出了合成方法的使用。使用这些方法,我可以像在解决方案中那样访问私有字段。当然,我需要使用反射,而且我认为使用这种东西可能会很危险......

问题

作为开发人员,对我来说,合成方法的兴趣是什么?什么是使用合成物有用的好情况?

4

3 回答 3

1

正如您所演示的,Java 访问修饰符只是提供信息,可以通过使用反射来规避它们。所以你的问题大致相当于“绕过访问修饰符有什么好处?” 除了恶意目的之外,还会想到调试;例如,您可以从库外部记录某些库内部的内部状态,而根本无需接触库代码本身。另一个例子是与脚本语言的合作;如果您在编译时不知道哪些类和方法可用,反射会非常有用。例如,Jython的内部使用了大量的反射。

于 2010-03-17T07:47:49.550 回答
1

作为开发人员,您对合成方法有什么兴趣?好吧,对于初学者来说,不要调用它们(出于其他回答者解释的原因)。

但是要记住一件有趣的事情,特别是如果您正在编写 Java 代码以在安全敏感的环境中运行,那就是合成方法可能会为攻击者提供进入您的私有字段的后门。

当然,有一些重要的“如果”——我的意思是,必要条件——让这样的漏洞真正重要:

  1. 攻击者实际上必须在您的 JVM 中运行代码。大多数时候这不是问题,因为唯一运行的代码是您自己的。
  2. 攻击者的代码需要和你的内部类在同一个包中(因为合成方法被声明为包私有)。
  3. 攻击者的代码必须从与您的类相同的类加载器中加载。如果您让不受信任的代码在您的 JVM 中运行(条件 #1),那么您最好为不受信任的代码使用单独的类加载器。
于 2010-03-17T07:48:20.093 回答
0

您永远不应该使用反射调用合成访问器方法,它们可能会根据您使用的编译器而改变。例如,在我的机器上在 jdk1.6 上运行您的解决方案失败,因为access$000找不到该方法。

合成访问器是一种隐蔽的编译器破解方法,可以绕过内部类是 Java 1.1 的补充,并且 VM 规范从未更改以适应它们的事实。

于 2010-03-17T07:41:08.900 回答