J2EE和XML開發——用戶接口(一)

J2EE和XML開發——用戶接口(一)
    作者 KURT A. GABRICK
              DAVID B. WEISS
    出處 J2EE and XML Development第五章
    地址 <
http://www.manning.com/gabrick>


一. 引語
通常,爲J2EE應用創建健壯的表示層是富有挑戰性的嘗試。這是因爲絕大多數J2EE應用是基於Web的瘦客戶端應用。在本文中,我們將檢測一些在J2EE應用的用戶接口設計中十分常見的問題並討論如何使用XML技術來克服這些問題的方法。
我們首先探討瘦客戶端開發的特徵以及你在構建和維護表示層當中可能遇到的挑戰。然後我們通過使用純J2EE技術構建這樣一個表示層的例子來說明當前的J2EE架構的缺點。
本文餘下的部分將集中討論通過使用XSLT技術克服使用純J2EE的侷限性。首先我們開發了一個基於XSLT的表示層,然後我們通過介紹一個Web發佈框架的使用解釋使用第三方API的好處和不足。
本文的目標不是告訴你某種架構比另一種更好,而是希望你瞭解更多的選擇,看它們如何使用並且理解這些選擇積極的和消極的方面。

二. 構建瘦客戶端用戶接口
本文我們的焦點是基於Web的,瘦客戶端分佈式應用。在討論如何克服有關這些應用的問題的細節之前,我們應該花些時間討論這些問題的內容以及爲什麼爲它們構造接口如此的困難。
如果你從前開發過基於Web的應用,那麼毫無疑問你不會對瘦客戶端體系感到陌生。直到最近,爲應用在服務器端生成用戶接口不是很困難。但是,最近的兩個相關的要求使得開發和維護你的表示層部件更具挑戰。

2.1 服務不同的設備
第一個挑戰是關於不斷髮展的嵌入式Web設備。好象現在所有的電子設備都有Web瀏覽器這些智能設備包括手機、PDA和冰箱(聽起來像個笑話)。問題是它們中的有些識別HTML的子集,而另一些又需要完全獨立的標記語言。例如嵌入了WAP的手機需要使用無線標記語言(WML)才能請求和瀏覽Web頁。
使用傳統的J2EE(或者ASP、ColdFusion)創建一個能處理符合各種不同設備要求的獨立的用戶接口是十分困難的。爲了在J2EE的Web應用的服務Web瀏覽器和客戶手機,你必須開發和維護兩個獨立的表示層。一個可能用於生成HTML,另一個則爲WML。對於應用升級可能使這兩層都需要更新。

2.2 服務多個地區
另一個討厭的關注點是爲不同地區的用戶提供服務。明顯的挑戰是決定用戶喜歡的語言以及使用該語言提交Web頁面。這就意味着應用中需要一些機制在不同的自然語言中轉換。所有的內容都需要被翻譯,包括圖形的和JSP中靜態的文字信息。
其次,可能不那麼明顯的是不同地區的文化差異。不僅僅是頁面中的文字內容,它可能有必要爲新的區域改變用戶界面的整個結構。你可能想要改變顏色,頁面佈置甚至界面的導航。這些因素涉及到心理學、市場學或其他的主題。
如果你嘗試過使用純J2EE有效的開發出表示層爲多語言或地域服務,你已經感受過了這種挑戰的大小。如果沒有,我們會在本文餘下的部分突出這些可怕的特徵。使用傳統方法嘗試解決這些需求是導致產生大量的冗餘,通常JSP和Servlet的誤用將導致維護和擴展的噩夢。

2. 3 本文的例子
爲了真實的瞭解問題的核心和XML的解決之道,讓我們通過一個例子來說明。處於普遍性和真實性,我在應用中使用簡單的用戶接口和功能。這是一個股票交易網站並且這裏的功能是向用戶提交一份股價的信息報表。
這裏是本文例子應用的一些需求定義:
·股價頁面必須可以在PC的瀏覽器和WAP手機上顯示
·股價頁面能在兩個區域提交,美國和英國,每個區域需要各自的特定信息。
·股價頁面中顯示的股票價格必須使用各自貨幣形式表達,USD和GBP。
 你可以發現上面的需求使得我們必須提供四個獨立的頁面。
