2015-05-14 56 views
1

我正在使用java來做一些與C++動態庫使用相同的事情。使用由Classloader創建的對象無需接口調用或反映調用?

我沒有找到直接使用相同類對象而沒有反射調用樣式代碼的方法。

這是我的動態庫代碼,我把它做成一個罐子。

package com.demo; 

public class Logic { 
    public String doWork() { 
     System.out.println("Hello from Dll"); 
     return "Dll"; 
    } 
} 

在我的主應用程序,我可以創建一個URLClassLoader實例,調用由反映是好的:

public class Main { 
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { 
     File file = new File("C:\\plugin.jar"); 
     URL url = file.toURI().toURL(); 
     URL[] urls = {url}; 

     ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); 
     ClassLoader loader = new URLClassLoader(urls, parentLoader); 
     Thread.currentThread().setContextClassLoader(loader); 

     Class<?> clazz = loader.loadClass("com.demo.Logic"); 

     System.out.println("New Instance!!"); 
     Object logic = clazz.newInstance(); 
     Method method = logic.getClass().getMethod("doWork"); 
     method.invoke(logic); 
} 

輸出:

New Instance!! 
Hello from Dll 

但是,當我更改代碼,而無需使用Reflect invoke:

public class Main { 
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { 
     File file = new File("C:\\plugin.jar"); 
     URL url = file.toURI().toURL(); 
     URL[] urls = {url}; 

     ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); 
     ClassLoader loader = new URLClassLoader(urls, parentLoader); 
     Thread.currentThread().setContextClassLoader(loader); 

     Class<?> clazz = loader.loadClass("com.demo.Logic"); 

     Logic logic = (Logic)clazz.newInstance(); 
     logic.doWork(); 
    } 
} 

編譯成功(與外部模塊編譯),但是當我運行程序時,它沒有在該行Logic logic = (Logic)clazz.newInstance();

例外:

Exception in thread "main" java.lang.NoClassDefFoundError: com/demo/Logic 
    at Main.main(Main.java:31) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:606) 
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) 
Caused by: java.lang.ClassNotFoundException: com.demo.Logic 
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366) 
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425) 
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358) 
    ... 6 more 

有沒有什麼辦法讓它工作嗎?沒有體現/接口。(在C++中,我可以很容易地做到這一點,共享相同的結構/類中聲明,確保使用相同的編譯器編譯兩個部分。恕我直言,Java的會做同樣)


補充說明1

我想改變當前的類加載器的行爲,使其認識到動態加載的類,這是嘗試簡單和幼稚,找不到其他方向:

 ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); 
     ClassLoader loader = new URLClassLoader(urls, parentLoader); 
     Thread.currentThread().setContextClassLoader(loader); 
+0

歡迎來到世界的類加載器...您可以測試看到'Logic.class!= clazz' - 它們由不同的類加載器加載,所以它們是不同的類(即使它們具有相同的類名) – ZhongYu

+0

有關 - http://stackoverflow.com/questions/30227510/sandbox-for-memory/30227614#30227614 – ZhongYu

+0

@ bayou.io是的,它們是不同的類加載器,我想使原點類加載器識別的類動態地 – Gohan

回答

1

我有一個小的變化測試代碼(使用默認的類加載器並將邏輯放入我的類路徑中)。 這工作:

public class Main { 
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { 

     ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); 

     Class<?> clazz = parentLoader.loadClass("com.demo.Logic"); 

     Logic logic = (Logic)clazz.newInstance(); 
     logic.doWork(); 
    } 
} 

你的問題就出在從plugin.jar檢索類。


更新:

我還試圖從與來自第二示例中的代碼的jar檢索的類。 我把編譯後的Logic.class放在com \ demo下,並用jar cvf plugin.jar .\com\demo\Logic.class構建了jar,並且放在與你一樣的路徑中。 它也沒有問題。

此外,您不需要將當前的線程類加載器設置爲加載器。 至少不是這個例子的目的。

您的plugin.jar實際上可能不包含該類。


更新到:

補充說明1

我想改變當前的類加載器的行爲,使其認識 動態加載的類,這種嘗試是爲了簡單和幼稚,可以」 t找到 其他方向:

ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); 
ClassLoader loader = new URLClassLoader(urls, parentLoader); 
Thread.currentThread().setContextClassLoader(loader); 

您的權利與子類加載器這樣做。

但是,如果你想「改變當前的類加載器的行爲」你應該讀 this answer

類加載器是爲了是不可改變的;你不應該能夠在運行時爲它添加類。

從而解決你想出了一個子類加載器。

這就是爲什麼我說「你不需要將當前的線程類加載器設置爲加載器(子類加載器)」。

+0

Plugin.jar由IDEA artifacts jar創建。 '類 clazz所= loader.loadClass( 「com.demo.Logic」);'工作這麼類是在罐中。我會盡力罐子類手動 – Gohan

+0

我發現,您的解決方案並沒有被主類中使用動態負載罐子。你的ClassLoader已經@Gohan我更新了回答您的附加信息預先設定的classpath – Gohan

+0

公認的類。 –

2

爲了使這項工作,你必須在邏輯上分割類分成三組:

  1. 主類
  2. 你的插件類
  3. 你的插件依賴性類

當你創建一個新的類加載器,你必須確保類#2 #3都是由同一個類加載器加載,因爲URLClassLoade r代表團只向父母進發。這意味着JVM應用程序類加載器中加載主類的類無法在新類加載器中看到該類。爲了像C的工作,你需要更新你的主類的類路徑,這是不支持(這是可能的,但它不是支持;我讀過的Java 9將刪除此功能)。在實踐中,你應該將主類分成兩部分(#1和#3),然後使用反射來加載/調用插件相關類(一個反射調用,如果你的插件相關類實現可運行的,你可以使用((Runnable)loadClass("PluginDependent").newInstance()).run()來減少)。但是,你需要確保你的URLClassLoader不委託的插件依賴性類的負載,例如:

  1. 分裂您的應用程序成以上(main.jar文件,plugin.jar,以及所列出的三個離散套main-plugin-dependent.jar),並將它們全部列在URLClassLoader上。

  2. 更改創建的URLClassLoader來指定一個明確的空母,這樣就不會委託給JVM類加載器,然後同時指定plugin.jar 你的主要JAR。

  3. 編寫一個覆蓋loadClass的自定義URLClassLoader,以確保您的插件相關類由該類加載器加載,而不是委派給JVM應用程序類加載器。

+0

我想我可以理解你的解決方案,使'直接調用code' &&'用相同的類加載器加載代碼implement',再次呼籲採取一切其他直接'東西call'。我幾乎要的是使主體工程'直接調用code'作爲同普通的Java代碼,看起來它需要在主項目的每個'直接調用code' – Gohan

+0

一些改變如果你需要跨越多次調用,然後在你的「主」代碼中創建一個接口,並在你的「插件相關類」中實現它,調用插件類中的代碼。即,除了您控制接口之外,這與我給出的Runnable建議相同,因此您可以根據需要添加儘可能多的跨類加載器調用。 –