2014-05-20 109 views
14

我正在研究一個項目,其中有很多由庫創建的對象,並且我無法訪問這些對象的創建過程。是否有可能在Java中運行時實現接口?

下面的片段可以作爲一個很好的例子來說明我的問題。

代碼:

public class Clazz { 
    //The contents of Clazz are irrelevant 
    //Clazz is NOT my class. I have no access to its internal structure. 
    //However, I do have access to Clazz objects that were created elsewhere. 
} 

ExampleInterface是clazz中可能會或可能不會在編譯時實現一個接口。

代碼:

public interface ExampleInterface { 
    public void run(); 
} 

下面的代碼是,我遇到了這個問題。看看下面的注意事項:

  1. run()當c是ExampleInterface一個實例只調用。
  2. getRunConditions(Clazz c)executeClazz(Clazz c)都是我無權訪問的類中的私有方法。
  3. 在編譯時,Clazz而不是包含一個名爲run()的方法。
  4. ExampleExecutor是不是我的班。我無法使用任何 方式(我甚至無法獲得該類的實例)。

代碼:

public class ExampleExecutor { 
    public void executeClazz(Clazz c) { 
     if ((c instanceof ExampleInterface) && getRunConditions(c)) { 
      ExampleInterface ex = (ExampleInterface) c; 
      ex.run(); 
     } 
    } 
} 

顯然以下方法在語法上不能夠,但它確實是我想要的目的。基本上,如果c尚未實現ExampleInterface,請將c設置爲執行ExampleInterface,然後提供必須覆蓋的方法。

需要注意以下的:

  1. extendInterface(Name of Interface)虛構的語法,試圖說明我的目標創造我 。
  2. run()必須在這裏定義(在運行時)。
  3. 我不能使用包裝或代理類作爲解決方案。 IE,Clazz對象必須結束執行ExampleInterface,並且我無法使用解決方法。 (參考this link如果你想知道爲什麼)。

代碼:

public void implementInterface(Clazz c) { 
    if (!(c instanceof ExampleInterface)) { 
     c.extendInterface(ExampleInterface { 
      @Override 
      public void run() { 
       //code 
      } 
     }); 
    } 
} 

爲了澄清,我正在運行到的問題是,我需要總是知道什麼時候run()被稱爲Clazz。如果Clazz曾經不執行ExampleInterface,我不知道什麼時候應該調用run()

與此同時,我還想偶爾爲run()添加支持,默認情況下不支持。因爲我無法訪問創建Clazz對象,所以我無法通過自己實現接口來完成此操作。

問題:簡單地說,是否可以在運行時實現接口(並提供所需的方法)?

注:而唯一的解決方案可能需要反思(如果有的話,請張貼下文),我使用的庫有安全管理器,阻止使用所有的反射。 IE,反思性的解決方案在將來可能對其他人有用,但對我來說沒用。

此外,我並不是說只在自己的程序中使用庫。一個已經運行的主機應用程序(這是我使用的庫)是遵循並且然後運行我爲它編寫的代碼。如果該應用程序不喜歡我提供的任何代碼(IE,與其安全管理器衝突),則代碼甚至從不編譯。

爲什麼我需要這樣做:

它與我使用的圖書館做。因爲ExampleExecutor是我無法訪問的方法,而且我無法控制Clazz的創建,所以無法確定run()何時執行。

我需要知道什麼時候run()被執行的原因是因爲實際上,run()是一個事件處理程序,它是我正在使用的庫的一部分。

例如:mouseClicked(CustomMouseEvent evt)可能是一個方法,它是接口CustomMouseListener的一部分。有時Clazz的實例當鼠標點擊時(因此繼承CustomMouseListener),我正在小心處理,而其他時間則不會。

Clazz實例不同,我總是在意鼠標是否被點擊,並始終需要觸發該事件。

在現實中,ExampleInterface實際上是以下幾點:

public interface CustomMouseListener { 
    public void mouseClicked(CustomMouseEvent evt); 
    public void mousePressed(CustomMouseEvent evt); 
    public void mouseReleased(CustomMouseEvent evt); 
    //etc 
} 
+1

要說清楚,你是不是在問匿名課程? http://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html –

+0

你可以用'new SomeInterface(){...}'在運行時創建一個實現接口的實例,但它贏得了' t是其他任何東西的實例(當然除了Object和超接口)。這聽起來像你正在獲取某個類的實例,只需要檢查它是否實現了一個特定的接口,如果是的話,就用它做些什麼。是對的嗎? –

+0

不,不。參考我已經鏈接的其他問題,這可能會對我正在努力完成的事情有所瞭解。 代碼'if(c extends ExampleInterface){'已被內部庫調用。我需要確保如果Clazz在編譯時沒有實現'ExampleInterface',我可以強制它在運行時執行此操作。 – user3144349

回答

4

做你所說的其實是使用字節代碼裝備的唯一途徑。您可以添加一個代理程序,它在加載之前更改要修改的clazz的字節代碼。

您需要在加載時執行此操作的原因是,許多JVM不允許您更改字段,有些不允許您在加載類之後添加方法。

更簡單的解決方案是反編譯類,修改它並重新編譯。假設該類可以反編譯,這將爲您節省大量時間和精力。

