3

我正在为服务开发一个插件。为了使其发挥作用,它需要一些服务不提供的数据。

插件具有严格的加载/卸载规范。一个裸插件如下所示:

public class Plugin extends JavaPlugin 
{    
    @Override
    public void onEnable() {} //Plugin enters here. Comparable to main(String[] args)

    @Override
    public void onDisable() {} //Plugin exits here, when service shuts down.
}

有一个名为 org.service.aClass 的包。里面有一个Method。aMethod 看起来像这样:

public boolean aMethod(boolean bool) {
return bool;
}

一个过于简单的场景,但它确实有效。每当调用 aMethod 时,我的插件都需要知道 bool 的值。这对我的程序绝对至关重要;我没有其他方法可以获得该值。

我会建议 aMethod,但由于我的插件是在服务之后加载的,所以这不起作用。据我了解,加载时间编织也不适合这里,因为加载后。

尽管它不起作用,但这是我正在使用的方面,以防它可能有用:

public aspect LogAspect {

    pointcut publicMethodExecuted(): execution(* org.service.aClass.aMethod(..));

    after(): publicMethodExecuted(){
    cat(String.format("Entered method: %s.",
        thisJoinPoint.getSignature()));

    List<Object> arguments = Arrays.asList(thisJoinPoint.getArgs());
    List<Object> argTypes = new ArrayList<Object>();
    for (Object o: arguments) {  
        argTypes.add(o.getClass().toString());

    }

    cat(String.format("With argument types: %s and values: %s.",
            argTypes, arguments));


    cat(String.format("Exited method: %s.", thisJoinPoint.getSignature()));
    }

 public void cat(Object dog) {
    System.out.println("[TEST] " + dog);
    }

}

我现在在我旁边打开了 AspectJ: In Action 书籍,在所有加载时编织示例中,它都提到程序必须以 -javaagent 标志启动。因为我的程序是一个插件,所以这不可能发生。

我还研究了 ASM。我在这里找到了一个关于构建分析器的精彩教程(基本上是我想做的)。

问题是它在启动时再次使用 -javaagent 标志,以及公共静态 premain,所以它不合适,因为我只有 onEnable 和 onDisable。

然后我发现了 Java 的Attach API。从外观上看,这将允许我在加载类之后附加我的代理,即探查器。看起来很完美,但经过半小时的搜索,我找不到一个我能理解的好例子。

有人可以帮忙吗?这是一个二合一的问题:AspectJ 可以用于此目的吗?如果是这样,怎么办?此外,如果它不能,有人可以指出我使用带有 ASM 分析器的 Attach API 的正确方向吗?

提前致谢!

4

1 回答 1

2

我做到了!

我为此处概述的分析器创建了一个运行时附加程序。基本上就是这样,将 premain 重命名为“agentmain”。

我创建了一个 Util 类,它具有附加器以及其他有用的功能。附加程序通过使用代理创建一个 jar 来工作,并带有一个说明它可以分析的清单。Util 类如下所示:

    public class Util {

    /**
     * Gets the current JVM PID
     * @return
     * Returns the PID
     * @throws Exception
     */

    public static String getPidFromRuntimeMBean() {
    String jvm = ManagementFactory.getRuntimeMXBean().getName();
    String pid = jvm.substring(0, jvm.indexOf('@'));
    return pid;
    }

    /**
     * Attaches given agent classes to JVM
     * 
     * @param agentClasses
     * A Class<?>[] of classes to be included in agent
     * @param JVMPid
     * The PID of the JVM to attach to
     */

    public static void attachAgentToJVM(Class<?>[] agentClasses, String JVMPid) {

    try {


    File jarFile = File.createTempFile("agent", ".jar");
    jarFile.deleteOnExit();

    Manifest manifest = new Manifest();
    Attributes mainAttributes = manifest.getMainAttributes();
    mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
    mainAttributes.put(new Attributes.Name("Agent-Class"),
        Agent.class.getName());
    mainAttributes.put(new Attributes.Name("Can-Retransform-Classes"),
        "true");
    mainAttributes.put(new Attributes.Name("Can-Redefine-Classes"), "true");

    JarOutputStream jos = new JarOutputStream(new FileOutputStream(
        jarFile), manifest);


    for(Class<?> clazz: agentClasses) {         
        JarEntry agent = new JarEntry(clazz.getName().replace('.',
            '/')
            + ".class");
        jos.putNextEntry(agent);

    jos.write(getBytesFromIS(clazz.getClassLoader()
        .getResourceAsStream(
            clazz.getName().replace('.', '/') + ".class")));
    jos.closeEntry();
    }

    jos.close();
    VirtualMachine vm = VirtualMachine.attach(JVMPid);
    vm.loadAgent(jarFile.getAbsolutePath());
    vm.detach();
    } catch (Exception e) {
        e.printStackTrace();
    }

    }

    /**
     * Gets bytes from InputStream
     * 
     * @param stream
     * The InputStream
     * @return 
     * Returns a byte[] representation of given stream
     */

    public static byte[] getBytesFromIS(InputStream stream) {

    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    try {
        int nRead;
        byte[] data = new byte[16384];

        while ((nRead = stream.read(data, 0, data.length)) != -1) {
        buffer.write(data, 0, nRead);
        }

        buffer.flush();
    } catch (Exception e) {
        System.err.println("Failed to convert IS to byte[]!");
        e.printStackTrace();
    }

    return buffer.toByteArray();

    }

    /**
     * Gets bytes from class
     * 
     * @param clazz    
     * The class
     * @return
     * Returns a byte[] representation of given class
     */

    public static byte[] getBytesFromClass(Class<?> clazz) {            
    return getBytesFromIS(clazz.getClassLoader().getResourceAsStream( clazz.getName().replace('.', '/') + ".class"));   
    }

}

为了清楚起见,我包含了 JavaDoc 注释。

使用它的一个例子是:

Util.attachAgentToJVM(new Class<?>[] { Agent.class, Util.class,
        Profile.class, ProfileClassAdapter.class,
        ProfileMethodAdapter.class }, Util.getPidFromRuntimeMBean());   

请记住,attacher 希望 Agent.class 成为主要代理。您可以轻松更改此设置。Class[] 的其余部分是要包含在临时 agent.jar 中的类

如果您的 IDE 抱怨“UnsatisfiedLinkError”,那是因为为此所需的 attach.(dll|so) 仅随 JDK 提供。只需将其复制粘贴到您的 %JAVA_PATH%/jre/lib 中。另外,添加对 JDK 的 tools.jar 的引用,因为它包含所有 com.sun 导入。

编辑:我有一个有效的 github 示例,任何人都可能认为这很有用。它在这里

于 2012-09-05T23:15:28.550 回答