在今天的多層結構的web應用程序的設計中,我們可以同時使用Java applet和Servlet。Applet為建立功能強大的動態界面提供了便利的機制,Servlet為web服務器或者其他應用服務器處理請求提供了高效率的手段。
Sun公司的應用程序模型描述了在Java2平台下開發企業級的Java應用的最好的規范。一種被推薦的規范是:在前端使用Applet、Html和JSP,在後端使用Enterprise JavaBeans支持的Servlet及其他成分。
這種體系結構的關鍵是在客戶端的Applet和在服務器說?ervlet之間的通信。但是由於Applet受浏覽器安全模式的限制,在一個Applet中存取數據和信息並不想看上去的那麼簡單。在這篇文章中,我們將解釋在Applet-Servlet結構中開發者所面對的限制,並探討幾個不同的可以在Applet和Servlet之間轉輸數據的通信策略。
如果你已經熟悉Applet和Servlet,這肯定會對你閱讀本文有幫助,如果你還不是那麼熟悉,那也沒關系,我們會簡要地介紹它們。
APPLET和SERVLET的簡介
Applet Java applets實際上是運行在web頁面上的Java程序。它是一個繼承於java.applet.applet的Java類,它通過引用被嵌入到HTML頁面中去,就象一個圖像一樣。Applet和Html的組合,可以建立功能更為強大的動態界面。
對於一些只用來滾動正文和播放動畫的Applet,我們可以在一個企業級的應用程序中利用它來顯示和處理來自服務器上的資源的數據。例如,一個Applet可以用來浏覽和修改數據庫中的記錄或者控制運行在服務器上的其他應用程序。
Java applet除了可以使用它自己定義的類文件外,還可以使用其他的類,不管這些類是獨立存在的還是被打包成了一個JAR文件。Applet和它的類文件通過標准的HTTP請求進行分布,所以Applet可以越過web頁面數據所在的平台的防火牆進行發送。
除非是涉及到保持應用程序完整性的問題,Applet總會在每次用戶重新訪問web主機時自動刷新並會在客戶端保留一段時間。
我們得感謝Java操作系統的平台無關性,這才使得Applet可以運行在任何擁有Java虛擬機(JVM)的浏覽器上。Sun公司的Java插件甚至可以使用可以利用最新版本的JVM編制頁面,而不用擔心受你的用戶的浏覽器上的JVM的版本的限制。
因為Applet是Java平台的擴展,所以在你建立用Applet建立你的web應用程序的界面時,你可以重用已存在的Java組件。
正如我們在下面的例子中可以看到的那樣,你可以在你的Applet的組件中使用復雜的Java對象來開發本來由服務器端應用程序完成的工作。事實上,你可以編寫這樣的Java代碼,它既可以在Applet上執行,也可以在應用程序內執行。
Applet具有所有傳統的Java應用程序的功能,包括使用Sun公司的JFC/Swing組件。Applets也可以用來制作圖形以及應用程序中的用戶界面(盡管有些輔助的窗口會被標志為“Warning, Java Applet Window”)。
但是不管他們有多麼相似,在應用程序和Applet之間還是一些關鍵性的差別的。例如,我們不得不考慮到我們的Applet是受到安全模式的限制的。
Applet的安全約束
Applet代碼來自於web主機並在最終用戶的機器的浏覽器中運行。有害的含有病毒的Applet可能會造成破壞性的效果,為了防止這樣的Applet,Applet受到安全方面的約束,那就是Applet只可以與提供這個Applet的主機進行通信,而且Applet不能操作最終用戶的機器。
它們不能讀寫該用戶的文件系統,不能執行上面的程序,也不能檢查一些敏感的環境參數。(事實上,我們有一種方法可以回避這種限制,那就是開發者可以利用數字簽名的技術對Applet進行標志,這將會詢問用戶是否可以給予Applet某種特殊的待遇。
但是這已經超出我們這篇文章中所討論的范圍了。)此外,Applet不能建立或接受外來的socket連接。所謂外來的是指這個連接超出了提供這個Applet類文件的主機(不是提供引用這個Applet的Html所在的主機)。
因為這個安全性的限制,我們與Applet的通信必須采用一種特殊的策略。通信的唯一的途徑就是在提供Applet的主機和提供相應的Html的主機之前的網絡連接。
Servlets Java servlet是服務器端的組件,它和CGI有很多相似。它可以處理web請求,並返回數據或Html。Servlet可以訪問數據庫,進行計算,並和Enterprise JavaBean這樣的組件進行通信。
與CGI程序不同的是,Servlet是持久有效的,也就是說,它只要被示例一次就可以不斷地處理請求(這些請求很可能是同時發生的)。因此,Servlet比CGI來得更高效。
Servlet運行在一個Servlet引擎中,通常是在一個web服務器或應用程序服務器上。Netscape Enterprise Server 4.0和Netscape Application Server都支持最新版本的Java servlet規范。
和Applet不同,Servlet不受安全約束的限制。因為Servlet是完全在服務器上運行的,它具有所有操作系統所允許的性能。
Servlet可以用來很方便地建立在Applet和Web浏覽器這樣的客戶端和企業應用程序的核心之間的連接。對於客戶端來說,向Servlet發出的請求與其他web請求並沒有任何不同。客戶端通過一個URL來接受返回的信息,正如我們看到的那樣,返回的信息並不一定只能是HTML,實際上我們可以通過Html協議發送和接受任何類型的數據。
構造方法
一個企業級的應用程序可以有幾種方法來構造Applet和Servlet的使用。我將向大家介紹三種不同的構造方法,並對它們的優缺點進行比較。
第一種方法實際上只使用了Applet而沒有使用Servlet,盡管Applet受到它們的安全模式的限制,但是Applet還是可以使用象JDBC、RMI這樣的協議來訪問象數據庫、LDAP目錄和Enterprise JavaBeans組件這樣的後端信息。
這種方法雖然看上去很簡單,但是這並不是一個好的方法,它會帶來很多的問題。首先, 這種安排要求你將所有的訪問信息直接嵌入到你的Applet代碼中。數據庫用戶名、口令、服務器標識,所有的這一切都必須包含在你的Applet代碼中,這樣最終用戶就有可能從類文件中搜集到這些信息。
此外,數據庫或任何其他你訪問的系統都必須在提供Applet的同一台服務器上。這意味著你的服務器將不得不承擔雙重的負擔,它既是一個web服務器,也是一個數據庫服務器。
典型的情況是,你的後端資源可能受到防火牆的保護,但是在這種情況下,這是不可能的,因為運行在客戶端上的Applet必須直接訪問你的機器。最後,使用這種方法,你想使用web服務器群集,如果不是不可能的,至少也是很困難的。
好一點的方法是將與後端資源通信的事務封裝到Servlet中,而Applet僅僅用來處理前端的工作。在這種構造方法中,正如我們在圖2中所示的那樣,Servlet克服了Applet固有的安全約束,並用來控制Applet訪問企業信息系統和事務邏輯。
當Servlet接受到一個請求時,它會在後端數據庫中查詢信息、執行計算、處理對代表Applet的信息的獲取並作用於來自Applet的信息。這種方法的一大進步是Applet/Servlet對可以分布在一個後端web服務器的群集上,所有與某一共享的數據庫的通信都存在於後端。
此外,使用Servlet的設計有助於設計的模塊化、抽象應用程序的後端處理商業邏輯並提高設計的靈活性。
如果你是圍繞Enterprise JavaBeans構建你的應用程序,Servlet就成了中間件。EJB組件可以更加有助於將商業邏輯從Servlet中分離出來,並將其更加抽象。
在這種情況下,一個Applet與它的Servlet通信,Servlet再與EJB組件通信。在應用程序構建中引入由EJB組件、Servlet和前端的applet/Html這樣的層次結構,可以給我們提供最大限度的彈性和性能。盡管這樣做你必須附出復雜化和費用的代價。
通信策略
如果你使用了這樣的構造:在前端使用Applet,在後端使用Servlet,那麼你將需要執行Applet和Servlet的通信。因為Applet受浏覽器的安全模式的限制,我們在對一個Applet存取數據和信息時並沒有太多的選擇。
正如我們在前面提到的,我們不能讀取客戶端的文件系統、不能運行客戶端的程序,由於Applet不是在服務器上運行的,我們也不能訪問服務器上的文件系統。我們只能建立到運行在我們的主機上的服務的網絡連接。
另外,不要忘記應用程序是在一個公開的Internet上發布的,防火牆可能會限制通過HTTP到Servlet或其它web-server模塊的會話。事實上,因為Applet本身就是在網絡上通過HTTP發布的,所以我們必須准確把握通信的策略。
假定在客戶端的Applet和服務器端的Servlet之間的網絡連接是我們可以使用的唯一的通信路徑,我們可以有幾種方法交換信息。正如你知道的,文字流可以由服務器通過HTTP發放。
但是你可能不知道Java對象出可以用這種方式發放。我們將詳細地介紹HTTP文字流和HTTP對象流的使用。另外,我們將簡單地介紹通過Socket進行通信的方法。
Applet與Servlet交換信息的最簡單地方法就是通過HTTP文字流。Java的URL和URLConnection類型使得從一個URL讀取數據變得很容易,你可以不用擔心Socket和其它有關網絡工作的通常的復雜問題。我們所需要的只是一個服務器端的組件,這個組件應該可以通過URL發放信息。這就是我們在這兒使用Servlet的原因。
作為一個例子,我們想要監控服務器的JVM所能使用的內存的總數,並在一個Applet中用一個簡單的儀表顯示它。首先我們需要開發一個Servlet,當通過它的URL訪問這個Servlet時可以返回我們所需要繪制儀表的信息。這個Servlet的源代碼如清單1所示。
清單1import Javax.servlet.*;
import Javax.servlet.http.*;
public class ShowMemservlet
extends Httpservlet
{
public void doGet(HttpservletRequest
req, HttpservletResponse res)
throws servletException, IOException
{
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
Runtime rt = Runtime.getRuntime();
out.println(rt.freeMemory());
out.println(rt.totalMemory());
}
}
這個非常簡單的Servlet會響應一個GET請求(直接通過浏覽器或者是象我們在下面將看到的那樣通過我們的Applet),並返回兩行文字。第一行顯示了服務器的JVM的剩余的自由空間,第二行顯示了JVM可用的全部空間(譯者注:包括已使用的空間)。
要建立我們的Applet中的儀表,我們只需要建立一個到這個Servlet的連接,將它的InputStream封裝到一個DataInputStream中,讀出這兩個參數,將其轉換成數字,並更新我們的儀表。
我們可以讓我們的Applet執行Runnable接口並在其自己的線程中運行。每隔一秒鐘,我們可以運行一個方法來更新我們的儀表。refresh()方法的代碼如清單2所示。
清單2 private void refresh()
throws MalformedURLException,
IOException
{
URL url = new URL(getCodeBase(),
"/servlet/ShowMemServlet");
URLConnection con = url.openConnection();
con.setUseCaches(false);
InputStream in = con.getInputStream();
DataInputStream textStream;
textStream = new DataInputStream(in);
String line1 = textStream.readLine();
String line2 = textStream.readLine();
double freeMem = Double.parseDouble(line1);
double totalMem = Double.parseDouble(line2);
int usedMem = totalMem - freeMem;
int percentUsed =
(int) 100 *(usedMem / totalMem);
meter.setLength(percentUsed);
}
正如你看到的,HTTP文字流的使用相當簡單而且直接。Applet建立到Servlet的連接,讀取它返回的兩行信息並對其進行適當的處理。
使用簡單的文字流來交換數據有一個主要的弱點,那就是Applet並不直接理解數據的信息,而是要將其轉換成一個有用的格式。在我們的例子中,將字符串轉換成數字還不算太復雜,但是當我們試圖處理一個更復雜的數據和對象時,轉換的工作會很快變得無法控制。事實上,在下面我們可以看到我們一種簡單的方法來處理這些復雜的數據。
public void doGet(HttpservletRequest req,
HttpservletResponse res)
throws servletException, IOException
{
OutputStream out;
ObjectOutputStream obJStream;
out = res.getOutputStream();
obJStream = new ObjectOutputStream(out);
Album album = fetchNextAlbum();
out.writeObject(album);
}
private Album getNextAlbum()
throws MalformedURLException,
IOException
{
URL url = new URL(getCodeBase(),
"/servlet/Albumservlet");
URLConnection con = url.openConnection();
con.setUseCaches(false);
InputStream in = con.getInputStream();
ObjectInputStream obJStream;
obJStream = new ObjectInputStream(in);
album = (Album)obJStream.readObject();
return album;
}