PETSTORE原始碼追蹤記(二)- Model篇 前冒 在傳統的Java網頁應用系統( Web Application Solution),通常以JSP或 Servlet透過JDBC連結資料庫’用 JavaBean來封裝資料’最後組成HIML回應 ( response)給使用者’主要以這四種Java技術來建構動態網頁系統’這樣的方式 在JSP規格中稱爲 ModelⅠ·適用於較小型丶簡單的系統。若我們要架構大型企 業應用網頁系統’必須要能易於雜護丶容易擴充ν所以Java倡導將 Model(資料 模型)丶ⅵiew畫面展現)及 Controll!流程控制)三者獨立·即所謂的MvC模式 ( Design pattern),又稱鳥 Model2 上一期我們探討了 Petstore的 Controll(流程控制)及View畫面展現)部份 我們可以利用上一期所介貂的架構來建構流程控制與畫面展現獨立的網頁應用 系锍( Web application)’如此的架構雖然很有彈性但只能建構靜態的網頁系統 目前一般較大型的網站都是動態網頁系統’動態網頁內容通常來自資料庫·讀取 資料庫丶封裝資料’這就是 Model(資料模型)肭主要工作 寵物目錄 現在讓我們來探索 Petstore是如何處理Moel(資料模型)部份·以首頁的左 邊的寵物目錄爲例它的主要功用在於提供使用者 Petstore所販賣的寵物種類(圖 -)’若使用者點選其中一類’畫面則會切換列岀該種類之所有寵物(圖二)
PETSTORE 原始碼追蹤記(二)-Model 篇 前言 在傳統的 Java 網頁應用系統(Web Application Solution),通常以 JSP 或 Servlet 透過 JDBC 連結資料庫,用 JavaBean 來封裝資料,最後組成 HTML 回應 (response)給使用者,主要以這四種 Java 技術來建構動態網頁系統,這樣的方式 在 JSP 規格中稱為 Model 1,適用於較小型、簡單的系統。若我們要架構大型企 業應用網頁系統,必須要能易於維護、容易擴充,所以 Java 倡導將 Model(資料 模型)、View(畫面展現) 及 Controll(流程控制)三者獨立,即所謂的 MVC 模式 (Design Pattern),又稱為 Model 2。 上一期我們探討了 Petstore 的 Controll(流程控制)及 View(畫面展現)部份, 我們可以利用上一期所介紹的架構來建構流程控制與畫面展現獨立的網頁應用 系統(Web Application),如此的架構雖然很有彈性,但只能建構靜態的網頁系統, 目前一般較大型的網站都是動態網頁系統,動態網頁內容通常來自資料庫。謮取 資料庫、封裝資料,這就是 Model(資料模型)的主要工作。 寵物目錄 現在讓我們來探索 Petstore 是如何處理 Model(資料模型)部份,以首頁的左 邊的寵物目錄為例,它的主要功用在於提供使用者 Petstore 所販賣的寵物種類(圖 一),若使用者點選其中一類,畫面則會切換列出該種類之所有寵物(圖二):
W上mt日tP:M2 Intemet Explorer 榕案①编国检我的最要⑤工且①就明 中上一真·→,岱现国我的最爱守媒體3日 辆址①)t0pthr 移至連结4x JavaTM Pet Store J2EE BluePrints Sample Application Account I CartI Sign in Birds 厂圈近滿内部路 圖一 箱①际板现①我的爱(工具①D明 中上一页·→·的△按国我的船守媒替(马回日 址圈h →移至 JavaTM Pet Store J2EE BluePrints Sample Applicati Account I Cart Sonin Products for this Category mazon Parrot Great companion for up to 75 years Reptiles Great stress reliever The Java Pet Store Demo is a fictional sample application from the Java BluePrints for the Enterprise. For more information, wsn the Jaa BluePnnis for the Enterprise RePRints for the 22001 Sun Microsystems Inc. All rights nsE I aa Sofware Sun Microsystems 圖二
圖一 圖二
構成這段功能的程式碼在 Petstore homelsrclapps'petstorelsrcldocrootlsidebar jsp( Petstore home即我們安裝 Petstore之目錄) sp use bea d="catalog class="com.sun j2ee blueprints. catalog. client. Catalog Helper scope="session"> Pets <c out value="S item name)"A </c forEach
構成這段功能的程式碼在 Petstore_home\src\apps\petstore\src\docroot\sidebar.jsp(Petstore_home 即我們安裝 Petstore 之目錄)。 sidebar.jsp Pets ">
td> 快取自訂標籤 首先看到它使用兩組自訂標籤( Custom Tag) 第一組是2002年6月 Release的JsTL1.0 JSP Standard Tag Library),它是 由JCP通過標凖的自訂標籤,已包含在 JWSDP1.0裡面·想要多了解它’可至 http:/iava.sun.com/products/isp/istl/ http:/jakarta.apacheorg/taglibs/index.html ctld及相關Jar檔的位置在 Petstore home\srcllibljstl 第二組是 Petstore自己定義的自訂標籤, waftage:tld的位置在 Petstore home\srclwafsrc\docrootWEB-INF 接下來我們看到 它的功用是做網頁快取用,屬性name指定名稱; Scope指定範圍," contextˆ爲整 個 web server丶其他值尙有 session"' request'"page"; duration是指定保留時 間·單位駑毫秒( millisecond)使用時它會先比對當初網頁存λ時間與系統時間 若未超過則直接從指定範圍中取岀網頁內容回應給使用者否則繼續執行標籤裡 面內容’最後再將新內容存入指定範圍’程式碼在 Petstore homelsrclwaftsrclviewltaglibslcom\sunj2eelblueprintstaglibssmartl CacheT ag java 程式碼節錄如下 public int doStartTag( throws Jsp Tag Exception HttpservletrEquest req ((httP servletreqUest)page Context getrequesTo); key =req getRequestURLO toString +'#'+name ? req. getQueryStringO
快取自訂標籤 首先看到它使用兩組自訂標籤(Custom Tag): 第一組是 2002 年 6 月 Release 的 JSTL 1.0(JSP Standard Tag Library),它是 由 JCP 通過標準的自訂標籤,已包含在 JWSDP1.0 裡面,想要多了解它,可至 http://java.sun.com/products/jsp/jstl/ http://jakarta.apache.org/taglibs/index.html c.tld 及相關 Jar 檔的位置在 Petstore_home\src\lib\jstl。 第二組是 Petstore 自己定義的自訂標籤,waftags.tld 的位置在 Petstore_home\src\waf\src\docroot\WEB-INF。 接下來我們看到: 它的功用是做網頁快取用,屬性 name 指定名稱;scope 指定範圍,”context”為整 個 web server、其他值尚有”session”、”request”、”page”;duration 是指定保留時 間,單位為毫秒(millisecond),使用時它會先比對當初網頁存入時間與系統時間, 若未超過則直接從指定範圍中取出網頁內容回應給使用者,否則繼續執行標籤裡 面內容,最後再將新內容存入指定範圍,程式碼在: Petstore_home\src\waf\src\view\taglibs\com\sun\j2ee\blueprints\taglibs\smart\CacheT ag.java 程式碼節錄如下: public int doStartTag() throws JspTagException { HttpServletRequest req = ((HttpServletRequest) pageContext.getRequest()); key = req.getRequestURL().toString() + '#' + name + '?' + req.getQueryString();
∥從指定範圍取出網頁內容·本例範圍是 context if ( "context".equals(scope))i entry=(Entry) page Context. getServletContext( getAttribute(key) else if ("session.equals(scope))i entry =(Entry) page Context getSession().getAttribute(key) else if("request"equals(scope))( entry =(Entry) page Context. getRequesto) getAttribute(key); else if("page".equals(scope))( entry =(Entry) page Context. getAttribute(key) ∥判斷網頁內容是否過期 if(entry != null & entry is Expired)( entry= null 若過期則續執行標籤裡面內容·否則接略過 return(entry ==null)? EVAL BODY BUFFERED: SKIP BODY public int doEndTago throws Jsp Tag Exception ∥若過期則將網頁內容存入指定範圍 if(entry ==null)( Body Content bc- get Body Contento if (bc I= null)i String content=bc. getStringO entry =new Entry(content, duration); ∥本例範圍是? context if ("context".equals(scope))( page Context. getServletContexto setAttribute(key, entry ) else if ("session".equals(scope))( page Context. getSession(). setAttribute(key, entry) else if("request"equals(scope))( page Context. getRequesto) setAttribute(key, entry)
//從指定範圍取出網頁內容,本例範圍是”context” if ("context".equals(scope)) { entry = (Entry) pageContext.getServletContext().getAttribute(key); } else if ("session".equals(scope)) { entry = (Entry) pageContext.getSession().getAttribute(key); } else if ("request".equals(scope)) { entry = (Entry) pageContext.getRequest().getAttribute(key); } else if ("page".equals(scope)) { entry = (Entry) pageContext.getAttribute(key); } //判斷網頁內容是否過期 if (entry != null && entry.isExpired()) { entry = null; } //若過期則繼續執行標籤裡面內容,否則直接略過 return (entry == null) ? EVAL_BODY_BUFFERED : SKIP_BODY; } public int doEndTag() throws JspTagException { //若過期則將網頁內容存入指定範圍 if (entry == null) { BodyContent bc = getBodyContent(); if (bc != null) { String content = bc.getString(); entry = new Entry(content, duration); //本例範圍是”context” if ("context".equals(scope)) { pageContext.getServletContext().setAttribute(key, entry); } else if ("session".equals(scope)) { pageContext.getSession().setAttribute(key, entry); } else if ("request".equals(scope)) { pageContext.getRequest().setAttribute(key, entry);
else if ("page".equals(scope))( page Context. setAttribute(key, entry ) 將取得內容輸出 Jsp Writer out =bc. get Enclosing Writer; out.print(content); catch(IOException ioe) System. err println("Problems with writing.") else{∥若未過期則從指定範圍取岀網頁內容輸岀 try wRiter out= page Context. getOut(; out.print(entry. get Contento); catch(IOException ioe)( System. err printIn("Problems with writing.") return EVAL page. 類別用來幫助 CacheTag記錄網頁內容及判別網頁內容是否過期 class entry i String content ong long duration oublic Entry(String content, long duration)i this content= content;/記錄網頁內容 timestamp= System. currentTimeMilliso;〃記錄系統時間
} else if ("page".equals(scope)) { pageContext.setAttribute(key, entry); } //將取得內容輸出 try { JspWriter out = bc.getEnclosingWriter(); out.print(content); } catch (IOException ioe) { System.err.println("Problems with writing..."); } } } else { //若未過期則從指定範圍取出網頁內容輸出 try { JspWriter out = pageContext.getOut(); out.print(entry.getContent()); } catch (IOException ioe) { System.err.println("Problems with writing..."); } } return EVAL_PAGE; } //此類別用來幫助 CacheTag 記錄網頁內容及判別網頁內容是否過期 class Entry { String content; long timestamp; long duration; public Entry(String content, long duration) { this.content = content; //記錄網頁內容 timestamp = System.currentTimeMillis(); //記錄系統時間
this duration= duration;,∥保存期限·單位以毫秒計 public String getContento( return content; 1 public boolean is Expired i ∥比對當初網頁存入時間與目前系統時間之差是否大於保存期限 ong current Time System current TimeMilliso return((currentTime-timestamp)>duration); 资料封裝 接下來我們回到 sidebar. jsp’會看到以下程式碼 <jsp use Bean class="com. sun j2ee blueprints. catalog. client Catalog Helper SSIon Petstore使用這個 Javabean作暠資料的容器·記錄寵物分類資料’它的位置 在 Petstore homelsrclcomponentslcataloglsrclcomlsunlj2eelblueprintslcatalogiclient\Cat alog Helper. java,程式碼很長以下會擷取重點部份加以探討。接著在 sidebar. jsp 會見到 <c set value=en US"target="(catalog)"property=locale" <c set value="5 target=S(catalog)"property="count"p <c: set value="0"target="S(catalog)"property=start" Petstore利用JSIL自訂標籤設定 catalog JavaBean三個屬性 locale="enUS”丶 count=5、 start=ˆ0”。我們會利用 catalog JavaBean讀取寵物 分類資料·組成HIML來展現, locale是指定語系’ count指定取得資料筆數 start指定起始取得資料列’以本例來說即從第一筆開始取5筆英語系資料
this.duration = duration; //保存期限,單位以毫秒計 } public String getContent() { return content; } public boolean isExpired() { //比對當初網頁存入時間與目前系統時間之差是否大於保存期限 long currentTime = System.currentTimeMillis(); return ((currentTime - timestamp) > duration); } } 資料封裝 接下來我們回到 sidebar.jsp,會看到以下程式碼: Petstore 使用這個 JavaBean 作為資料的容器,記錄寵物分類資料,它的位置 在 Petstore_home\src\components\catalog\src\com\sun\j2ee\blueprints\catalog\client\Cat alogHelper.java,程式碼很長,以下會擷取重點部份加以探討。接著在 sidebar.jsp 會見到: Petstore 利用 JSTL 自訂標籤設定 catalog JavaBean 三個屬性 locale=”en_US”、count=”5”、 start=”0”。我們會利用 catalog JavaBean 讀取寵物 分類資料,組成 HTML 來展現,locale 是指定語系,count 指定取得資料筆數, start 指定起始取得資料列,以本例來說即從第一筆開始取 5 筆英語系資料
資料讀取 這段程式碼乃本篇文章的重頭戲ν功用是將資料從資料庫( Cloudscape)取 出·置入先前設置好的 catalog JavaBean,再從 catalog javabean取所霈資料組成 HTML將’重點在於從這個標籤字面可推敲,它的功 用是將取得資料筆筆倒出,可達到 Petstore多個寵物分類的效果,它的資料來 源是透過 Items="${ catalog categories. list}"取得。初次看到 catalog. categories.list 筆者當場傻眼’這是什麼啊!經過伃細推敲’它就是 catalog. getCategorieso) getListo’現在我們來看看 catalog是如何取得寵物分類資 料,請開啓 Petstore homelsrclcomponents\catalog srcIcom sunlj2eeblueprintslcatalogclient\Cat alog Helper. java 在約197列地方會看到下列函式( Method) public Page getCategorieso 'atal n return use FastLane get Categories FromDAO(start, count, locale) get Categories FromEJB(start, count, locale) use Fastlane在 Catalog Helper初始化時的預設值駑true,它的功用決定資料 讀取路徑’直接透過JDBC或透過EJB,前者是目前使用的方式,它要求三個參 數’在 sidebar. jsp前面程式碼已設定好ν它的優點是讀取效能好’缺點是不易雜 護,因鳥資料讀取(在 Javabean)及資料更新(在EB)的程式碼分散;後者的優缺 點正好與前者顛倒,在此筆者僅介紹透過JBC的方式’若讀者想試試透過EJB
資料讀取 "> 這段程式碼乃本篇文章的重頭戲,功用是將資料從資料庫(Cloudscape)取 出,置入先前設置好的 catalog JavaBean,再從 catalog JavaBean 取所需資料組成 HTML 將,重點在於這個 JSTL 自訂標籤,這一段有點複雜,筆者著 實花了不少時間,且聽我慢慢說來,從這個標籤字面可推敲,它的功 用是將取得資料一筆筆倒出,可達到 Petstore 多個寵物分類的效果,它的資料來 源是透過 items="${catalog.categories.list}"取得。初次看到 catalog.categories.list 筆者當場傻眼,這是什麼啊!經過仔細推敲,它就是 catalog.getCategories().getList(),現在我們來看看 catalog 是如何取得寵物分類資 料,請開啟 Petstore_home\src\components\catalog\src\com\sun\j2ee\blueprints\catalog\client\Cat alogHelper.java 在約 197 列地方會看到下列函式(Method): public Page getCategories() throws CatalogException { return useFastLane ? getCategoriesFromDAO(start, count, locale) : getCategoriesFromEJB(start, count, locale); } useFastLane 在 CatalogHelper 初始化時的預設值為 true,它的功用決定資料 讀取路徑,直接透過 JDBC 或透過 EJB,前者是目前使用的方式,它要求三個參 數,在 sidebar.jsp 前面程式碼已設定好,它的優點是讀取效能好,缺點是不易維 護,因為資料讀取(在 JavaBean)及資料更新(在 EJB)的程式碼分散;後者的優缺 點正好與前者顛倒,在此筆者僅介紹透過 JDBC 的方式,若讀者想試試透過 EJB
方式,可將 useFastLane改 false,接著呼叫下列函式( Method),在約189列 private Page getCategories FromDAO(int start, int count, Locale locale throws Catalog Exception i ull dao= Catalog DAOFactory. getDAOO nt, locale) i catch( Catalog DAOSysException se)i throw new Catalog Exception(se. getMessageO) dao= CatalogDAoFactory, getDAOO這段程式的功用是透過 Catalog DAOFactory動態取得 Data Access Object, Data Access Object顧名思義它 是資料讀取的物件·這也是Java所建議的種 Design patternν通常我們會將連 接資料庫的JDBC相關設定’如 driver,url,及SQL語法包裝其中·集中控管 易於雜護。原始碼在 Petstore homelsrclcomponentslcataloglsrclcom\sun j2eelblueprintsIcatalog dao\Catal og DAOFactory java public static Catalog DAO getDAOO throws Catalog DAOSys Exception i Catalog DAO cat Dao= null InitialContext ic =new InitialContext( String class Name = String)ic lookup (UNDINames CATALOG DAO CLASS) cat Dao=( Catalog DAO) Class. forName(className). newInstanceO 3 catch(Naming Exception ne)( throw new Catalog DAOSys Exception ("Catalog DAOFactory.getDAO Naming Exception while getting DAO type: n"+ ne. getMessageO) i catch(Exception se)i throw new Catalog Exception("Catalog DAOFactory. getDAO Exception while getting DAO type: \n"+se. getMessageO) return cat dao
方式,可將 useFastLane 改為 false,接著呼叫下列函式(Method),在約 189 列: private Page getCategoriesFromDAO(int start, int count, Locale locale) throws CatalogException { try { if (dao == null) dao = CatalogDAOFactory.getDAO(); return dao.getCategories(start, count, locale); } catch (CatalogDAOSysException se) { throw new CatalogException(se.getMessage()); } } dao = CatalogDAOFactory.getDAO() 這段程式的功用是透過 CatalogDAOFactory 動態取得 Data Access Object,Data Access Object 顧名思義它 是資料讀取的物件,這也是 Java 所建議的一種 Design Pattern,通常我們會將連 接資料庫的 JDBC 相關設定,如 driver,url,及 SQL 語法包裝其中,集中控管, 易於維護。原始碼在 Petstore_home\src\components\catalog\src\com\sun\j2ee\blueprints\catalog\dao\Catal ogDAOFactory.java public static CatalogDAO getDAO() throws CatalogDAOSysException { CatalogDAO catDao = null; try { InitialContext ic = new InitialContext(); String className = (String) ic.lookup (JNDINames.CATALOG_DAO_CLASS); catDao = (CatalogDAO) Class.forName(className).newInstance(); } catch (NamingException ne) { throw new CatalogDAOSysException("CatalogDAOFactory.getDAO: NamingException while getting DAO type : \n" + ne.getMessage()); } catch (Exception se) { throw new CatalogDAOSysException("CatalogDAOFactory.getDAO: Exception while getting DAO type : \n" + se.getMessage()); } return catDao; }
JNDINames CATALOG DAO CLASS指定的值爲 Java.comp/env/param/atalogdAoClass,可從deploytool中找到Environment Entries對應的類別是 com. sun. J2 ee. blueprints. catalog. dao. Generic CatalogDAO 透過動態載入方式產生此類別後傳回。 File Edit Tools Help 心e的圆国国圆凹创四 ♀口 p◆ ChatRoomApp Resource Erv Refs Resource Refs SecurityTransactions 邑Cha? oomJAR EJB Refs Entries Referenced in Code 12 自 CustomercontrolJ aramcatatoy六司tbt p◇ PetstoreEAR o- i PetstoreWAR 國 Async SenderEJB ♀國 CatalogER O Catalog EJB 昌 Customer 昌 PetstoreJAR e- B ShoppingCartJAR Delete B SignOnJAR 自 UniqueldGeneratorJAR 9 Serve 吕 OrderProcessingCenterEAR 令 PetstoreEAR ◇ SupplierEAR 圖三 GenericCatalogDAo原始碼位置在 Petstore homelsrclcomponentslcataloglsrclcomlsunJj2eelblueprintslcatalog\dao\ Generic DAO. java,它在建構時會讀取 Petstore home\\appslpetstorelsrcldocroot\Catalog DAOSQL xml EFX Hash Map xml檔案主要功用記載所有讀取資料庫動作所需使用的SOL語法以本例來說 Catalog DAOSQL Xml片段 ∥對應動作 ∥指定參數個數 ∥指定SQL語法 select a catid. name. desc from(category a join category details b on a catid=b catid where locale=? order by name
JNDINames.CATALOG_DAO_CLASS 指定的值為 "java:comp/env/param/CatalogDAOClass",可從 deploytool 中找到 Environment Entries 對應的類別是 com.sun.j2ee.blueprints.catalog.dao.GenericCatalogDAO ,透過動態載入方式產生此類別後傳回。 圖三 GenericCatalogDAO 原始碼位置在 Petstore_home\src\components\catalog\src\com\sun\j2ee\blueprints\catalog\dao\ GenericCatalogDAO.java,它在建構時會讀取 Petstore_home\src\apps\petstore\src\docroot\CatalogDAOSQL.xml,組成 HashMap, xml 檔案主要功用記載所有讀取資料庫動作所需使用的 SQL 語法,以本例來說: CatalogDAOSQL.xml 片段 //對應動作 //指定參數個數 //指定 SQL 語法 select a.catid, name, descn from (category a join category_details b on a.catid=b.catid) where locale = ? order by name