由於這是必須多人maintain 的一個系統,要防止對方網頁改版而撈不到資料
因此我想到了Java的dynamic binding功能
在Java原生支援dynamic binding,而他主要又分成Implicit(隱式)跟explicit(顯式)兩大類
參考資料:http://www.yuloo.com/news/2008-08-27/112873.html
Implicit(隱式連結)
Java在宣告的時候類別本身並不會真正加載類別,只有在產生實體的時候會用ClassLoader去載入Class
考慮下面程式碼
class MyClass{
static{
System.out.println("類別載入了");
}
}
public class Test {
public static void main(String[] args) {
MyClass myclass;
}
}
上面程式碼執行的時候他不會去做類別載入的動作,因為他在這個case下並沒有用到
必須使用new關鍵字產生實體後才會真正去作載入的動作
考慮下方程式
class MyClass{
static{
System.out.println("類別載入了");
}
}
public class Test {
public static void main(String[] args) {
MyClass myclass;
myclass=new MyClass();
}
}
//output: 類別載入了
Explicit(顯式連結)
Implicit的方式可以幫助我們用比較彈性的方式去呼叫一個類別,但是這還不能完全展現動態連結的優勢
為了可以做到類似Plug-in的功能,讓程式可以在run-time期間去改變甚至更新他的行為,就要依靠顯式連結
Java有提供Class.forName以及ClassLoader.loadClass兩個solution去替我們達到這個目的
在Java共有三種主要ClassLoader
參考資料:
http://caterpillar.onlyfun.net/Gossip/JavaEssence/ClassLoader.html
http://godleon.blogspot.com/2007/09/class-class-java-class-class-jvm-class.html
Bootstrap Loader→Extended Loader→System Loader
- Bootstrap Loader
由 C++ 開發,會去搜尋 JRE 目錄($JAVA_HOME/jre/)中的 class 以及 lib 資料夾中的 *.jar 檔,檢查是否有指定的 class 需要載入。
- ExtClassLoader
會去搜尋 JRE 目錄($JAVA_HOME/jre/)中的 lib/ext 資料夾,檢查其中的 class 與 *.jat 檔案,是否有指定的 class 需要載入。
- AppClassLoader
搜尋 classpath 中是否有指定的 class 需要載入。
當我們自定一些型別的時候,都會由AppClassLoader去幫我們載入
考慮下面這段程式
class MyClass{
static{
System.out.println("類別載入了");
}
}
public class Test {
public static void main(String[] args) {
MyClass myclass;
myclass=new MyClass();
System.out.println(myclass.getClass().getClassLoader().getClass());
}
}
//output: 類別載入了
//output:class sun.misc.Launcher$AppClassLoader
而像這種類別載入器就是幫我們做到用字串去得到類別的好幫手,根據字串就能指定類別
這可以替我們的程式更有彈性更易擴充
下面先簡單舉一段Class.forName的例子
public interface CLI2 {
public String say();
}
/**********************Interface Definition****************************/
public class CLTest implements CLI2 {
static{
System.out.println("Now!Class Load!");
}
@Override
public String say() {
System.out.println("Hello I am a very happy boy!");
return "Hello";
}
}
/**********************Implement Definition****************************/
.......
Class c=Class.forName("CLTest");
Object o=c.newInstance();
CLI2 c2=(CLI2)o;
System.out.println(c2.say());
.......
/**********************Main Function****************************/
/*output:
Now!Class Load!
Hello I am a very happy boy!
Hello
*/
上面的程式碼必須要注意一件是就是 CLTest產生的.class要跟執行main function的程式放到同一個目錄下才抓的到,不然要出現ClassNotFoundException這個例外,因為他會去classpath找,所以要方到classpath裡有指定的目錄
接下來就是今天筆記的重點URLClassLoader啦,因為他可以指定要去哪個Path或是jar檔去抓我們要的class,所以今天就在研究這個類別
來說說今天的心路歷程,原本我想說可以針對Interface寫程式的話可以讓程式更自由一點
所以開了兩個Project一個放實作的plug-in,CLTest跟介面CLI2,一個則是放實際要跑的main function跟同樣的介面CLI2
第一個Project內容
public interface CLI2 {
public String say();
}
/**********************Interface Definition****************************/
public class CLTest implements CLI2 {
static{
System.out.println("Now!Class Load!");
}
@Override
public String say() {
System.out.println("Hello I am a very happy boy!");
return "Hello";
}
}
/**********************Implement Definition****************************/
第二個Project內容
public interface CLI2 {
public String say();
}
/**********************Interface Definition****************************/
URL url1 = new URL("file:c:/TC/");
URLClassLoader urlClassLoader1 =new URLClassLoader(new URL[] {url1});
Class c2 = urlClassLoader1.loadClass("CLTest");
Object o=c2.newInstance();
CLI2 ci=(CLI2)o;
ci.say();
/**********************Main Function****************************/
上面是我程式最一開始的長相(非常糟糕 我知道),這程式跑出來會一堆錯誤
先看一下URLClassLoader的reference
http://java.sun.com/j2se/1.4.2/docs/api/java/net/URLClassLoader.html
他可以藉由URL類別去指定要查找指定路徑下或是指定jar內的.class
他有幾種指定方法
- 指定一個絕對位置目錄:URL url1 = new URL("file:c:/TC/"); 以/符號結尾
- 指定一個絕對位置Jar檔:URL url1 = new URL("file:c:/Lib.jar");
- 指定一個相對位置目錄:URL url1 = new URL("file:TC/"); 以/符號結尾
- 指定一個相對位置Jar檔:URL url1 = new URL("file:Lib.jar");
或許有人會問為什麼我兩個Project都要放Interface CLI2,主要是當初考量寫程式方便,CLTest能夠順利參考到他的Interface,不過後來經人指證這是不好的做法,之後再談談改進的方法。
而我在此狀態下執行的結果IllegalAccessError這樣一個錯誤
Exception in thread "main" java.lang.IllegalAccessError:class CLTest cannot access its superinterface CLI2
Why?找不到Interface,難道不能用繼承嗎?後來查了一下這跟ClassLoader的屬性有關而造成無法存取Default package的東西。所以我採用了URLClassLoader的另一個建構子
URLClassLoader urlClassLoader1 = new URLClassLoader(new URL[] {url1}
,Thread.currentThread().getContextClassLoader());
Class c2 = urlClassLoader1.loadClass("CLTest");
Object o=c2.newInstance();
CLI2 ci=(CLI2)o
ci.say();
使用getComtectClassLoader取得當前的類別下載器,不過很可惜他並沒有解決我的問題
因此我很天真的乾脆將他設為null
URLClassLoader urlClassLoader1 = new URLClassLoader(new URL[] {url1}
,null);
Class c2 = urlClassLoader1.loadClass("CLTest");
Object o=c2.newInstance();
CLI2 ci=(CLI2)o
ci.say();
可以跑了,但是卻出現轉型錯誤的exception
Exception in thread "main" java.lang.ClassCastException:CLTest cannot be cast to CLI2
後來上網查了一下資料,Java在比對Type的時候除了Class Name跟Package Name之外,還必須要同一個ClassLoader載入才會當做同一個。也就是(ClassName,PackageName,ClassLoader)三個比對
在這支程式CLI2是由AppClassLoader所載入,而CLTest則是由URLClassLoader所載入,他不能當作同一個家族
後來查找了一些資料,才發現必須設定package而不能用default package。
因為我那堆程式都放在default package之下才會出現這問題,因此我就替他們修改到特定Package之下
第一個Project內容
package org.inter;
public interface CLI2 {
public String say();
}
/**********************Interface Definition****************************/
import org.inter.*;
public class CLTest implements CLI2 {
static{
System.out.println("Now!Class Load!");
}
@Override
public String say() {
System.out.println("Hello I am a very happy boy!");
return "Hello";
}
}
/**********************Implement Definition****************************/
第二個Project內容
package org.inter;
public interface CLI2 {
public String say();
}
/**********************Interface Definition****************************/
import org.inter.CLI2;
..................
URL url1 = new URL("file:c:/TC/");
URLClassLoader urlClassLoader1 =new URLClassLoader(new URL[] {url1},Thread.currentThread().getContextClassLoader());
Class c2 = urlClassLoader1.loadClass("CLTest");
Object o=c2.newInstance();
CLI2 ci=(CLI2)o;
ci.say();
/**********************Main Function****************************/
如此一來就解決了問題,用package設定去解IllegalAccessError用Thread.currentThread().getContextClassLoader()去解的問題
--------------------------------------------------------
但是上面存在著一個問題,就是兩個Project都維護著一份Interface CLI2
這個是比較不好的體系,而Eclipse有提一個解決方案,就是可以參照Project
這樣一來我只需maintain一個inteface就好
首先我先修改架構
第一個Project
CLTest.java //interface implement
第二個Project
CLI2.java //interface
Main.java
之後Eclipse對第一個Project按右見選[properties],再來選[Java Build Path],選裡面的[Projects]按下[Add]按鈕,把第二個Project加入參照就可以大功告成
沒有留言:
張貼留言