【问题标题】:Adding a class to the class path with a Java Agent使用 Java 代理将类添加到类路径
【发布时间】:2017-10-19 14:38:16
【问题描述】:

我正在使用 Java 代理和 Javassist 向一些 JDK 类添加一些日志记录。本质上,当系统加载一些 TLS 类时,Javassist 会向它们添加一些额外的字节码,以帮助我调试一些连接问题。

这就是问题所在,因为这个类包含在代理 jar 中:

package com.something.myagent;
public class MyAgentPrinter {
    public static final void sayHello() {
        System.out.println("Hello!");
    }
}

在我的代理的转换方法中,假设我尝试使用 javassist 调用该类:

// this is only called for sun.security.ssl.Handshaker
ClassPool cp = getClassPool(classfileBuffer, className);
CtClass cc = cp.get(className);
CtMethod declaredMethod = cc.getDeclaredMethod("calculateKeys");
declaredMethod.insertAfter("com.something.myagent.MyAgentPrinter.sayHello();");
cc.freeze();
return cc.toBytecode();

你认为这行得通,但我得到了这个:

java.lang.NoClassDefFoundError: com/something/myagent/MyAgentPrinter
    at sun.security.ssl.Handshaker.printLogLine(Handshaker.java)
    at sun.security.ssl.Handshaker.calculateKeys(Handshaker.java:1160)
    at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:292)

有没有办法将该类 [MyAgentPrinter] 添加到应用程序的类路径中?

【问题讨论】:

    标签: java classpath bytecode javassist javaagents


    【解决方案1】:

    您的代理的 jar 文件已添加到类路径中,由 the java.lang.instrument package documentation 指定:

    代理类将由系统类加载器加载(参见ClassLoader.getSystemClassLoader)。这是类加载器,它通常加载包含应用程序main 方法的类。 premain 方法将在与应用程序 main 方法相同的安全和类加载器规则下运行。

    这就是为什么 Javassist 在转换字节码时可以找到 Agent 的类的原因。

    问题似乎是您正在转换可能由引导加载程序或扩展加载程序加载的sun.** 类。标准的类加载委托是
    application loader → extension loader → bootstrap loader,因此应用程序加载器可用的类对扩展或引导加载器加载的类不可用。

    因此,要使它们可用于所有类,您必须将代理的类添加到引导加载程序:

    public class MyAgent {
        public static void premain(String agentArgs, Instrumentation inst) throws IOException {
            JarURLConnection connection = (JarURLConnection)
                MyAgent.class.getResource("MyAgent.class").openConnection();
            inst.appendToBootstrapClassLoaderSearch(connection.getJarFile());
    
            // proceed
        }
    }
    

    在任何其他操作之前执行此操作至关重要,即在加载您希望可用于检测代码的类之前。这意味着代理类本身,即包含premain 方法的类不能被检测代码访问。 Agent 类也不应该直接引用 MyAgentPrinter 以避免意外的提前加载。

    更可靠的方法是将Boot-Class-Path 条目添加到代理 jar 的清单中,请参阅the “Manifest Attributes” section of the package documentation,以便在代理启动之前添加该条目。但是,jar 文件的名称以后不能更改。

    【讨论】:

    • 你说The agent class will be loaded by the system class loader (see ClassLoader.getSystemClassLoader). 但是问题中的问题是找不到类AgentPrinter。如果AgentPrinterMyAgent 位于同一目录中,那么您是对的,否则可能就是我所说的情况
    • @rakwaht:我的回答解释了为什么 Javassist 没有失败,即找到了类,而 instrumented code 确实失败了。
    • 看来你是对的。我删除了我的答案,因为它是错误的。 PS:我投了赞成票:)
    • 非常棒的答案。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多