圖5.1是在PC瀏覽器下顯示的區域爲美國的頁面


 
圖5.2是在PC瀏覽器下顯示的區域爲英國的頁面


 
圖5.3是在手機WAP下顯示的區域爲美國的頁面


 
圖5.4是在手機WAP下顯示的區域爲英國的頁面


 
在本文餘下的部分我們使用多種技術創建和重創建這四個頁面。在三中我們僅使用J2EE
並看看在什麼地方會出現阻力。而在四和五中,我們將引入XML技術爲頁面創建可重用和統一的接口並能夠支持多種設備和語言。


三. 純J2EE解決方案
在我們討論任何新的用於用戶接口創建的基於XML的體系結構模型前,我們有必要理解爲什麼我們需要它們。在這個部分我們首先探討純J2EE方案和開發我們例子系統時的侷限性。
3.1 J2EE表示層開發工具
 J2EE表示層組件包括Applets、Servlets、JSP、JavaBeans、JSP定製標記和Filters,除了Applet外,所有的這些組件在J2EE服務器端的Web容器中C/S。這些應用幾乎都是瘦客戶端且基於Web的。在這種體系結構下的J2EE服務器端組件需要利用MVC模式設計並與其他組件合作。成功實現了MVC模式後,各個組件就相互獨立了。在J2EE表示層中,Servlets通常作爲控制器。它們接收客戶的請求,操作模型然後返回適當的視圖。在一些簡單和設計不良的系統中,servlet自己可能用來提交視圖。這種servlet代碼中包含了靜態的HTML內容,所有的開發人員都不喜歡這種風格。
 更高級的解決方案是讓你的控制器選擇合適的JSP作爲視圖並且將請求轉發給它。JSP更易維護而且可以利用定製標記和JavaBeans將應用邏輯從靜態頁面中分離出來。這種使用JSP的表示層應用模型被稱爲Model 2。圖5說明了整個的規則。
 
3.2 J2EE與MVC架構中的問題
 J2EE框架是採用MVC瘦客戶應用的適配器,在大多數案例中,它並不能完全的將表示層邏輯從你的應用數據中分離出。在這部分文章中,我們一個一個的探索一些對所有的純J2EE表示層實現很常見的目標和挑戰。

3.2.1  強制分離邏輯和內容
 Servlets和JSP可以使用許多方法實現。在圖5中,我們見到了Model 2架構,其中JavaBeans被用做模型,Servlets作爲控制器,JSP作爲視圖。


 考慮下面的請求-響應流程:
1. Web請求被入Filter攔截並做預處理,如認證或記錄然後重定向到一個Servlet。
2. 這個Servlet作一些輕量處理並且與應用的商業邏輯層交互,最後轉發請求到JSP提交。
3. JSP聯合使用JavaBeans和定製標記來提交合適的表示法並且將請求轉發給出Filter做響應用戶之前的後續處理。
MVC模式的成功實現依賴於組件間清晰的角色劃分。當嚴格的執行這些劃分時,各組件的可重用性和可維護性能大大提高。但是如果沒有分清它們的角色,這樣的MVC架構的使用將使原本就很複雜的系統更加難以維護和擴展。

 3.2.2   模板語言的侷限性
 JSP是動態頁面的模板,它包含邏輯和數據的集合體。它們和其他流行的動態模板語言(ASP和ColdFusion)類似。在這種模板語言中完全分離應用邏輯和數據是非常困難的,有時甚至是不可能的。例如,下面這三行JSP就混合了頁面結構,靜態內容以及代碼。
<html>
<h1>Hello <%=request.getParameter(uName)%> </h1>
</html>
當JSP最初開發出來時,它就被廣泛的批評,因爲它將Java代碼直接嵌入HTML中。導致HTML頁面作者和JSP開發者需要在同一份原文件上工作。作爲企業應用,通常有明顯的需要將UI設計者和代碼編寫者的角色分開。爲了解決這個問題,JSP加入了定製標記和JavaBeans。這些附加功能使JSP將大多數的Java代碼封裝到了包含複雜顯示邏輯的定製標記和數據結構的JavaBeans中。XML標記現在已經替代了大多數的陳舊的JSP代碼。
 雖然JSP中的代碼減少了,但是整體的複雜度卻增加了。特別是標記總是同時包含顯示標記和Java代碼,使得一些頁面更難維護。頁面設計師不希望HTML的一點變化就需要修改和重編譯標記文件;而且找到那些需要改變的代碼也十分煩人。

