合成方法的惩罚是什么?

16

在Eclipse下开发Java应用程序时,我收到了关于“通过合成方法访问的方法/值”的警告。解决方案很简单,只需将私有访问修饰符更改为默认级别。

这让我想知道:使用合成方法会有什么代价吗?有吗?我认为编译器/Eclipse会发出警告,但是这是非常重要的东西还是可以安全地忽略掉的东西?

我在这里没有看到这些信息,所以我来问一下。

2个回答

16

Eclipse警告您可能会暴露您认为是私密的信息。如下所示,合成访问器可被恶意代码利用。

如果您的代码需要在安全的虚拟机中运行,则使用内部类可能是不明智的。如果您可以使用反射并完全访问所有内容,则合成访问器不太可能产生可衡量的差异。


例如,考虑以下类:

public class Foo {
  private Object baz = "Hello";
  private class Bar {
    private Bar() {
      System.out.println(baz);
    }
  }
}

Foo的签名实际上是:

public class Foo extends java.lang.Object{
    public Foo();
    static java.lang.Object access$000(Foo);
}

access$000 是自动生成的,目的是让分离的类 Bar 可以访问 baz,并且会被标记为Synthetic属性。生成的确切名称因实现而异。常规编译器不允许你编译此方法,但你可以使用 ASM(或类似工具)生成自己的类,如下所示:

import org.objectweb.asm.*;
public class FooSpyMaker implements Opcodes {
  public static byte[] dump() throws Exception {
    ClassWriter cw = new ClassWriter(0);
    cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "Spy", null, "java/lang/Object",null);
    MethodVisitor ctor = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
    ctor.visitCode();
    ctor.visitVarInsn(ALOAD, 0);
    ctor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
    ctor.visitInsn(RETURN);
    ctor.visitMaxs(1, 1);
    ctor.visitEnd();
    MethodVisitor getBaz = cw.visitMethod(ACC_PUBLIC, "getBaz",
        "(LFoo;)Ljava/lang/Object;", null, null);
    getBaz.visitCode();
    getBaz.visitVarInsn(ALOAD, 1);
    getBaz.visitMethodInsn(INVOKESTATIC, "Foo", "access$000",
        "(LFoo;)Ljava/lang/Object;");
    getBaz.visitInsn(ARETURN);
    getBaz.visitMaxs(1, 2);
    getBaz.visitEnd();
    cw.visitEnd();
    return cw.toByteArray();
  }
}

这创建了一个简单的类,称为Spy,它可以让您调用access$000

public class Spy extends java.lang.Object{
    public Spy();
    public java.lang.Object getBaz(Foo);
}

使用这个方法,你可以检查baz的值,而不需要使用反射或任何暴露它的方法。

public class Test {
  public static void main(String[] args) {
    Foo foo = new Foo();
    Spy spy = new Spy();
    System.out.println(spy.getBaz(foo));
  }
}

Spy 的实现要求它与 Foo 在同一个包中,并且 Foo 不在密封的 JAR 中。


1
我认为使用反射来访问/修改私有字段要容易得多。所以我不认为访问器会增加任何问题。 ;) - Peter Lawrey
4
@Peter Lawrey - 我在我的开场白中试图阐述这一观点——只有在特定(异常)情况下才能将这些内容视为漏洞利用:1)您正在使用禁止反射的安全管理器(或至少 setAccessible )2)攻击者可以运行他们自己的代码3)包未被封装。我们在谈论编写安全的 Applet 类似框架。这个例子的原因是因为一旦你开始思考它,很难不去尝试它。 - McDowell
@McDowell 我怎样才能在 Foo 中看到 access$000?当我尝试打印 Class<Foo> 内的所有方法时,它并没有打印出 access$000 - Jatin
1
@Jatin 请查看 javap 工具。 - McDowell

4

我相信处罚不过是一个额外的方法调用。换句话说,对于几乎所有的使用情况都没有意义。

如果它在特别繁忙的路径中,你可能会感到担忧,但你应该先通过分析器确定这是否真正是你的问题。

我只是关闭警告。


2
由于合成方法总是微不足道的,因此应该进行内联。这只会在具有较少优化(例如某些J2ME)的JVM中产生差异。我不喜欢合成方法,因为它们会在堆栈跟踪中污染调用堆栈。;) - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接