使用 Java 代理 (Byte Buddy) 将现有字段从私有字段转换为 public

Using Java agent (Byte Buddy) to transform existing field from private to public

我正在尝试从我的应用程序代码中的 FilePermission 有效地访问 cpath 字段。请注意,这应该可以通过适当的安全管理器来完成,所以如果可能的话,我不想求助于调用 setAccessible。

我已经在使用 Byte Buddy 支持的代理,它使用 AgentBuilder。以下是 AgentBuilder 的作用:

public static void premain(String arg, Instrumentation inst) {
    install(arg, inst);
}

public static void agentmain(String arg, Instrumentation inst) {
    install(arg, inst);
}

private static void install(String arg, Instrumentation inst) {
    Transformer filePermissionTransformer = (builder, typeDescription, classLoader, module) ->
        builder.field(named("cpath")).transform(ForField.withModifiers(Visibility.PUBLIC));

    new AgentBuilder.Default()
        .with(new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE))
        .with(Listener.StreamWriting.toSystemOut())
        .with(InitializationStrategy.NoOp.INSTANCE)
        .with(RedefinitionStrategy.REDEFINITION)
        .with(TypeStrategy.Default.REDEFINE)
        .ignore(none())
        .type(named("java.io.FilePermission"))
        .transform(filePermissionTransformer)
        .installOn(inst);
}

我用sysout listener可以看到确实在改造:

[Byte Buddy] DISCOVERY java.io.FilePermission [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.io.FilePermission [null, null, loaded=true]
[Byte Buddy] COMPLETE java.io.FilePermission [null, null, loaded=true]

然后我尝试获取应用程序中的字段:

if (perm instanceof FilePermission) {
        Field cpathField = perm.getClass().getDeclaredField("cpath");
        String cpath = (String) cpathField.get(perm);
}

但这会导致 IllegalAccessException,原因告诉我它仍然是 "private transient"。

只是为了好玩,我尝试使用 .annotateField 而不是 .transform 并使用 Deprecated 注释。这确实有效,并且在运行时我可以从声明的字段中检索注释。所以这至少证明了现场转换的路径是有效的……只是出于某种原因不是这个特定的转换。

仅供参考,不,这不是我使用 Byte Buddy 的 唯一 原因...我还用它来重新定义其他一些东西。我可以自己计算 cpath 参考 OpenJDK 代码,但我希望它尽可能高效......并且由于 FilePermission 已经在内部完成工作我宁愿获取价值而不是两次工作.由于我已经在其他事情上使用仪器,这似乎是一个更优雅的解决方案。

干杯!

如果你运行程序属性 -Dnet.bytebuddy.dump设置到某个文件夹,字节好友将提取生成的class文件并将它们写入指定文件夹.如果您使用 javap 来调查为 FilePermission 创建的文件,您会得到:

Compiled from "FilePermission.java"
public final class java.io.FilePermission extends java.security.Permission implements java.io.Serializable {
  public transient java.lang.String cpath;
  public java.io.FilePermission(java.lang.String, java.lang.String);
  java.io.FilePermission(java.lang.String, int);
  public boolean implies(java.security.Permission);
  boolean impliesIgnoreMask(java.io.FilePermission);
  public boolean equals(java.lang.Object);
  public int hashCode();
  int getMask();
  public java.lang.String getActions();
  public java.security.PermissionCollection newPermissionCollection();
  static java.lang.String access[=10=]0(java.io.FilePermission);
}

如您所见,cpath 字段已呈现 public 但 JVM 似乎并不关心这一点。如果您使用相同的 cpath 字段和 运行 上面的转换定义 class,这将起作用。

我只能怀疑 JVM 在某处对这个 属性 进行了硬编码。你可以在JVM邮件列表上问为什么会这样,我只能猜测。