從我在JavaWorld 上寫
Plug into Java with Java Plug
in
開始
年已經過去了
我的前一篇文章給Java Plug
in下了定義
講到了怎樣在Netscape Communicator
和 Internet Explorer
上怎樣安裝版本
描述了與Java Plug
in相關的Windows注冊表設置
了解了Java Plug
in的控制面版
考查了兩個java主控台
討論了HTML 的<embed> 和 <object>標簽
還發布了一個基於Swing的用於Demo的applet
自從我寫了那篇文章後java和Java Plugin已經相繼發布了許多個版本看來那篇文章已顯老態所以我已經寫好它的系列篇它專注於針對Firefox Web浏覽器的最近的Java Plugin其中之一這篇文章首先為你講解從Firefox怎樣訪問Java Plugin然後討論Java Plugin文件對象模塊(DOM) applet狀態持久化 和cookie比如在Firefox上運行的各種applet當你在學習這些內容時將會強化你已經掌握的知識這篇文章通過探尋Firefox 和Java Plugin之間的聯系再做歸納因為對於Java Plugin而言在其他地方會比我在這裡所講的要詳細得多所以我建議可以學習Sun的Java Plugin文檔你將會學到更多的技術
這篇文章的實驗環境是Mozilla Firefox JSE 和Windows SE即使你沒有這些軟件我仍然建議你讀完這篇文章真誠地希望你能發現一些對你來說還是未知的有興趣的東西
Java Plugin 和Firefox
Java Plugin象橋一樣服務於浏覽器和一個外部的 java運行環境(JRE)之間Java Plugin對於在Firefox 中運行的applet而言是相當重要的因為浏覽器不能自身為其提供一個JRE——其中包括一個JVM因為這篇文章描述的applet會涉及到不同多個不同的Java Plugin內容還有就是這些applet都是在Firefox的環境中運行的所以我們首先應該看一下如何從Firefox中訪問Java Plugin
對於初學者來說你必須確認在你的平台上已經安裝了JSE 和 JRE在我的windows平台上我用jdk__windowsiexe安裝文件把JSE 和 JRE都安裝好了確定你已經安裝了JRE因為JRE包含了Java Plugin在安裝好JSE 後連接Firefox 到 Java Plugin首先從Firefox的工具菜單中選擇Options然後選擇Web Features最後點擊Enable Java checkbox
一般情況下標簽<embed> 和 <object>應該指向在Java Plugin下運行的applet這對Java Plugin 來說不是必要的它能運行由簡單的<applet>標簽指定的applet在安裝時你有機會屏蔽Internet Explorer 和 Mozilla (Firefox)/Netscape Web浏覽器對applet的執行如果它們遇到了<applet>標簽而轉交給Java Plugin處理如果你放棄這個選項遲些時候你可以通過Java Plugin的控制面版來完成這個變化比如打開控制面版選擇標簽Advanced然後點擊<APPLET> Tag Support左邊的+再選擇Mozilla and Netscape選項關閉這個面版後當Firefox遇到<applet>標簽時它就會交給Java Plugin執行圖給出了這些必要的改變
選擇Mozilla and Netscape所以當Firefox遇到<applet>標簽時它就會交給Java Plugin執行
提示
我發現在我的平台上選擇Mozilla and Netscape不是必須的我的Firefox Web浏覽器不會去管那個復選框的設置當遇見<applet>時它就會讓Java Plugin來執行Java Plugin 的文檔談到這是個奇怪的行為如果Mozilla 和 Netscape 都被安裝好了負責管理對<APPLET>標簽是否支持的Mozilla and Netscape選項沒有被選中的情況下<APPLET>標簽中的內容照樣可以在Sun VM上運行這是與Netscape 的自動掃描功能相關的一個bug因為我已經安裝過Netscape 所以我認為我找到了這個bug如果在你的平台上遇到了這個問題記住這點就行了
平台上能駐留多個JRE當Firefox遇到<applet>標簽時到底哪一個JRE來實現Java Plugin?可以從Java Plugin控制面版上的Java標簽上找到答案在二選一的情況下我用來進行版本測試的applet給出了答案同時也證明了Firefox是連接了 Java Plugin的圖顯示我當前使用的JRE版本是(版本是JSE 的內部版本號)
圖 我已經配置好使用JRE 的Java Plugin
Listing 給出了該applet的VersionDemojava文件的源代碼這些代碼在系統屬性javaversion 中設置了JRE 的版本
Listing VersionDetectjava
// VersionDetectjava
import javaawt*;
public class VersionDetect extends javaappletApplet{ public void paint (Graphics g) { int width = getSize ()width; int height = getSize ()height;
gsetColor (Colororange); gfillRect ( width height);
String version = JRE + SystemgetProperty (javaversion);
FontMetrics fm = ggetFontMetrics ();
gsetColor (Colorblack); gdrawString (version (widthfmstringWidth (version))/ height/); }}
為了簡潔在這篇文章中我將不會給出運行這個或者其他applet的HTML參見這篇文章中的source code
提示 Firefox只支持Java Plugin _和更高版本
調用DOM
根據WWW 聯合會 (WC)所說文件對象模塊(DOM)是一個API它針對有效的HTML和合式的XML文檔它定義了文檔的邏輯結構訪問和操作文檔的方法 Java Plugin提供了兩種方法來調用DOM類netscapejavascriptJSObject和Common DOM API
JSObject為Java applet 和Web 浏覽器的JavaScript實現之間提供了接口包含使用了DOM 的JavaScript 對象這些對象的例子Document Link 和 Window我將不會深入JSObject因為在我早期的文章Talk with Me Java中我已經討論了這個類因此我專注於在一個applet中通過Common DOM API來遍歷DOM
在JSE 版本中已經介紹過Common DOM API它是一個類和接口的集合通過這些接口可以讓applet遍歷DOM實例因為一個浏覽器通過幀和窗口能夠顯示多個文檔所以會有許多DOM實例以備遍歷一般一個applet就遍歷它自己的DOM實例—這個與文檔相聯系的DOM實例指定了這個applet
訪問DOM實例的起點就是靜態方法getService(Object obj)該方法位於類comsunjavabrowserdomDOMService中一般情況下一個被喚醒applet的this指針會被作為一個參數傳遞給getService()這個方法要麼返回一個 DOMService對象(為這個applet和它的DOM實例之間提供接口)要麼拋一個異常comsunjavabrowserdomDOMUnsupportedException (the DOM service is not available to the object) 或者 comsunjavabrowserdomDOMAccessException (a security violation has resulted)例子DOMService service = DOMServicegetService (this);
因為web浏覽器提供了不同的DOM實現所以訪問一個DOM實例不是線程安全的除非那個訪問是發生在DOM access dispatch線程為了確保線程安全性DOMService提供了兩個方法來保證訪問只是發生在DOM access dispatch線程—invokeLater() 和 invokeAndWait()
兩種方法都用實現了接口comsunjavabrowserdomDOMAction的對象作為參數來提供方法run()invokeLater()在DOM dispatch線程中異步執行run()invokeAndWait()在DOM dispatch線程中同步執行run()
方法run()用實現了接口comsunjavabrowserdomDOMAccessor的對象作為其唯一參數然後返回一個基本的Object引用方法run()經常喚醒DOMAccessor的getDocument()方法傳遞一個applet的指針做getDocument()的Object參數作為回應getDocument()會返回一個實現orgwcdomDocument接口的對象那個對象中就包含了這個文檔的信息對於HTML文檔而言它會把Document向下轉型為HTMLDocument(位於l中)然後調用HTMLDocument的方法去獲取這個文檔的標題域applet集和一些其他的在調用屬性方法後run()返回一個包含文檔信息(比如HTMLDocument的標題)的Object對象那個Object隨後又從invokeAndWait()和invokeLater()方法中返回
為了梳理上面討論的內容我寫了一個從一個HTML文檔中提取標題的applet然後把標題顯示給用戶下面就是它的代碼
Listing TitleExtractjava
// TitleExtractjava
import comsunjavabrowserdom*;
import javaappletApplet;
import javaawt*;
import l*;
public class TitleExtract extends Applet{ private String title = unknown;
public void init () { try { DOMService service = DOMServicegetService (this); title = (String) serviceinvokeAndWait (new DOMAction () { public Object run (DOMAccessor accessor) { HTMLDocument doc = (HTMLDocument) accessorgetDocument (TitleExtractthis); return docgetTitle (); } }); } catch (DOMUnsupportedException e) { Systemoutprintln (DOM not supported); } catch (DOMAccessException e) { Systemoutprintln (DOM cannot be accessed); } }
public void paint (Graphics g) { int width = getSize ()width; int height = getSize ()height;
gsetColor (Colorcyan); gfillRect ( width height);
FontMetrics fm = ggetFontMetrics ();
gsetColor (Colorblack); gdrawString (title (widthfmstringWidth (title))/ height/); }}
DOMAction的run()方法調用了HTMLDocument的getTitle()方法去獲取HTML文檔的標題隨後顯示該標題例如當在l(參見本文的source code)中遇到元素時getTitle()返回Extract the title圖中顯示了Firefox中的結果
圖 顯示一個文檔的標題
注意
當從標簽<applet> 中調用TitleExtract時Firefox要求mayscript屬性需要被給出否則由於Firefox的DOM實現依賴於JSObjectJava Plugin會拋出各種異常
Applet在Firefox session中的持久狀態
Java Plugin 的文檔提到許多有趣的課題其中之一就是applet的持久性API它可以讓一個applet保存其狀態以備在後來的相同的web浏覽器session中使用State被保存在由Java Plugin控制的一個內部持久性存儲數據結構中一個session是指某個web浏覽器被打開運行開始到這個浏覽器被關閉結束
在JSE 中介紹的applet持久性API 由聲明在接口javaappletAppletContext中的三個方法構成
· void setStream(String key InputStream stream) throws IOException 需要當前applet上下文的一個具體key和具體的stream作為參數如果一個新的stream對應了當前的key這個新stream將會取代那個舊的stream如果新stream的size超出了內部size的限制將會拋出一個異常IOException
· InputStream getStream(String key)返回與當前applet key相聯系的InputStream如果沒有指向該key的stream就返回null
· Iterator<String> getStreamKeys()返回一個迭代器它包括了指向當前applet中stream的所有key
通過調用setStream()來保存狀態這個方法把一個key(它可以方便地標識一個輸入流)和一個InputStream的引用存儲到一個內部數據結構中去比如一個HashMap取出某狀態可以通過相應的標識key調用getStream()它會返回一個InputStream(如果這個key存在)以恢復到這個狀態
因為Java Plugin文檔沒有提供例子來指出怎樣使用applet持久性API所以我准備了一個例子它給出了一個消息傳送applet的兩個實例其中的一個使用applet持久性API發送消息給另一個圖中用戶在左邊實例的 Message To Send文本框中輸入一個消息然後點擊該實例的Send按扭然後用戶點擊右邊實例的Receive按扭這個消息就會在該實例的Message Received文本框中顯示
圖 applet的持久性API使applet之間相互發送消息給對方點擊最大化
消息發送發生在下面代碼的action listener中
Listing MessageTransferjava
// MessageTransferjava
import javaapplet*;
import javaawt*;
import javaawtevent*;
import javaio*;
import javaxswing*;
public class MessageTransfer extends JApplet implements ActionListener{ private TextField txtRecvMsg txtSendMsg;
public void init () { // Build the applets GUI
setLayout (new GridLayout ( ));
JPanel pnl = new JPanel (); pnlsetLayout (new FlowLayout (FlowLayoutLEFT));
pnladd (new JLabel (Message to send:));
txtSendMsg = new TextField (); pnladd (txtSendMsg);
getContentPane ()add (pnl);
pnl = new JPanel (); pnlsetLayout (new FlowLayout (FlowLayoutLEFT));
pnladd (new JLabel (Message received:));
txtRecvMsg = new TextField (); pnladd (txtRecvMsg);
getContentPane ()add (pnl);
pnl = new JPanel (); pnlsetLayout (new FlowLayout (FlowLayoutLEFT));
JButton btnSend = new JButton (Send); btnSendaddActionListener (this); pnladd (btnSend);
JButton btnReceive = new JButton (Receive); btnReceiveaddActionListener (this); pnladd (btnReceive);
getContentPane ()add (pnl); }
public void actionPerformed (ActionEvent e) { JButton btn = (JButton) egetSource ();
if (btngetText ()equals (Send)) { String text = txtSendMsggetText ();
try { // Output the String object to a byte array output stream
ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); ooswriteObject (text); oosclose ();
// Extract the String object from the byte array output stream // as an array of bytes
byte [] data = baostoByteArray ();
// Convert the array of bytes to a byte array input stream When // the setStream() method is invoked it caches the input stream // reference and key in the applet persistent store
InputStream is = new ByteArrayInputStream (data); getAppletContext ()setStream (text is); } catch (Exception e) { Systemoutprintln (etoString ()); } } else { InputStream is = getAppletContext ()getStream (text);
if (is != null) try { // Input the cached String object
ObjectInputStream ois = new ObjectInputStream (is); String text = (String) oisreadObject (); txtRecvMsgsetText (text); } catch (Exception e) { Systemoutprintln (etoString ()); } } }}
這個消息發送applet把消息持久化為一個string對象通過把這個對象序列化到ByteArrayOutputStream轉化這個stream到一個byte數組基於這個byte數組構建一個ByteArrayInputStream然後通過這個stream和一個名為text的key調用方法setStream()該applet用這個名為text的key調用getStream()取回這個消息然後反序列化這個InputStream
在Firefox中被持久化後的消息對在不同窗口或面版上運行的applet都是可以被訪問的這種消息對不同session(也就是說Firefox的不同實例)中運行的applet是不能被訪問的因為每個session只與它自己的Java Plugin實例和內部持久性存儲數據結構進行通信
提示 出於安全考慮使用不同codebase的applet不能訪問對方的stream
了解cookie
Web浏覽器和web服務器經常發送小的數據包給對方進行交互這些包也就是大家知道的cookie可以被用來跟蹤用戶的喜好幫用戶自動登錄站點等Java Plugin結合web 浏覽器可以讓applet訪問cookie例如Java Plugin讓簽名applet去查看從web服務器發送到web浏覽器的cookie
Java Plugin文檔中談及對cookie的支持時給出了幾個與cookie交互的代碼片段因為你可能想在applet中測試這些代碼片段還有就是可能你不確定該怎樣把它們轉變成簽名applet所以下面我給出了一個用來查看cookie的簽名applet在討論完這些重要的代碼後我將帶你學習怎樣建立和簽名applet最後我們會在Firefox web浏覽器中運行這個applet
下面就是代碼
Listing CookieDetectjava
// CookieDetectjava
import javaawtBorderLayout;
import javaawtevent*;
import *;
import javautil*;
import javaxswing*;
public class CookieDetect extends JApplet implements ActionListener{ private JTextArea txtaStatus; private JTextField txtURL;
public void init () { // Build the applets GUI
JPanel pnl = new JPanel ();
pnladd (new JLabel (Enter URL:));
txtURL = new JTextField (); txtURLaddActionListener (this); pnladd (txtURL);
getContentPane ()add (pnl BorderLayoutNORTH);
txtaStatus = new JTextArea ( );
getContentPane ()add (new JScrollPane (txtaStatus)); }
public void actionPerformed (ActionEvent e) { try { URL url = new URL (txtURLgetText ()); URLConnection conn = urlopenConnection ();
nnect ();
Map<String List<String>> headers = conngetHeaderFields (); List<String> values = headersget (SetCookie);
if (values == null) { txtaStatussetText (No cookies detected\n); return; }
txtaStatussetText (); for (Iterator iter = erator (); iterhasNext();) txtaStatussetText (txtaStatusgetText () + iternext () + \n);
txtaStatussetText (txtaStatusgetText () + \n); } catch (Exception e) { Systemoutprintln (e); } }}
CookieDetectjava生成的GUI主要由兩個條目構成一個接受URL的文本框和一個顯示cookie的文本域無論何時用戶按下Enter鍵和這個文本框獲得了輸入的光標文本框的action listener的方法actionPerformed()都會被調用
方法actionPerformed()首先用文本框中的URL建立一個URL對象然後調用這個對象的openConnection()方法返回一個表示該applet和該URL相互連接的URLConnection對象繼續往下面看URLConnection()的方法connect()建立這個連接它的getHeaderFields()方法可以得到不會改變的一組HTTP頭和相對應的值Map的方法get()返回與頭SetCookie相對應的值組成的List一個循環列舉出了該List中的值每個值表示一個cookie被陸續加入文本區域中
假使CookieDetectjava存放在當前目錄c:\applets\CookieDetect中完成下面的操作建立和簽名這個查看cookie的applet
· 編譯 CookieDetectjava: javac CookieDetectjava
· 把上步得到的CookieDetectclass替換成一個jar文件jar cvf CookieDetectjar CookieDetectclass
· 在一個新keytore中創建一個新keykeytool genkey keystore ks alias me當彈出來後輸入testtest作為keystore的密碼你的姓和名你所在行業(比如IT)你公司的名字你所在城市名所在州或省的名字你的行業代碼 不管你剛才輸入的信息是否真實當彈出窗口時選擇Yes當彈出窗口讓你輸入me的密碼 時單擊Enter這樣可以讓密碼與keystore 中的密碼(testtest)相同所有這些信息都被放在文件ks中這個文件需要我們自己簽名的測試認證
· 創建一個簽名的測試認證keytool selfcert alias me keystore ks當被彈出後輸入testtest作為keystore的密碼這個認證就被放進ks中了Alias me (在前一步這步和下一步中)提醒你這個認證已被簽名僅僅用來被測試換句話說不要在公共站點上用這個測試認證來發布簽名applet
· 用這個測試認證來簽名這個jar文件jarsigner keystore ks CookieDetectjar me當彈出後輸入testtest作為keystore的密碼這個工具更新該jar文件的METAINF目錄以包含認證信息和CookieDetectclass的一個數字簽名
讓我們運行這個applet從本文附的代碼中找到l然後放進目錄c:\applets\CookieDetect下該HTML文件的<applet>中包含一個archive屬性來標識CookieDetectjar文件打開Firefox然後輸入該HTML文件的URLc:\applets\CookieDetect\l一段時間後圖的安全對話框就會出現在你眼前
Figure 安全對話框讓你有權信任這個簽名的applet
單擊yes鍵響應該安全對話框然後這個applet被顯示輸入URL然後按下回車如圖所示網站JavaWorld 不會發送cookie
Figure 你不會從 JavaWorld 取得cookie
與JavaWorld 相比網站JavaLobby會發送一個cookie輸入圖中就會顯示有這樣一個cookieid 是SESSIONID
Figure JavaLobby 發送了一個簡單的cookie
一些網站會發送出大量的cookie比如像kasssamba如圖所顯示的該站點發送了個cookie到web浏覽器
Figure Kasamba 發送了不少的cookie到web浏覽器
我極力建議你繼續去體會cookie的使用把余下的三段代碼(來源於Java Plugin 文檔中對cookie支持的標題)也轉換成簽名applet
小竅門
訪問 可以學到更多關於cookie的內容
理解 hood
許多人在讓Firefox識別Java Plugin時會遇到困難說來說去就是指applet不能運行通過理解Firefox與Java Plugin怎樣進行交互你能避開許多類似的麻煩在後續部分我會為你講解Firefox如何探測Java PluginJRE的 NP*dll 插件文件和一些被稱為OJI的東西
探測Java Plugin
在前面你已經安裝了一個簡單的JRE(它包含了Java Plugin)現在要安裝Firefox你可以運行那個浏覽器上網沖浪打開那些使用了applet的網頁那些applet就會運行起來了不久後你會思考Firefox是怎樣找到Java Plugin讓applet運行起來的畢竟說來Firefox只檢索它自己的插件列表來查找插件而JRE把它自己的Java Plugin放在它自身的bin目錄下在這樣的安排下Firefox怎麼可能檢測到Java Plugin呢?
當Firefox開始運行時它的目錄服務提供器就接到一個任務在windows平台上的安裝目錄中去找adobe Acrobat Apple Quicktime Microsoft Windows Media Player 和 Sun Java pluginJava Plugin的安裝目錄一經找到目錄服務提供器將會傳遞以下信息給pluginscanSunJRE用戶的個人設置名稱和該設置的值(要求的最低JRE版本)更進一步securityenable_java 這個用戶設置必須存在而且它的布爾值必須為true
假如pluginscanSunJRE 和 securityenable_java存在還假設securityenable_java的值是true目錄服務提供器會在windows的注冊表中列出所有版本號作為HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Plugin的子鍵最新版本號識別出Firefox所使用的JRE/Java Plugin這個版本號必須要比pluginscanSunJRE的值要大或相等版本號子鍵自身的JavaHome子鍵包含了被識別出的JRE根目錄路徑目錄服務提供器把\bin附加在這個目錄後這時Firefox就知道了Java Plugin的安裝路徑了有點糊塗了?我會用一個例子來撥開這一層霧
我的Firefox浏覽器給出我的pluginscanSunJRE值為13它同時也指定securityenable_java值為真在啟動時目錄服務提供器掃描我的windows注冊表相關的設置如下
HKEY_LOCAL_MACHINE
Software
JavaSoft
Java Plugin
JavaHome C:\Program Files\Java\jre″
目錄服務提供器列舉出所有版本號的子鍵在Java Plugin子鍵下面那個子鍵所示的最高版本號就大於或等於13了我僅有一個子鍵大於或等於因此目錄服務提供器含有子鍵值為的JavaHome子鍵—C:\Program Files\Java\jre—然後把\bin附加在這個值的後面這就意味著Firefox會在目錄c:\Program Files\Java\jre\bin下去找Java Plugin
因為Firefox的pluginscanSunJRE用戶設置用3作為它的默認值所以Java Plugin版本低於的不會被識別這就是為什麼Firefox只支持Java Plugin _和更高的版本
NP*dll文件
仔細查看你的JRE的 bin目錄你會發現不少文件名以NP開頭以dll結尾的文件例如我的平台下就列出了下面這些文件
NPJavadll
NPJavadll
NPJavadll
NPJavadll
NPJavadll
NPJPIdll
NPOJIdll
上面列出的文件是Netscape的插件文件加上你可能在你的JRE的bin目錄下發現的其他NP*dll文件和也應該被列出的若干個jpi*dll文件他們一起構成了Java Plugin每個NP*dll文件有相同的大小因為他們基本上都是做的同樣的事情它與jpi*dll文件中的一個協同工作下載虛擬機讓java環境運行起來
每一個NP*dll文件能識別一種或多種MIME(多用途網際郵件擴展協議)類型當遇到某種MIME類型時這種類型會告訴Firefox去裝載一個相應的具體NP*dll文件例如思考下面的<EMBED>標簽
<embed code=VersionDetectclass width= height= pluginspage= type=application/xjavaapplet;version=>
注意type屬性的application/xjavaapplet;version= MIME類型當Firefox遇到這種MIME類型它就會選擇相應的NP*dll文件因為NPJavadll標記了那種MIME類型NPJavadll會去裝載——而不是其他的NP*dll裝載NPJavadll與jpi*dl文件一起讓java環境運行
當Firefox遇見<applet>標簽時它到底表示的是哪種MIME類型?我認為當Firefox 遇到<applet> 標簽時application/xjavavm就是MIME類型測試顯示出只有擁有那個MIME類型的NPOJIdll會去裝載
什麼是OJI?
你可能已經注意到在NPOJIdll中有OJI想知道這三個字母代表什麼OJI表示Open JVM Integration 這是個Mozilla project/API它允許外部的JVM能夠插入一個 Mozilla浏覽器(就像Firefox這樣)除了沒有被綁定在內部虛擬機上這個浏覽器也沒有被綁定在sun指定的外部虛擬機上——浏覽器能夠通過其他途徑與虛擬機進行交互只要這些虛擬機支持OJI
OJI有許多特性包括允許用戶通過web浏覽器來顯示java主控台OJI也會更改一個applet的生命周期一旦一個applet網頁被打開這個applet的init() 和 start()方法就會被調用為了讓你能看到這個過程先編譯Listing 的 LifeCyclejava源代碼在Firefox下運行這個applet打開java主空台然後不停在網頁之間進行切換在你每次進入這個applet網頁的時候你將注意到對這個applet構造器的調用它表明一個新的LifeCycle對象被生成了
Listing LifeCyclejava
// LifeCyclejava
public class LifeCycle extends javaappletApplet{ public LifeCycle () { Systemoutprintln (constructor called); }
public void init () { Systemoutprintln (init() called); }
public void start () { Systemoutprintln (start() called); }
public void stop () { Systemoutprintln (stop() called); }
public void destroy () { Systemoutprintln (destroy() called); }}
總結
即使講完上面所有的內容我也僅僅是做了淺層次的講解其實還有很多東西我想寫下來可能只有在以後我再寫另一篇後續文章來完成了或者你也可以寫那篇文章不管怎樣都讓我們繼續拓展我們對Sun的 Java Plugin技術的理解吧
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26693.html