2012-09-02 79 views
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的包。裏面有一個方法。 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的:在行動書打開我的身邊,現在,在所有的加載時織造示例中,它提到程序必須以-javaagent標誌開始。由於我的proram是一個插件,所以不可能發生這種情況。

我也看了成ASM。我發現了一個關於構建探查器的奇妙教程(基本上我想做的)here

問題在於它在啓動時再次使用-javaagent標誌,以及公共靜態premain,因此它不適合,因爲我只有onEnable和onDisable。

然後我發現了關於Java的Attach API。從它的外觀上看,它將允許我在加載一個類之後附加我的代理程序,分析器。這看起來很完美,但經過半個小時的搜索,我找不到一個我能理解的好例子。

任何人都可以幫忙嗎?這是一個二合一的問題:AspectJ可以用於這個嗎?如果是這樣,怎麼樣?另外,如果它不能,有人能指出我正確的方向與ASM分析器一起使用Attach API嗎?

在此先感謝!

+0

爲什麼你不能在你的插件加載之前編織服務? (我假設你控制整個jvm,因爲你指出你可以控制服務jar)。 – jtahlborn

+0

@jtahlborn服務本身就是這樣運作的。 Loader讀取一個指定要加載哪個服務的yml文件。我正在做的是創建服務的副本,更改我需要的類,然後修改yml文件以指向我的新服務。在我改裝的服務啓動時,我刪除了香草服務。服務加載後,我的插件由服務加載。 – Xyene

+0

也許我只是不明白你的評論,但我沒有看到LTW不適合你的理由。它是爲你想做的事情發明的:編織類時加載 - 因此名稱LTW。 – kriegaex

回答

2

我做到了!

我爲profiler概述了here創建了運行時附加程序。它的基本原理是,將前綴重命名爲'agentmain'。

我做了與其它有用的功能沿着附接器一個實用程序類。通過與代理人建立一個罐子,以及一個聲明它可以分析的清單,工作人員的工作。 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()); 

記住附加器要Agent.class是主要的代理。你可以很容易地改變它。其餘的類[]是要包含在臨時代理中的類.jar

如果您的IDE抱怨「UnsatisfiedLinkError」,這是因爲附加。(dll | so)所需的只有JDK。只需將其複製到%JAVA_PATH%/ jre/lib中即可。另外,爲JDK的tools.jar添加一個引用,因爲它包含所有com.sun導入。

編輯:我有一個工作github的例子是任何人都可能認爲這是有用的。它的here