the library I am using has a security manager that blocks the use of all reflection

這是一個奇怪的選擇,因爲你可以在調用庫前,把你自己的安全管理器,它不能阻止你做任何事情。

+0

嗯,也許圖書館不是最好的詞。我正在編寫的代碼是從另一個基於我正在使用的庫的應用程序中調用的。如果宿主應用程序檢測到代碼中的某些內容,我提供它不喜歡(與安全管理器衝突),它不會編譯和運行我的任何代碼。 – user3144349

+0

@ user3144349如果您可以更改命令行,則可以執行任何操作。如果你不能改變命令行,你只限於你的框架允許你做的事情。這是擁有SecurityManager的關鍵。 ;) –

+0

我沒有訪問命令行:( – user3144349

0

我不認爲你想要什麼是可能的;有Dynamic Proxies,但它們使用反射,並且看起來不太可能有權訪問類加載器(您可以在其中設置自己的即時操作字節碼)。

5

您可以使用java instrumentation API(強制)將類調整到接口。 APM,AOP框架和剖析器通常使用此技術,以便在運行時將日誌記錄和度量標準測試代碼插入到目標類中。應用程序直接使用這種技術是非常罕見的。如果我在生產代碼中看到這個,這將是一個大紅旗。

儘管如此,

鑑於這些clazz中:

package com.sabertiger.example; 

public class Clazz { 
    public void purr(){ 
     System.out.println("Hello world"); 
    } 

} 

接口

package com.sabertiger.example; 

public interface ExampleInterface { 
    void run(); 
} 

執行人

package com.sabertiger.example; 

public class ExampleExecutor { 
    public static void main(String[] args) { 
     Clazz c=new Clazz(); 
     // Normally a ClassCastException 
     ExampleInterface i=(ExampleInterface)(Object)(Clazz) c; 
     i.run(); 
    } 
} 

正常運轉中產生這個錯誤:

Exception in thread "main" java.lang.ClassCastException: 
    com.sabertiger.example.Clazz cannot be cast to 
    com.sabertiger.example.ExampleInterface 
    at com.sabertiger.example.ExampleExecutor.main(ExampleExecutor.java:7) 

你可以把它通過提供缺少的接口和實現工作,通過變換類:

package com.sabertiger.instrumentation; 

import java.lang.instrument.ClassFileTransformer; 
import java.lang.instrument.IllegalClassFormatException; 
import java.lang.instrument.Instrumentation; 
import java.security.ProtectionDomain; 

import javassist.ClassPool; 
import javassist.CtClass; 
import javassist.CtMethod; 
import javassist.CtNewMethod; 

public class ExampleInterfaceAdapter implements ClassFileTransformer { 

    public static void premain(String agentArgument, Instrumentation instrumentation) { 
     // Add self to list of runtime transformations 
     instrumentation.addTransformer(new ExampleInterfaceAdapter()); 
    } 

    @Override 
    // Modify only com.sabertiger.example.Clazz, return all other unmodified 
    public byte[] transform(ClassLoader loader, String className, 
      Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
      byte[] classfileBuffer) throws IllegalClassFormatException { 

     if(className.matches("com/sabertiger/example/Clazz")) { 
      return addExampleInterface(className, classfileBuffer);    
     } else { 
      return classfileBuffer; 
     } 
    } 

    // Uses javassist framework to add interface and new methods to target class 
    protected byte[] addExampleInterface(String className, byte[] classBytecode) { 
     CtClass clazz= null; 
     try { 
      ClassPool pool = ClassPool.getDefault(); 
      clazz = pool.makeClass(new java.io.ByteArrayInputStream(classBytecode)); 

      String src= 
       "{   "+ 
       " purr(); "+ 
       "}   "; 

      //Add interface 
      CtClass anInterface = pool.getCtClass("com.sabertiger.example.ExampleInterface"); 
      clazz.addInterface(anInterface); 

      //Add implementation for run method 
      CtMethod implementation = CtNewMethod.make(
        CtClass.voidType, 
        "run", 
        new CtClass[0], 
        new CtClass[0], 
        src, 
        clazz); 
      clazz.addMethod(implementation); 

      classBytecode=clazz.toBytecode(); 
     } catch(Throwable e) { 
      throw new Error("Failed to instrument class " + className, e); 
     } 
     return classBytecode; 
    } 

} 

和所需的MANIFEST.MF

Manifest-Version: 1.0 
Premain-Class: com.sabertiger.instrumentation.ExampleInterfaceAdapter 
Boot-Class-Path: javassist.jar 

包一切都變成罐子使其工作:

jar -tf agent.jar 
META-INF/MANIFEST.MF 
com/sabertiger/instrumentation/ExampleInterfaceAdapter.class 

現在我們能夠通過clazz中以ExampleExecutor

java -javaagent:agent.jar -classpath ..\instrumentation\bin com.sabertiger.example.ExampleExecutor 
Hello world 
-1

根據您的Java版本,你可以使用lambda表達式(與Java 8)。

的代碼會相對簡單:

Clazz o = .... // Here you obtain your object through third party library 
ExampleInterface yourInterface = o::run; 
yourInterface.run(); 

請注意,這僅適用於與一個方法接口。兩個簽名(界面和Clazz)必須匹配。

相關問題