3.2.3  代碼冗餘
 我們在前面說過,J2EE應用的多設備支持和國際化需求使得純J2EE方案更加多變。在下一步部分中,建造我們的股票例子頁面需要2到4個JSP頁。當我們再增加地區和設備時,頁面數量還要翻番。考慮考慮要開發和維護100個頁面而僅僅爲了滿足5個功能將是一個怎樣的擔子啊!再想想,要在20個不同的JSP頁面中做同樣改動,單獨測試它們,還要在應用中重複這些測試,多可怕的一件事。如果是商務邏輯的改變,你慶幸將它們封裝到了標記或JavaBean中。如果是顯示邏輯的改變,那麼你可要遭受許多創傷了。

3.3 使用J2EE構件我們的例子
理論已經夠多了,現在我們將理論化爲實際。僅使用J2EE服務我們的例子,需要用到下面的組件:
·一個Sevlet接收客戶請求並與應用邏輯層交互獲取股價信息。
·每種設備一個JSP用來提交股價列表。
 3.3.1   處理請求
我們要求Servlet接收股票列表的用戶請求,獲得這個列表,並且選擇一個適當的JSP頁提交請求。在僅使用J2EE的場景中,我們的Servlet將轉發股價表到4個JSP之一,這取決於用戶的設備種類和地區。
首先,Servlet將需要提取用戶的股票引用列表,我們假設我們的應用在Http會話中使用字符串存儲用戶的標識。獲取用戶身份後,我們調用方法從應用邏輯層獲取JDOM值對象形式的股票列表。
HttpSession session = request.getSession(false);
ListBuilder builderInterface = ...
// ... validate session object exists
String userId = (String) session.getAttribute("userId");
org.jdom.Document quoteList = builderInterface.getWatchList(userId);
然後我們使用JavaBean對象包裝這個quoteList並且在請求對象中存儲它,以便稍後可以被JSP組件獲取。我們稍後在討論JavaBean的代碼。
XMLHelperBean helper = new XMLHelperBean(quoteList);
session.setAttribute(helper, helper);
最後一步是決定哪一個JSP頁面用來提交。要做到這一點,我們需要確定用戶使用的設備類型和地區。首先我們編寫一個方法來找出用戶的設備信息,可以利用User-Agent HTTP頭信息。
private String getOutputType(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
// compare to list of WAP User-Agents
// for simplicity, we'll only try one here
if (userAgent.indexOf("UP.Browser") >= 0)
return "wml";
return "html";
}
在真實世界場景中,這個Servlet將需要維護一個所有user-agent和它們MIME類型的字典。例如,我們僅輸出WML如果某人使用UP.Browser電話瀏覽器。調用下述方法將設置輸出格式。現在我們需要一個方法選擇一個基於輸出格式和用戶區域的JSP。爲了得到它,我們使用Accept-Language HTTP頭,該頭信息包含了一個按順序列出的用戶瀏覽器設置的語言列表。
private String getForwardURL(
HttpServletRequest request, String outputType) {
String result = null;
if (outputType.equals("html"))
result = "/watchlist/watchlist.html.en_US.jsp";
else
result = "/watchlist/watchlist.wml.en_US.jsp";
Enumeration locales = request.getHeaders("Accept-Language");
while (locales.hasMoreElements()) {
String locale = (String) locales.nextElement();
if (locale.equalsIgnoreCase("en_GB"))
if (outputType.equals("html"))
return "/watchlist/watchlist.html.en_GB.jsp";
else
return "/watchlist/watchlist.wml.en_GB.jsp";
}
return result;
}
上面的方法將從四個JSP頁中選擇合適的那個。現在只差把這些方法在我們的ServletD的請求方法中聯合起來啦。
String outputType = getOutputType(request);
String forwardURL = getForwardURL(request, outputType);
context.getRequestDispatcher(forwardURL).forward(request, response);
列表1顯示了我們的新Servlet的完整代碼
列表1 The watch list JSP servlet
import org.jdom.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* The stock watchlist servlet with JSP
*/
public class WatchListJSPServlet extends HttpServlet {
private ListBuilder builderInterface = new ListBuilder();
private ServletConfig config;
private ServletContext context;
public WatchListJSPServlet() { super(); }
public void init(ServletConfig config)
throws ServletException {
this.config = config;
this.context = config.getServletContext();
Listin}
public void doGet(HttpServletRequest request,
HttpServletResponse response)throws ServletException, IOException {
// get userid from HttpSession
HttpSession session = request.getSession(false);
if (session == null) {
context.getRequestDispatcher("/login.jsp")
.forward(request, response);
return;
}
String userId = (String) session.getAttribute("userId");
Document quoteList = builderInterface.getWatchList(userId);
XMLHelperBean helper =new XMLHelperBean(quoteList);
request.setAttribute("helper", helper);
String outputType = getOutputType(request);
String forwardURL =getForwardURL(request, outputType);
context.getRequestDispatcher(forwardURL)
.forward(request, response);
}
private String getOutputType(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
// compare to list of WAP User-Agents
// for simplicity, we'll only compare one here
if (userAgent.indexOf("UP.Browser") >= 0)
return "wml";
return "html";
}
private String getForwardURL(HttpServletRequest request,
String outputType) {
String result = null;
if (outputType.equals("html"))
result = "/watchlist/watchlist.html.en_US.jsp";
else
result = "/watchlist/watchlist.wml.en_US.jsp";
Enumeration locales = request.getHeaders("Accept-Language");
while (locales.hasMoreElements()) {
String locale = (String) locales.nextElement();
if (locale.equalsIgnoreCase("en_GB"))
if (outputType.equals("html"))
return "/watchlist/watchlist.html.en_GB.jsp";
else
return "/watchlist/watchlist.wml.en_GB.jsp";
}
return result;
}
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
 3.3.2 獲取和使用XML數據
我們的Watch List Servlet利用了ListBuilder類,代碼可以從本文網站上下載(地址 <http://www.manning.com/gabrick>)ListBuilder返回一個JDOM文檔,它包含了XML格式的股票信息列表。列表2說明我們例子使用的XML數據集。它包括三個股票價格,注意多個price節點包含在對應的quote元素中,每個價格都有不同的貨幣表示。這使我們可以在頁面生成時採用用戶本地貨幣顯示價格。
列表2 股票引用XML數據集
<?xml version="1.0"?>
<quote-list date="Nov. 4, 2001" time="9:32 AM EST">
<customer first-name="Kurt" last-name="Gabrick" id="9999"/>
<quote symbol="SRMC" name="Sierra Monitor Corporation">
<price amount="2.00" currency="USD"/>
<price amount="1.05" currency="GBP"/>
<price amount="4000.00" currency="MXP"/>
</quote>
<quote symbol="IBM" name="International Business Machines">
<price amount="135.00" currency="USD"/>
<price amount="67.75" currency="GBP"/>
<price amount="230000.00" currency="MXP"/>
</quote>
<quote symbol="ORCL" name="Oracle Corporation">
<price amount="15.00" currency="USD"/>
<price amount="7.75" currency="GBP"/>
<price amount="30000.00" currency="MXP"/>
</quote>
</quote-list>
在獲得JDOM文檔後,我們用JavaBean組件把它包裝起來。這麼做的原因是保持XML操作代碼在JSP文件之外。通過在HttpRequest對象中存儲這個JavaBean,使它在任何JSP提交視圖時都可以使用。此Bean的代碼如列表3所示。注意這個Bean同時還是一個定製標記,擴展了類javax.servlet.jsp.tagext.TagSupport並且實現了doStartBody方法。這樣我們就可以在Bean中動態生成股價信息表的顯示標記並減少了JSP中的代碼。

列表3 XMLHelper JavaBean/Tag
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import java.util.*;
import org.jdom.*;
public class XMLHelperBean extends TagSupport {
  //JavaBean屬性
private String firstName;
private String lastName;
private String quoteTime;
private String quoteDate;
private Vector quotes = new Vector();
//定製標記屬性
private boolean useLinks=false;
private String currency = "USD";

public XMLHelperBean(Document doc) {
Element root = doc.getRootElement();
this.quoteTime = root.getAttributeValue("time");
this.quoteDate = root.getAttributeValue("date");
Element customer = root.getChild("customer");
this.firstName = customer.getAttributeValue("first-name");
this.lastName = customer.getAttributeValue("last-name");
// build quote list
List quoteElements = root.getChildren("quote");
Iterator it = quoteElements.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
Quote quote = new Quote();
quote.symbol = e.getAttributeValue("symbol");
quote.name = e.getAttributeValue("name");
List priceElements = e.getChildren("price");
Iterator it2 = priceElements.iterator();
while (it2.hasNext()) {
Element pe = (Element) it2.next();
quote.prices.put(
pe.getAttributeValue("currency"),
pe.getAttributeValue("amount"));
}
}
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getQuoteTime() {
return quoteTime;
}
public String getQuoteDate() {
return quoteDate;
}
// supplied only for consistency with
// JavaBeans component contract - inoperative
public void setFirstName(String s) {}
public void setLastName(String s) {}
public void setQuoteTime(String s) {}
public void setQuoteDate(String s) {}
public void setUseLinks(String yesno) {
if (yesno.equalsIgnoreCase("yes"))
useLinks = true;
else
useLinks = false;
}
public boolean getUseLinks() { return useLinks; }
public void setCurrency(String currency) {
this.currency = currency;
}
public String getCurrency() { return currency; }
public int doStartTag() { //在HTML或WML中動態創建表格
try {
JspWriter out = pageContext.getOut();
out.print("<tr><th>Stock Symbol</th>");
out.print("<th>Company Name</th><th>Last Price</th>");
if (useLinks)
out.print("<th>Easy Actions</th>");
out.print("</tr>");
for (int i = 0; i < quotes.size(); i++) {
Quote q = (Quote) quotes.get(i);
out.print("<tr><td>");
out.print(q.symbol);
out.print("</td><td>");
out.print(q.name);
out.print("</td><td>");
out.print(q.prices.get(currency));
out.print(" ");
out.print(currency);
out.print("</td>");
if (useLinks) {
out.print("<td><a href=/"http://www.exampleco.com/
buyStock?symbol=");
out.print(q.symbol);
out.print("/">buy</a>&nbsp;&nbsp;&nbsp;");
out.print("<a href=/"http://www.exampleco.com/sellStock?symbol=");
out.print("/">sell</a></td>");
}
out.print("</tr>");
}
} catch(IOException ioe) {
System.out.println("Error in XMLHelperBean.doStartTag(): " + ioe);
}
return(SKIP_BODY);
}
private class Quote {
Hashtable prices = new Hashtable();
String symbol;
String name;
}
}
使用列表3的類,我們可以將JSP中的Java代碼全部分離出來。希望這個小的例子帶給你的設計和開發必要的靈感以便使你的JSP代碼無關。
 
 3.3.3  提交輸出
 現在我們需要使用JSP了,我們可以開發一個包含許多控制結構的複雜JSP。這樣的頁面能處理兩種輸出格式和兩個區域,但是它想必太複雜和難於維護,也難以獨立於數據之外。
 我們也可以會開發兩個JSP而不是一個。其中一個產生HTML另一個則是WML。如果頁面佈置不會因區域改變而改變,我們可以將所有與本地文字和圖象相關的邏輯放到我們的定製標記代碼中。但是,我們將仍然不得不在分支語句中處理本地化。
 所以我決定開發四個獨立而簡單的JSP頁,每個相對於一個設備和地區。列表4到7,分別說明了這四個JSP頁,每個JSP都從請求對象中獲得XMLHelper JavaBean的引用用來提交各自的輸出。

列表4
<jsp:useBean name="helper" scope="page" class="examples.chapter5.XMLHelperBean"/>
<%@ taglib uri="example.tld" prefix="helperTag" %>
<html>
<head><title>Your Watch List</title></head>
<body>
<h1>Your Stock Price Watch List</h1>
<h3>
Hello, <jsp:getProperty name="helper" property="firstName"/>!
</h3>
<h3>
Here are the latest price quotes for
your watch list stocks.
</h3>
<p><i>
Price quotes were obtained at
<jsp:getProperty name="helper" property="quoteTime"/> on 
<jsp:getProperty name="helper" property="quoteDate"/>.
</i></p>
<table cellpadding="5" cellspacing="0" border="1">
<helperTag:printData useLinks="yes" currency="USD"/>// #1
</table>
</body>
</html>
#1處我們提供useLinks=yes,我們的定製標記打印HTML表格,它包括四列,每列都是一個超連接指向購買和拋售各個股票的頁。設置currency=USD將用美圓作爲價格的貨幣單位。列表4顯示的是美國地區HTML版本的JSP,列表5給出的是英國地區的同一版本。請注意兩者的相似和不同之處。美國 頁面使用不那麼正式的語言並用USD描述價格。英國版本則語言更加正式並使用英鎊描述價格。但是,它們都使用同一數據源且都沒有包含Java代碼。

列表5 
<jsp:useBean name="helper" scope="page"
class="examples.chapter5.XMLHelperBean"/>
<%@ taglib uri="example.tld" prefix="helperTag" %>
<html>
<head><title>Your Watch List</title></head>
<body>
<h1>Your Stock Price Watch List</h1>
<h3>
Greetings, Mr.<jsp:getProperty name="helper" property="lastName"/>!
</h3>
<h3>
Here are the latest prices on
your stocks of interest.
</h3>
<p><i>
Price quotes as of <jsp:getProperty name="helper" property="quoteTime"/>
On <jsp:getProperty name="helper" property="quoteDate"/>.
</i></p>
<table cellpadding="5" cellspacing="0" border="1">
<helperTag:printData useLinks="yes" currency="GBP"/>
</table>
</body>
</html>
現在餘下的部分是開發WML版本的頁面。這些頁面包含的文字信息稍微少一些而且沒有直接的指向購買頁的連接。如果你需要回憶一下這些頁面是怎麼樣的,跳到圖1到4去看看。

列表6 美國地區WML版本Watch List JSP
<jsp:useBean name="helper" scope="page"
class="examples.chapter5.XMLHelperBean"/>
<%@ taglib uri="example.tld" prefix="helperTag" %>
<wml>
<card id="main" title="Your Watch List">
<do type="accept" name="do-back" label="Back">
<go href="http://www.exampleco.com/home.wml" />
</do>
<do type="accept" name="do-buy" label="Buy Shares">
<go href="http://www.exampleco.com/buyShares" />
</do>
<do type="accept" name="do-sell" label="Sell Shares">
<go href="http://www.exampleco.com/sellShares" />
</do>
<p><b>
Hello,<jsp:getProperty name="helper" property="firstName"/>!
</b>
</p>
<p>Here are your latest watch list price quotes:</p>
<table columns="3">
<helperTag:printData useLinks="no" currency="USD"/>
</table>
</card>
</wml>

列表7 英國地區WML版本Watch List JSP
 <jsp:useBean name="helper" scope="page"
class="examples.chapter5.XMLHelperBean"/>
<%@ taglib uri="example.tld" prefix="helperTag" %>
<wml>
<card id="main" title="Your Watch List">
<do type="accept" name="do-back" label="Back">
<go href="http://www.exampleco.com/home.wml" />
</do>
<do type="accept" name="do-buy" label="Buy Shares">
<go href="http://www.exampleco.com/buyShares" />
</do>
<do type="accept" name="do-sell" label="Sell Shares">
<go href="http://www.exampleco.com/sellShares" />
</do>
<p><b>Greetings, Mr.
<jsp:getProperty name="helper" property="lastName"/>!
</b></p>
<p>Here are the latest prices on your stocks of interest.</p>
<table columns="3">
<helperTag:printData useLinks="no" currency="GBP"/>
</table>
</card>
</wml>
 3.3.4  結果分析
我們認爲你會承認,使用J2EE實現多設備,多地區的表示層不是一件簡單的事情。設想一下如果要擴展我們的例子以便支持更多的地區和設備需要多少努力。這是一個基於XML的表示層能發揮它作用的問題領域。當實質上地爲一些應用的不同的視圖提供服務總是一種挑戰的時候,那些XML工具的出現會使你的開發過程多少更容易一點。

導讀
本文的第二部分將介紹如何使用J2EE聯合XML技術(XSLT)來更優雅的解決上述股票的例子並且介紹了流行的XML Web發佈框架Cocoon。

 

更多信息
1. <http://www.theserverside.com/>
2. <http://www.javaworld.com/>

聲明

本文由starchu1981保留版權,如果需要轉貼請寫明作者和出處。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

發佈了27 篇原創文章 · 獲贊 0 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章