2012年9月28日 星期五

安裝 Tomcat 7.0.x

安裝 Tomcat 7.0.x

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟
請勿轉貼
看其他教材

我個人最愛的 Tomcat 5.5.x 終於被宣布自 09/2012 起停止支援,甚至到了 12/2012 就無法下載了。在無奈之下,只好開始轉戰 Tomcat 7.0.x 版。 Tomcat 7.0.x 版的安裝對於 JSP 的開發並沒有太大的改變,改變最多的大概 就是 servlet 的程式了;為了避免大幅修改 Java Servlet 入門, 我會在本文中說明開發 servlet 程式時,web.xml 的必要修改。

支援一般連線

Tomcat 是一個讓開發者能夠執行 JSP/servlet 的伺服器(比較專業的說法是 container),其安裝步驟如下:

細談 URL 編碼 (Part IV)

細談 URL 編碼 (Part IV)

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟

請勿轉貼
看其他教材

在 Part IV 中,我們將之前的測試網頁改成 UTF-8 的編碼,在傳遞 URL 之前 仍然不進行編碼。使用者可以點選 測試網頁 來試試看;另外,makeRequest() 的內容跟前一個測試 網頁大同小異,只是這次呼叫的遠端 servlet 是 EchoUTF8。 請在測試網頁上點一下"確定"按鈕。如果使用 Firefox,你應該會看到如下的對話視窗:


細談 URL 編碼 (Part III)

細談 URL 編碼 (Part III)

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟

請勿轉貼
看其他教材

在探討 URL 編碼的 Part III 中,我們繼續之前的說明方式來討論 Ajax 網頁中負責傳送 URL 的物件 XMLHttpRequest 物件的編碼方式。 在 Ajax 網頁的 第一個測試網頁 中,網頁內容的編碼採用 Big5。 <form> 標籤的定義如下:
<form name="myform">
姓名: <input type="text" value="老呂" name="data"></input>
<input type="button" value="OK" onClick="makeRequest('http://xml.nchu.edu.tw:8080/xml/servlet/EchoBig5');"></input>
</form>

細談 URL 編碼 (Part II)

細談 URL 編碼 (Part II)

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟

請勿轉貼
看其他教材

Part I,我們使用了 Big5 來編寫 網頁。在這個部分,我們使用另一種常用的編碼 -- UTF-8 -- 來編寫網頁。 網頁內容幾乎跟 Part I 的測試網頁相同,幾個比較不一樣的地方:儲存 檔案的時候,記得要以 UTF-8 的編碼方式儲存;<meta> 標籤的 charset 要改成 UTF-8;遠端的 servlet 要改成 EchoUTF8。你可以 到這裡 看一下。

如何把時鐘和文字放在同 一個畫面?

如何把時鐘和文字放在同一個畫面?

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

現在你已經知道怎麼把文字(也就是 Hello World)或者時鐘(剛剛的練習題)放到螢幕上,可是怎麼把兩個以上的 GUI 元件放到螢幕上呢?說明 setContentView() 的時候,我們說明了該方法只能有一個參數,而且該參數必須是 View 或者其子類別的物件,這項限制也說明了:如果利用這樣的方式使用 setContentView(),只有一個 GUI 元件可以顯示在螢幕上。為了瞭解是否能解決這種限制,除了搜尋之外,另一個方式就是找 API 文件。

當然一定要 Hello World:難的部分

當然一定要 Hello World:難的部分

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

如果你來到這個網頁,相信你已經執行完你的第一個 Android 程式:Hello World;由於我們採用的是"知難行易"的方式來說明,相信之前的操作(行易)還蠻簡單的,可是只會操作而不知其所以然,想要繼續開發 Android 程式會非常困難,所以這份文件試著只解釋必須知道的部分 -- 知難。

2012年9月26日 星期三

把玩"魔術師" -- 編譯 AOSP 2.2 (Froyo) 原始碼

把玩"魔術師" -- 編譯 AOSP 2.2 (Froyo) 原始碼

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

在不斷嘗試編譯 Android 2.2 的過程中,我發現了三份比較重要的文件,分別是 編譯在G1上運行的android 2.2(froyo)代碼編譯在G1上運行的android 2.2(froyo)代碼_正規版、以及 [HOW-TO] Compile AOSP Froyo + [ROM] Latest AOSP Froyo for Sapphire。經過幾個階段的測試、彙整,目前得到以下的方式最為完整。 之前所寫的另一個版本,也提供在 這裡 僅供參考。 既然能編譯出一個堪用的 HTC Magic 32A ROM,自然要把過程寫一下給有興趣的人試試看。 不過,這絕對不是一份任何人都一定看得懂的(我有些部分也還在摸索);因為 是給自己當筆記,我只能先假設你懂一些 Linux 的基本概念。我把編譯的過程 分成幾個步驟來說:(以下的步驟絕大部分都是在 Ubuntu/Linux 上執行)

把玩"魔術師" -- 編譯 AOSP 2.1 (Eclair) 原始碼

把玩"魔術師" -- 編譯 AOSP 2.1 (Eclair) 原始碼

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼
看其他教材

為了達到第一個目標 -- 自己編譯 AOSP(Android Open Source Project),便開始收集 資料,目前進度極緩慢,所以先把一些有用的資料集結在此,做個筆記,有機會再讓它 成為一篇完整的文章。在慢慢達成目標的過程,分成幾個階段,分別描述如下: 第一階段主要是參考 [HOW-TO] Compile AOSP Froyo + [ROM] Latest AOSP Froyo for Sapphire 這一串討論文。在編譯過程充滿了一些完全不知道要如何處理的困難(看起來基本功 不紮實),就算勉強解決了,可惜編譯出來的 image 檔無法在 Magic 上執行,也找不到 答案。這個階段有幾次想放棄,想想看:都這麼老了還玩?有沒有搞錯? 雖然這一階段的挫折最大,但是以下的文件也有不少是來自這一串討論文。 第二階段主要是參考 編譯 android 2.1 (eclair) 源碼 For HTC G1,這個文章是編譯給 HTC Dream 的,基本上這篇文章是參考一篇日文的文章 EclaironADP1andADP2,雖然看不懂日文,但是從指令可以看出想要編譯給 ADP2 (根據 Android FAQ,ADP2 是 Magic 32B),應該也可行。看文章跟著做的過程中,有不懂得就順便 Google 一下找答案,有 答案的,我會順便把它筆記下來。

把玩"魔術師"


把玩"魔術師"

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

目錄

  1. 編譯 AOSP 2.1 (Eclair) 原始碼
  2. 編譯 AOSP 2.2 (Froyo) 原始碼
  3. 製作自創的 ROM
  4. 自製第一個開機畫面
  5. 製作自創的開機動畫

大概是在 2009 年的 7 月買了人生第一部 Android 手機 -- HTC Magic,買它的原因 不外乎當初玩 Unix/Linux 的情形類似,因為畢竟 Android 的核心還是 Linux, 而且開發應用程式的程式語言是 Java(看的懂得人,不要暗笑,會內傷 ;-) )。 買了之後,玩了一陣子,也試著開發 一些小程式,算是滿意的;可是隨著 Google 不斷的推出,不到一年的時間,Android 的版本快速的從 1.5,發展到 2.2,甚至有傳言 2010 年的 10 月 Google 會推出 3.0 。 (其實這樣快速的大變動,是好的嗎?如果從企業經營的資訊策略 來看,會有什麼影響?要不要慢一點?) 這樣的發展,有什麼影響,目前還不能一探全貌,但是對我來說有下列幾點感想:

JOFC2 - 混合圖表

JOFC2 - 混合圖表

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

我們假設你已經依據之前的文章完成的 OFC2 的安裝工作,也了解之前 OFC2 和 JSP 的合用方式。由於在本文中,我們將介紹如何混搭 BarChart 以及 LineChart, 因此請讀者先熟讀 OFC2 - 第三個範例 以及 OFC2 - LineChart 和 PieChart 中第六個範例。 在進一步說明範例之前,我們要先說明的是:我們只針對跟之前不同的地方進行 說明,相同的部分就不再贅述! 簡單來說,如果需要在一個 Chart 物件中,加入(例如)BarChart 以及 LineChart, 你需要分別產生 BarChart 以及 LineChart,並對他們進行設定;一旦設定完成, 我們就可以將 BarChart 以及 LineChart 的物件加到 Chart 物件內。 如果讀者依據我們的程式執行,應該可以看到類似如下的畫面:
而完整的程式碼列示如下:
<%@page contentType="text/plain" pageEncoding="UTF-8"
        import="jofc2.*,
                jofc2.model.*,
                jofc2.model.elements.*,
                jofc2.model.axis.*,
                jofc2.model.elements.BarChart.*,
                jofc2.model.elements.LineChart.*"%>
<%
  Chart cht = new Chart("存貨量線型圖");
  XAxis labels = new XAxis();
  labels.addLabels("北區", "中區", "南區", "離島");
  cht.setXAxis(labels);
  YAxis range = new YAxis();
  range.setRange(0, 60, 10);
  cht.setYAxis(range);
  BarChart bar = new BarChart(BarChart.Style.THREED);
  LineChart lin = new LineChart();
  bar.setColour("#669900");
  lin.setColour("#6666FF");
  bar.setText("存貨量");
  lin.setText("價格");

  for(int i=0; i<4; i++) {
    bar.addValues((int) (Math.random() * 60) + 1);
    lin.addValues((int) (Math.random() * 10) + 1);
  }

  cht.addElements(bar, lin);
  out.println(cht.toString());

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

JOFC2 - LineChart 和 PieChart

JOFC2 - LineChart 和 PieChart

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


我們假設你已經依據之前的文章完成的 OFC2 的安裝工作,也了解之前 OFC2 和 JSP 的合用方式。在本文中,我們簡單的介紹 LineChart 和 PieChart 的產生方式。 由於這些圖表的產生方式跟之前 BarChart 非常類似,我們大多只列示其程式碼, 並加上執行後的範例畫面。 在進一步說明範例之前,我們要先說明的是:我們只針對跟之前不同的地方進行 說明,相同的部分就不再贅述!
  1. LineChart:jofc2.model.elements.LineChart 跟 BarChart 一樣,也是 jofc2.model.elements.Element 的子類別,因此使用的方式與 BarChart 非常 類似。依據 JOFC2 LineChart 的文件,我們之前介紹 LineChart 的 Style 共有 LineChart.Style.DOT、LineChart.Style.HOLLOW、 以及 LineChart.Style.NORMAL 三種;其中,只有 NORMAL 有效果,其它的,就算可以產生 json 檔,卻無法顯示。 後來在我有空的時候,我仔細檢查了 JOFC2 LineChart 的原始碼之後,我發現 原來文件和原始碼是不一致的,請以下列的方式來開發,我也強烈懷疑, 之前測不出來的效果,可能也是這個原因所造成的)。 在下列程式碼中,我們特別針對 LineChart 物件的產生方式說明一下;根據 JOFC2 的原始碼,我們說明在設定上彈性最高的一種建構元,該建構元總共有 六個參數;
    1. 第一個參數用來指定線型圖上每一個資料點的形狀:支援的形狀總共有 BOW, DOT, HALLOW_DOT, ANCHOR, STAR, 和 SOLID_DOT 六種。在範例中,我們 使用了 STAR 和 HALLOW_DOT。
    2. 第二個參數用來指定線型圖上每一個資料點的顏色:設定的方式是利用 #RGB 的方式;在範例中,我們分別指定紫色和紅色。
    3. 第三個參數用來指定線型圖上每一個資料點的大小;在範例中,我們分別指定的 大小為 8 和 5。
    4. 第四個參數用來指定線型圖上每一個資料點與兩端線的距離;在範例中, 我們分別指定的距離是 0 (也就是不留間隙) 和 10。
    5. 第五個參數用來設定轉動(rotate)每一個資料形狀的角度;在範例中, 我們讓 STAR(星狀圖)轉動 90 (從畫面呈現的效果,90 應該不是代表 90 度); 而 DOT(圓餅)不轉動。
    6. 第四個參數用來設定每一個資料點與兩端線的距離是否為真。目前測不出 任何效果。
    如果讀者依據我們的程式執行,應該可以看到類似如下的畫面;我們強烈建議 讀者盡量測試各種的效果。

    而完整的程式碼列示如下:
    <%@page contentType="text/plain" pageEncoding="UTF-8"
            import="jofc2.*,
                    jofc2.model.*,
                    jofc2.model.elements.*,
                    jofc2.model.axis.*,
                    jofc2.model.elements.LineChart.*"%>
    <%
      Chart cht = new Chart("存貨量線型圖");
      XAxis labels = new XAxis();
      labels.addLabels("北區", "中區", "南區", "離島");
      cht.setXAxis(labels);
      YAxis range = new YAxis();
      range.setRange(0, 60, 10);
      cht.setYAxis(range);
    
      // 初始化 LineChart,並分別設定線型圖的各種樣子
      // LineChart.Style 的第一個參數指的是每一個點的形狀;總共有 BOW, DOT, HALLOW_DOT,
      //                 ANCHOR, STAR, 和 SOLID_DOT
      // 第二個參數指的是點的顏色
      // 第三個參數指的是點的大小
      // 第四個參數指的是 halo (點以及線之間的距離)
      // 第五個參數指的是旋轉(rotate)點的角度
      // 第六個參數指的是是否要讓 halo 的值生效;目前測不出效果
      LineChart lin1 = new LineChart(new LineChart.Style(LineChart.Style.Type.STAR, 
                                     "#CC00CC", 8, 0, 90, false));
      LineChart lin2 = new LineChart(new LineChart.Style(LineChart.Style.Type.HALLOW_DOT, 
                                     "#CC0000", 5, 10, 0, true));
    
      // 設定線的的顏色
      lin1.setColour("#669900");
      lin2.setColour("#6666FF");
    
      // 設定線的的標題
      lin1.setText("存貨量");
      lin2.setText("價格");
    
      for(int i=0; i<4 data-blogger-escaped-10="" data-blogger-escaped-1="" data-blogger-escaped-60="" data-blogger-escaped-ath.random="" data-blogger-escaped-cht.addelements="" data-blogger-escaped-cht.tostring="" data-blogger-escaped-gt="" data-blogger-escaped-i="" data-blogger-escaped-int="" data-blogger-escaped-lin1.addvalues="" data-blogger-escaped-lin1="" data-blogger-escaped-lin2.addvalues="" data-blogger-escaped-lin2="" data-blogger-escaped-out.println="" data-blogger-escaped-pre="">
    
  2. PieChart: jofc2.model.elements.PieChart 跟 BarChart 一樣,是 jofc2.model.elements.Element 的子類別,但是它跟之前介紹過的 BarChart 和 LineChart 有幾個比較不同的地方:第一個 PieChart 是一個圓餅圖, 在一般的情形下,大概只有一系列的資料,而不會有多群的 Bars 或者 Lines; 第二個差異在於 PieChart 沒有明顯的 X 和 Y 軸,因此一些 X 和 Y 軸的 設定沒有必要。 PieChart 跟 BarChart 和 LineChart 一樣,可以直接把想表現的數值利用 addValues() 方法加進去,缺點是圓餅圖中的每一片(slice)的標籤就是 該片的數值,不太方便。因此,可以使用如下的方式:
      pie.addSlice((int) (Math.random() * 10) + 1, "北區");
    
    將數值以及其相對應的標籤一起家進去。如果讀者依據本程式執行,應該 可以看到如下的畫面:
    完整的程式碼如下:
    <%@page contentType="text/plain" pageEncoding="UTF-8"
            import="jofc2.*,
                    jofc2.model.*,
                    jofc2.model.elements.*,
                    jofc2.model.axis.*,
                    jofc2.model.elements.PieChart.*"%>
    <%
      Chart cht = new Chart("存貨量圓型圖");
    
      PieChart pie = new PieChart();
      pie.addSlice((int) (Math.random() * 10) + 1, "北區");
      pie.addSlice((int) (Math.random() * 10) + 1, "中區");
      pie.addSlice((int) (Math.random() * 10) + 1, "南區");
      pie.addSlice((int) (Math.random() * 10) + 1, "離島");
      
      // 請嘗試移除上列四個 pie.addSlice(), 並使用下列數值以觀察不同結果
    /*
      for(int i=0; i<4 data-blogger-escaped-10="" data-blogger-escaped-1="" data-blogger-escaped-ath.random="" data-blogger-escaped-cht.addelements="" data-blogger-escaped-cht.tostring="" data-blogger-escaped-gt="" data-blogger-escaped-i="" data-blogger-escaped-int="" data-blogger-escaped-out.println="" data-blogger-escaped-pie.addvalues="" data-blogger-escaped-pie.setcolours="" data-blogger-escaped-pie.setradius="" data-blogger-escaped-pie="" data-blogger-escaped-pre="">
    







Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu


JOFC2 - 第五個範例

JOFC2 - 第五個範例

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


我們假設你已經依據之前的文章完成的 OFC2 的安裝工作,也了解之前 OFC2 和 JSP 的合用方式。如果你熟悉 JSP 或者 Java servlet,但是不清楚 JSP/servlet 的生命週期,請仔細閱讀 JSP 的生命週期。由於我們在這個範例中不再 使用亂數產生器來產生亂數,而是利用資料庫中現有的資料來呈現,而且我們使用的 資料庫管理系統是 MySQL,我們假設讀者已經依據 MySQL Server 簡介 中介紹的方式,完成了 MySQL 的安裝, 並且已經新增了所需要的使用者(jlu)、資料庫(eric),以及其表格(Product)。 如果讀者依據本文的介紹完成程式,顯示的畫面如下:
在進一步說明範例之前,我們要先說明的是:我們只針對跟之前不同的地方進行 說明,相同的部分就不再贅述!
  1. 由於要與資料庫連接,我們需要下列敘述:
      import java.sql.*;
    
  2. MySQL Server 簡介 中介紹的程式碼類似,跟資料庫連接,我們需要完成載入 JDBC 驅動程式, 利用該驅動程式與資料庫產生連線,經由連線產生執行 SQL 語法的 Statement 物件, 然後利用 Statement 物件來執行 SQL 語法,最後處理 SQL 的結果。其相關的 程式碼如下:
        // 載入 MySQL JDBC 驅動程式
        Class.forName("com.mysql.jdbc.Driver");
    
        // 產生程式與資料庫之間的連線
        conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", PWD);
    
        // 產生用來執行 SQL 語法的 Statement 物件
        stmt = conn.createStatement();
    
        // 執行 Query
        ResultSet rs = stmt.executeQuery(qSQL);
    
        // 將查詢結果一筆一筆的取出來,並加入圖中
        while(rs.next()) {
          bar1.addValues(rs.getInt(1));
          bar2.addValues(rs.getInt(3));
    
          // 加 XAxis 的標籤
          labels.addLabels(rs.getString(2));
        }
    
        // ....
      } catch (Exception e) {
        System.out.println("Exception Occurs: " + e);
      } finally {
        try {
          stmt.close();
        } catch(Exception e) {
          System.out.println("Close error.");
        }
      }
    
  3. 為了完整性,我們將完整的 JSP 程式碼列示如下:
    <%@page contentType="text/plain" pageEncoding="UTF-8"
            import="jofc2.*,
                    java.sql.*,
                    jofc2.model.*,
                    jofc2.model.elements.*,
                    jofc2.model.axis.*,
                    jofc2.model.elements.BarChart.*"%>
    
    <%!
        private Connection conn = null;
    
        public void jspInit() {
          try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", UID, PWD);
          }
          catch (Exception e) {
            System.out.println("Fail to connect to database.");
          }
        }
    
        public void jspDestroy() {
          try {
            conn.close();
          } catch (Exception e) {
            System.out.println("Fail to close connection.");
          }
        }
    %>
    
    <%
      // query 字串
      String qSQL = "select qty, name, price from Product";
    
      Chart cht = new Chart("存貨量長條圖");
      XAxis labels = new XAxis();
      YAxis range = new YAxis();
      range.setRange(0, 210, 10);
      cht.setYAxis(range);
      BarChart bar1 = new BarChart(BarChart.Style.NORMAL);
      BarChart bar2 = new BarChart(BarChart.Style.NORMAL);
      bar1.setColour("#669900");
      bar2.setColour("#6666FF");
      bar1.setText("存貨量");
      bar2.setText("價格");
    
      Statement stmt = null;
      try {
        stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(qSQL);
    
        while(rs.next()) {
          bar1.addValues(rs.getInt(1));
          bar2.addValues(rs.getInt(3));
    
          // 加 XAxis 的標籤
          labels.addLabels(rs.getString(2));
        }
    
        cht.addElements(bar1, bar2);
        cht.setXAxis(labels);
    
        out.println(cht.toString());
      } catch (Exception e) {
        System.out.println("Exception Occurs: " + e);
      } finally {
        try {
          stmt.close();
        } catch(Exception e) {
          System.out.println("Close error.");
        }
      }
    %>
    


Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



JOFC2 - 第三個範例

JOFC2 - 第三個範例

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


我們假設你已經依據之前的文章完成的 OFC2 的安裝工作,也了解之前 OFC2 和 JSP 的合用方式。在本文中,我們希望能夠更進一步的調整條型圖的內容,這次 調整的內容是將兩個以上的 Bar 放到條型圖中;完成後,條型圖如下所示:
在進一步說明範例之前,我們要先說明的是:我們只針對跟之前不同的地方進行 說明,相同的部分就不再贅述!
  1. 由於我們需要在 Chart 物件上加上兩個以上的 Bars;或者更明確的說, 要加上兩個以上的 BarChart。因此,為了能產生兩個 BarChart 物件,我們 宣告了以下敘述:
      BarChart bar1 = new BarChart(BarChart.Style.NORMAL),
               bar2 = new BarChart(BarChart.Style.NORMAL);
    
  2. bar1 和 bar2 都會以預設的顏色(類紫色)來呈現,可是這樣的呈現效果 不好;一般來說,不同的 Bar 會有不同的顏色。設定 Bar 顏色的方法為:
      bar1.setColour("#993300");
      bar2.setColour("#669933");
    
    setColour() 的參數是一個字串,其格式為井字號(#)之後,加上 RGB 的 十六進位數字。讀者可以利用繪圖工具或者 Table Tutor - Color Chart 來選擇 顏色。
  3. 顏色設定完成之後,我們可以利用類似之前的迴圈將亂數值經由 addValues() 將資料加到 BarChart 內。最後,我們就可以利用下列方式將 bar1 和 bar2 加上 Chart 物件:
      cht.addElements(bar1, bar2);
    
  4. 為了完整性,我們將完整的 JSP 程式碼列示如下:
    <%@page contentType="text/plain" pageEncoding="UTF-8"
            import="jofc2.*,
                    jofc2.model.*,
                    jofc2.model.elements.*,
                    jofc2.model.elements.BarChart.*"%>
    <%
      Chart cht = new Chart("亂數資料");
    
      // 產生兩串資料,分別由 bar1 和 bar2 代表
      BarChart bar1 = new BarChart(BarChart.Style.NORMAL),
               bar2 = new BarChart(BarChart.Style.NORMAL);
      bar1.setColour("#993300");
      bar2.setColour("#669933");
      for(int i=0; i<4; i++) {
        bar1.addValues((int) (Math.random() * 10) + 1);
        bar2.addValues((int) (Math.random() * 7) + 1);
      }
      cht.addElements(bar1, bar2);
    
      out.println(cht.toString());
    %>
    



Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



OFC2 - 第四個範例

OFC2 - 第四個範例

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


我們假設你已經依據之前的文章完成的 OFC2 的安裝工作,也了解之前 OFC2 和 JSP 的合用方式。在本文中,我們希望能夠更進一步的調整條型圖的內容,這次 調整的內容包含修改圖表標題的顏色、大小,新增 X 軸的標籤,設定 Y 軸的 最小值、最大值、間距,設定 Y 軸的標籤(從之前的說明,目前只支援英文), 以及分別設定兩個 Bar 的值代表 2008 和 2009 的銷售量。程式修改後所呈現的 畫面如下:
在進一步說明範例之前,我們要先說明的是:我們只針對跟之前不同的地方進行 說明,相同的部分就不再贅述!
  1. 首先,我們修改圖表標題的字體大小、顏色,以及 alignment。為了設定 標題,我們必須先宣告一個沒有標題的 Chart 物件,然後利用 setTitle() 的 方法將標題文字以及格式(style;包含字體大小、顏色,以及 alignment)設定好。 setTitle() 的參數是一個 jofc2.model.Text 的物件,而產生 style 字串的方式 是藉由 createStyle(字體大小, 顏色, alignment) 來完成,所以我們的程式碼 如下所示:
      // 調整 title 的字體大小、顏色,以及 alignment
      cht.setTitle(new Text("亂數資料", Text.createStyle(20, "#9933FF", Text.TEXT_ALIGN_CENTER)));
    
  2. 我們必須使用 jofc2.model.axis.XAxis 來設定 X 軸的標籤:在產生完 XAxis 物件之後,我們利用其 addLabels() 方法將標籤加進去,最後我們 利用 Chart 物件的 setXAxis() 的方法將 XAxis 設定為 Chart 的 X 軸物件。 程式碼如下所示:
      // 調整 X 軸的標籤
      XAxis labels = new XAxis();
      labels.addLabels("北區", "中區", "南區", "離島");
      cht.setXAxis(labels); 
    
  3. 如果要調整 Y 軸的呈現方式,我們使用 jofc2.model.axis.YAxis 物件。 YAxis 物件提供 setRange(最小值, 最大值, 間距) 方法來設定 Y 軸的數值; 另外,我們也可以使用 Chart 物件的 setYLengend() 的方法來改變 Y 軸的 標籤。setYLengend() 的參數是一個 Text 物件,設定方式跟之前相同。 原始碼如下所示:
      // 調整 Y 軸的標籤(目前只能英文)以及間距
      YAxis range = new YAxis();
      range.setRange(0, 60, 5);
      cht.setYAxis(range);
      cht.setYLegend(new Text("Quantity", Text.createStyle(12, "#736AFF", Text.TEXT_ALIGN_CENTER)));
    
  4. 最後,我們需要為 bar1 和 bar2 分別設定標籤,其設定方式如下:
      // 為 bar1 和 bar2 加上標籤
      bar1.setText("2008");
      bar2.setText("2009");
    
  5. 為了完整性,我們將完整的 JSP 程式碼列示如下:
    <%@page contentType="text/plain" pageEncoding="UTF-8"
            import="jofc2.*,
                    jofc2.model.*,
                    jofc2.model.elements.*,
                    jofc2.model.axis.*,
                    jofc2.model.elements.BarChart.*"%>
    <%
      Chart cht = new Chart();
    
      // 調整 title 的字體大小、顏色,以及 alignment
      cht.setTitle(new Text("亂數資料", Text.createStyle(20, "#9933FF", Text.TEXT_ALIGN_CENTER)));
    
      // 調整 X 軸的標籤
      XAxis labels = new XAxis();
      labels.addLabels("北區", "中區", "南區", "離島");
      cht.setXAxis(labels);  
    
      // 調整 Y 軸的標籤(目前只能英文)以及間距
      YAxis range = new YAxis();
      range.setRange(0, 60, 5);
      cht.setYAxis(range);
      cht.setYLegend(new Text("Quantity", Text.createStyle(12, "#736AFF", Text.TEXT_ALIGN_CENTER)));
      
      // 產生兩串資料,分別由 bar1 和 bar2 代表
      BarChart bar1 = new BarChart(BarChart.Style.NORMAL),
               bar2 = new BarChart(BarChart.Style.NORMAL);
      bar1.setColour("#993300");
      bar2.setColour("#669933");
    
      // 為 bar1 和 bar2 加上標籤
      bar1.setText("2008");
      bar2.setText("2009");
    
      for(int i=0; i<4 data-blogger-escaped-10="" data-blogger-escaped-1="" data-blogger-escaped-60="" data-blogger-escaped-ath.random="" data-blogger-escaped-bar1.addvalues="" data-blogger-escaped-bar1="" data-blogger-escaped-bar2.addvalues="" data-blogger-escaped-bar2="" data-blogger-escaped-cht.addelements="" data-blogger-escaped-cht.tostring="" data-blogger-escaped-gt="" data-blogger-escaped-i="" data-blogger-escaped-int="" data-blogger-escaped-out.println="" data-blogger-escaped-pre="">
    




Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



JOFC2 - 第二個範例

JOFC2 - 第二個範例

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


我們假設你已經依據之前的文章完成的 OFC2 的安裝工作。在本文中,我們 開始說明如何將 OFC2 和 JSP 作結合。我們在本範例中,將以亂數來產生 四個整數,並將這四個整數值以 BarChart(條型圖)呈現出來。我們在這一系列 的教學文件中,都以條型圖為範例,至於其他的圖表,我們建議分別利用舊版的 Open Flash Chart 教學文件以及 JOFC2 的 JavaDoc 來學習。
  1. 首先我們來看 JSP 的程式碼:在所有 JOFC2 所產生的圖表都是由一個 jofc2.model.Chart 的物件所形成,而在形成 Chart 物件的時候,我們可以順便 為這個圖表產生"標題"。產生 Chart 物件的方式:
    import jofc2.model.Chart;
    
    Chart cht = new Chart("亂數資料");
    
    一旦 Chart 物件 cht 產生之後,我們就可以在這個物件上加上需要的圖表內 常見的物件;例如,條型圖(BarChart)(或者其他圖,如 LineChart、PieChart 等),圖例(legend),以及 X、Y 軸等。由於我們希望能產生條型圖,所以 我們需要產生一個 jofc2.model.elements.BarChart 物件(其實大多數圖表 的物件都是屬於 jofc2.model.elements.Element 的子類別,例如 BarChart、 LineChart、PieChart 等),其產生的方式如下:
    import jofc2.model.elements.*;
    
    BarChart bc = new BarChart(BarChart.Style.NORMAL);
    
    BarChart 的風格有三種,除了程式碼中的 BarChart.Style.NORMAL, 還有 BarChart.Style.GLASS 以及 BarChart.Style.THREED 兩種。我們鼓勵讀者在看完本文的範例之後,可以試試看更改 BarChart 的風格。 有了 BarChart 物件 bc 之後,我們可以開始為 bc 加上其所要表現的數字。 將數字加入 bc 是利用其 addValues() 的方法,其程式碼如下:
      for(int i=0; i<4 data-blogger-escaped-10="" data-blogger-escaped-1="" data-blogger-escaped-ath.random="" data-blogger-escaped-bc.addvalues="" data-blogger-escaped-i="" data-blogger-escaped-int="" data-blogger-escaped-pre="">
    
    完成了 BarChart 的設定之後,我們需要把它加到 cht 內,其方式如下:
      cht.addElements(bc);
    
    最後,我們就可以把 cht 轉換成字串,並將它回傳給客戶端,其方式如下:
      out.println(cht.toString());
    
    這個範例的完整繩式碼如下:
    <%@page contentType="text/plain" pageEncoding="UTF-8"
            import="jofc2.*,
                    jofc2.model.*,
                    jofc2.model.elements.*,
                    jofc2.model.elements.BarChart.*"%>
    <%
      Chart cht = new Chart("亂數資料");
    
      // 產生 BarChart 並將資料新增到 BarChart
      BarChart bc = new BarChart(BarChart.Style.NORMAL);
      for(int i=0; i<4; i++)
        bc.addValues((int) (Math.random() * 10) + 1);
    
      cht.addElements(bc);
      out.println(cht.toString());
    %>
    
    請注意: 跟之前的資料檔一樣,請在儲存這個檔案的時候,務必要把它 儲存成 UTF-8 的編碼方式,如此一來 OFC2 才能正確的顯示中文;另外,也請記得 將 @page 的 pageEncoding 的值定義成 UTF-8。
  2. 請將上述程式檔置放於 tomcat\webapps\test 內,並先執行它 以確保該程式能正確的回傳符合 json 格式的資料,而且也可以確定該程式可以 正確無誤的執行。假設我們將該程式碼儲存為 ofc2.jsp,執行後的畫面如下:
  3. 如果程式可以正確無誤的執行,我們就可以為它開發一個相對應的 HTML 檔。 這個檔案的內容跟之前的範例非常類似,差別只在我們為 data-file 設定的值 為程式的名稱,也就是 ofc2.jsp。
    <html>
    <head>
    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript">
    swfobject.embedSWF("open-flash-chart.swf", "my_chart", "500", "250", "9.0.0",
                       "expressInstall.swf", {"data-file":"ofc2.jsp"});
    </script>
    </head>
    <body>
     
    <p>第二個範例:結合 OFC2 和 JSP</p>
    <div id="my_chart"></div>
    </body>
    </html>
    
  4. 最後,我們可以載入 ofc2.html,其畫面如下:



由於條型圖的內容是由亂數產生器所產生的,因此每一次"重新載入"之後, 條型圖的內容應該會改變;這個情形在 Firefox 是完全沒問題的,但是在 IE 8 時,卻往往看到 IE 8 只會繼續使用 cache 的內容,而不會每次都重新 執行 ofc2.jsp 並使用其最新的值。日前,有位網友(Jackey)提供了一個 解決方式,那就是利用亂數字串給 JSP 檔,如此一來,IE 認定無法重複使用 cache 的內容而必須重新執行。以本範例而言,只需將 {"data-file":"ofc2.jsp"} 改成 {"data-file":"ofc2.jsp?random=(new Date()).valueOf()"} 但是使用時,請切記,你的 JSP 檔(在本例中就是 ofc2.jsp)內不會讀取 名為 random 的參數;如果有,請記得將名稱改成一個不用的參數名稱即可。

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



OFC2 - 第一個範例

OFC2 - 第一個範例

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


我們假設你已經依據之前的文章完成的 OFC2 的安裝工作,也已經把 ofc0.html 以及 data.json 放在適當的目錄下。 在本文中,我們開始說明第一個範例的內容:
  1. 由於在資訊系統中,資料的內容常常會因為資料庫的內容改變而不同,因此 系統開發時撰寫一份固定的資料檔(也就是範例中的 data.json)是沒什麼道理的。 一般來說,data.json 的內容會由程式產生,在我們的教學文件中,這些資料是由 JSPs/servlets 來產生。除非讀者有意願開發一套類似 JOFC2 的套件,大多數開發 人員是不需要知道 data.json 的內容的。
  2. 就像之前說明的,OFC2 是利用 open-flash-chart.swf 來讀取資料並將結果 呈現在網頁中的,所以需要呈現 OFC2 圖表的網頁必須要有一個機制將該 swf 檔 下載到瀏覽器;這個部分還蠻簡單的,因為 OFC2 的作者利用了一個 Javascript 檔(就是下列原始碼中的 swfobject.js)讓這個過程變的非常簡單。載入並使用 swfobject.js 的方式如下原始碼的綠色部分:在這段程式碼中,總共有兩段 <script>,第一段幾乎不必改變,可能需要改變以及說明的在第二段; 這兩段 <script> 習慣上會置放於 <head> 標籤之間。在第二段 <script> 中,我們呼叫了 swfobject.embedSWF() 方法(定義於 swfobject.js 內),該方法共有五個參數,第一個參數用來載入 open-flash-chart.swf (這個部分大多不會變,除非你將該檔置放於其他位置),第二個參數在指出該 圖表出現的地方;以我們這個範例來說,圖表會出現在一個 id 為 my_chart 的 標籤內,也就是如原始碼紅色的部分所示。
    <html>
    <head>
    
    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript">
    swfobject.embedSWF("open-flash-chart.swf", "my_chart", "500", "250", "9.0.0");
    </script>
    
    </head>
    <body>
     
    <p>第一個範例:使用 SWFObject.js</p>
    
    <div id="my_chart"></div>
    
    </body>
    </html>
    
    第三和第四個參數分別代表該圖表的 X 軸和 Y 軸的長度,以本範例來說,該圖表的 大小為 500x250 的方形;最後一個參數代表 Flash 的版本數,從這裡看得出來, open-flash-chart.swf 是以 Flash 9 版所寫出來的。
  3. 練習題: 請將 my_chart 改成 first_chart,另將大小改成 800x400,並試著將圖表置放於網頁的中央。

在上述範例中,我們必須在瀏覽器的 URL 欄位中多輸入 ?ofc=data.json, 雖然這個方法比較有彈性(讓使用者傳入不同的資料檔,例如之前的 wdata.json), 但是如果資料檔是固定的,或者資料來源是一個固定的程式執行後的結果,那麼 有沒有直接將檔案或者程式名稱寫在 HTML 檔案內的方式?答案是有的,請看下列程式碼:
<html>
<head>
<script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript">
swfobject.embedSWF("open-flash-chart.swf", "my_chart", "500", "250", "9.0.0",
                   "expressInstall.swf", {"data-file":"data.json"}
);
</script>
</head>
<body>
 
<p>第一個範例:使用固定來源名稱</p>
<div id="my_chart"></div>
</body>
</html>
這段原始碼跟之前的範例幾乎相同,不同的地方在於綠色部分:利用 swfobject.embedSWF() 來呼叫 expressInstall.swf,並利用它來讀取資料的來源,該資料來源的名稱必須 是 data-file,而其值可以依據資料來源的不同而不同,利用想使用 wdata.json 的話,請將 data.json 改成 wdata.json;或者如果想呼叫遠端程式 ofc2.jsp 的話,請將 data.json 改成 ofc2.jsp 即可;讀者可以點一下 測試頁 並觀察 URL 欄位的值。
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



OFC2 的準備工作

OFC2 的準備工作

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


OFC2 的安裝工作包含兩個部分,一個是 OFC2 本身,另一個是用來產生 JSON 的 Java 套件;當然第二個部分不是必要的,因為 OFC2 本身已經包含了像是 php 之類 的套件,如果你的開發環境不是 Java,你可以到 OFC2 的首頁確認你是否還需要 下載安裝其他套件。另外,由於我們之後的範例是以 JSP 為主,因此我們假設 讀者已經熟析 JSP,而且了解我們預設的 tomcat 設定如 JSP 入門 所示;也就是說,我們的測試目錄是 tomcat/webapps/test
  1. 安裝 OFC2:這個部分有點麻煩,第一個原因是該網頁中含有多個版本,至於哪個版本比較好也沒有清楚的說明;第二個原因是OFC2 開發的時間是 2009 年,似乎舊了一些。 最近找到了另一個新版本,目前從測試的結果來看,似乎網頁上的範例都 OK,至於 新版本有沒有提供新的功能,我需要找時間再試試看。以下針對我測試過的兩個版本 進行說明:
    1. Version 2 Lug Wyrm Charmer (28th, July 2009):
      1. 請到 OFC2 下載網頁 下載 Version 2 Lug Wyrm Charmer (28th, July 2009),檔案名稱為 open-flash-chart-2-Lug-Wyrm-Charmer.zip。
      2. 請將該壓縮檔進行解壓縮。
      3. 解壓縮之後,它包含有 12 個目錄(如 js, php-ofc-library, php5-ofc-library 等)以及兩個檔案(分別是 README.txt 和 open-flash-chart.swf);請將 open-flash-chart.swf 以及 js 目錄分別複製於 tomcat/webapps/test
    2. OFC2 Community Release:這個版本是在 OFC2 討論區 中看到的。從討論區的字裡行間看出,似乎 OFC2 原作者已經不再進行更新了,因此 OFC2 社群中有人跳出來進行持續更新。
      1. 請到 OFC2 Community Release 下載網頁 下載最新版本,我們正在測試 open-flash-chart-2-community-0.25.zip
      2. 請將該壓縮檔進行解壓縮。
      3. 解壓縮之後,請將 open-flash-chart.swf 以及 js 目錄分別複製於 tomcat/webapps/test
    3. 從第一個範例中,我們可以知道目前介紹的 open-flash-chart.swf 是無法讓 Y 軸 上的標籤出現中文的(例如,將 data.json 中的 "Open Flash Chart" 改成 "次數", open-flash-chart.swf 是不會呈現任何結果的)。如果讀者一定需要在 Y 軸出現中文, 甚至希望在 X 軸上的中文可以轉動某個角度,那麼可以 下載 這個版本;該版本的修改範圍如 說明請注意: 下載檔是一個 zip 檔,解壓縮之後的檔案為 open_flash_chart.swf(是底線),而該檔案的大小大約 7 個 MB,與原先不到 300 多 KB 的檔案比起來,明顯大很多,使用時要注意頻寬的問題。

  2. 安裝 OFC2 的 Java 套件:
    1. 請到 JOFC2 - Java API for Open Flash Chart 2 下載 jofc2-1.0-0.zip。雖然 JOFC2 的首頁介紹了 (開發中的)最新版 JOFC2,但是本文僅介紹原始版的。
    2. 解壓縮該 ZIP 檔之後,請將 jofc2-1.0-0.jar 以及 xstream-1.3.1.jar(在 解壓縮後的 lib 目錄內)複製到 tomcat\common\lib 或者 tomcat\shared\lib 內。
  3. 由於 JOFC2 的測試需要寫程式,所以這部份的測試留到開始介紹 JSP 程式時才 說明,我們先測試 OFC2 的安裝是否正確。請將下列資料檔案,命名為 data.json 並將其放置於 tomcat/webapps/test請注意: 當你編輯 data.json 的時候,由於 OFC2 只能正確的解讀 UTF-8 的資料,所以在資料中包含 有中文的資料時,請以 UTF-8 的編碼方式儲存;以"記事本"為例,在儲存檔案時, 請在"編碼"的地方,選擇 UTF-8。
    {
      "title":{
        "text":  "測試資料",
        "style": "{font-size: 20px; color:#0000ff; font-family: Verdana; text-align: center;}"
      },
     
      "y_legend":{
        "text": "Open Flash Chart",
        "style": "{color: #736AFF; font-size: 12px;}"
      },
     
      "elements":[
        {
          "type":      "bar",
          "alpha":     0.5,
          "colour":    "#9933CC",
          "text":      "Page views",
          "font-size": 10,
          "values" :   [9,6,7,9,5,7,6,9,7]
        },
        {
          "type":      "bar",
          "alpha":     0.5,
          "colour":    "#CC9933",
          "text":      "Page views 2",
          "font-size": 10,
          "values" :   [6,7,9,5,7,6,9,7,3]
        }
      ],
     
      "x_axis":{
        "stroke":1,
        "tick_height":10,
        "colour":"#d000d0",
        "grid_colour":"#00ff00",
        "labels": {
            "labels": ["一月","二月","March","April","May","June","July","August","Spetember"]
        }
       },
     
      "y_axis":{
        "stroke":      4,
        "tick_length": 3,
        "colour":      "#d000d0",
        "grid_colour": "#00ff00",
        "offset":      0,
        "max":         20
      }
    }
    
    請利用編輯器將下列 HTML 檔存成 ofc0.html 檔:
    <html>
    <head>
    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript">
    swfobject.embedSWF("open-flash-chart.swf", "my_chart", "500", "250", "9.0.0");
    </script>
    </head>
    <body>
     
    <p>第一個範例:使用 SWFObject.js</p>
     
    <div id="my_chart"></div>
     
    </body>
    </html>
    
    最後,請在瀏覽器的 URL 欄輸入 http://localhost:8080/test/ofc0.html?ofc=data.json,你應該會看到類似之前的圖表;你也可以點一下 測試頁 來看一下結果。
  4. 在測試 OFC2 的過程中,我試圖將圖表中 Y 軸的標籤進行更改;例如我希望 將範例圖表中的 "Open Flash Chart" 換成 "次數",但是"次數"無法顯示出來,似乎 OFC2 無法將中文顯示在 Y 軸的標籤。練習題: 請試著將 data.json 中 y_legend 後的 Open Flash Chart 換成 次數,並將 data.json 轉存成 wdata.json,然後輸入 http://localhost:8080/test/ofc0.html?ofc=wdata.json 試試看!




Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



Open Flash Chart 2 簡介

Open Flash Chart 2 簡介

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

開發以瀏覽器為介面的資訊系統是這幾年來系統開發的常態,而且常見的開發方式 都能很輕易的顯示一些 HTML 的文字內容(如條列示、表格式),但是對於顯示 圖表,卻往往發生找不到適當的中文教學文件的困擾,這篇文章就是希望能部分 解決這個問題,也順便可以當作教學文件用。
由於我們系上的程式語言以 Java 為主,因此以 Java 和 Chart 的關鍵字搜尋之後, 我們發現了 JFreeChart 以及 jCharts 這兩套免費的 Java 工具, 可以讓程式開發人員簡單的產生各式各樣的圖表。這兩套工具又以 JFreeChart 可以 找到的文件比較多,我也曾利用找到的教學資料:JFreeChart(JSP)學生學習評量曲線圖範例"使用JFreeChart製作美觀易懂的圖表 開發了一些能夠顯示工具提示(tooltip) 的條型圖。在測試 JFreeChart 的過程中,我發現雖然它可以提供一般 Java Applications 使用,也能在 servlets/JSPs 的開發環境下使用,但是總覺得它的設計以及使用上 實在太多於複雜。有幾次找尋資料的過程中,發現了 Open Flash Chart 2 的套件,於是 決定試試看,並比較它們的優缺點。目前的感覺,我個人覺得 Open Flash Chart 2 似乎比較簡單,只是它僅限於以瀏覽器為介面的資訊系統開發中使用,所以決定先 寫篇 Open Flash Chart 2 的教學文件(也就是本文);或許哪一天心血來潮也會把 之前的 JFreeChart 也寫一寫。
Open Flash Chart 2 是一個利用 Flash 物件來顯示圖表的一個工具,而且更妙的是這個工具不但是免費的, 而且還是 Open Source(開放原始碼)的。在開始介紹 OFC2 之前,請點選這個 範例,如果這樣的圖表能引起你的興趣,請繼續閱讀下去。
基本上,每一個 OFC2 的圖表都是由一個內嵌在網頁內的 Flash 物件所形成,而該 Flash 物件就是由 OFC2 所開發的 open-flash-chart.swf;當瀏覽器載入 一個含有 OFC2 Flash 物件的網頁,該 Flash 物件會由伺服器端下載;當下載完成後, 該 Flash 物件會開始讀取呈現圖表所需的資料檔案;OFC2 要求資料檔案必須符合 JSON 的格式,而該資料檔可以是一個固定的資料檔,或者是由一個程式(如 servlets/ JSPs)回傳給該 Flash 物件;資料讀取完成後,Flash 物件就把圖表顯示出來。
  1. 準備工作:這部分主要在於 OFC2 的安裝。 由於 OFC2 物件可以讀取由程式所形成的 JSON 資料檔,因此我們需要安裝給 servlets/JSPs 的套件,以便於可以輕易、快速的產生 JSON 檔。
  2. 第一個範例:我們在這個範例中利用固定的 資料檔案來呈現圖表。
  3. 第二個範例:我們在這個範例中利用 JSP 程式來產生資料,並由 HTML 檔呈現圖表。
  4. 第三個範例:這個範例主要在於呈現兩個 Bars 在條型圖中,並分別為它們設定各自的顏色。
  5. 第四個範例:這個範例主要在於為條型圖 更進一步的調整,調整的部分包含設定 X 軸的標籤等。
  6. 第五個範例:這個範例主要在於將條型圖 與資料庫結合;範例中使用的是 MySQL。
  7. 第六、七個範例:這兩個範例主要在於說明 如何使用 JSP 分別產生 LineChart 以及圓餅圖。
  8. 第八個範例:這個範例主要在於說明 如何在一個圖表中,建立兩種以上的不同圖表;範例中使用的是之前說過的 BarChart 以及 LineChart。

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

JSP 入門

JSP 入門

All examples are solely used for educational purposes. This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu 在以下的範例中,我們假設你已經在你自己的電腦上面安裝好了 Apache Tomcat 而且 port 為 8080。如果你還沒安裝好,你可以 參考 安裝 Tomcat 5.5.x

請勿轉貼
看其他教材

目錄

  1. Hello World -- JSP
  2. 互動式 Hello World -- JSP
  3. JSP 存取資料庫
  4. JSP 生命週期
    1. <%! %>
    2. <%@ include %>
  5. 從 web.xml 讀取初始值
  6. Cookie
  7. Session
  8. Custom Tag Library

這篇 JSP 入門的介紹非常精簡,它假設讀者已經熟悉了 Java 程式語言, 關聯性資料庫 SQL 語法,以及大概看過了Java Servlet 入門

Hello World -- JSP

JavaServer Page (JSP) 和 Java Servlet 一樣都是伺服器端的程式, 功能也差不多,只是語法與使用上比較像 PHP 和 ASP 之類的 script 語言。也因為如此,JSP 避免了許多在 Java Servlet 中必須出現的 println() 之類的敘述。可是 JSP 也跟 PHP 和 ASP 一樣比較難 debug。 首先我們先看一個小程式 -- Hello World -- JSP 版。請將這個網頁命名為 Hello.jsp 並將他 置放於 tomcat/webapps/test 的目錄內。為了能夠執行以下程式, 我們假設你們已經安裝好了 Apache Tomcat。
<html>
<head>
<title>
Hello World
</title>
</head>

<body>
<% String s = "Eric"; %>
<h1>Hello <%=s%></h1>
</body>
</html>
如果你能夠正確無誤的執行上面這一個範例,你可以檢查一下在 tomcat/work/DEFAULT 的目錄裡面有一個叫做 test 的子目錄,而這個子目錄裡面有 compile 好的 Hello_1.java 以及 Hello_1.class 檔。為什麼會有這兩個檔案呢?原來當使用者要求 Hello.jsp 的時候,Tomcat 會把 jsp 檔先轉換成 java 檔,然後再把 java compile 成 class 檔,最後才執行這個剛產生 的 class 檔。而這些新產生的 java 和 class 檔都會被放置在 tomcat/work 目錄之下。當這個 jsp 檔被再次呼叫的時候, Tomcat 會直接執行 tomcat/work 內的 class 檔。

互動式 Hello World -- JSP

以下只是使用 HTML 表格的部分元件範例,其他的元件請自行學習或者可以參考作者 非長久以前所寫的 Form Pages
  • 網頁: Greeting.html 置放於 tomcat/webapps/test 內。為了避免 中文的部份會出現亂碼,請記得加上 <meta> 那一行。
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=Big5">
    <title>
    Hello Form
    </title>
    </head>
    
    <body>
    <form method="post" action="http://localhost:8080/test/Greeting.jsp">
    <input type="text" value="老呂" name="data">
    <input type="submit">
    </form>
    </body>
    </html>
    
  • JSP 程式: Greeting.jsp
    • <%@ page language="java" contentType="text/html;charset=Big5" %> 這一行的目的在於告訴接收端(也就是瀏覽器)即將送過來的資料是以 Big5 編碼的。
    • String(request.getParameter("data").getBytes("ISO-8859-1"), "Big5"); 這一行的目的在於將客戶端傳送過來的 ISO-8859-1 資料轉換成 Big5。在客戶端 網頁內輸入的中文(也就是 Greeting.html 的文字欄位內的資料),會被傳送到 伺服器端;由於伺服器是 Tomcat,而 Tomcat 的預設編碼方式是 ISO-8859-1,因此我們 需要將 ISO-8859-1 的資料轉碼為 Big5;至於進一步的細節,如果讀者有興趣, 請閱讀細談 URL 編碼請注意: 雖然這個寫法感覺上比較複雜,但是無論 客戶端以 GET 或者 POST 的方式,都能正確的解碼。
    • 每一個 JSP 的程式都有四個已經定義的隱式物件(implicit object): request、response、session、以及 out。request 是 HttpServletRequest 的實體,response 是 HttpServletResponse 的實體,session 是 HttpSession 的實體,而 out 是 JspWriter 的實體。

    <%@ page language="java" contentType="text/html;charset=Big5" %>
    <html>
    <head>
    <title>
    Greetings
    </title>
    </head>
    
    <body>
    <% String s = 
       new String(request.getParameter("data").getBytes("ISO-8859-1"), "Big5");
    %>
    <h1>Hello <%=s%></h1>
    <%
       out.println("<h1>試試看 out 物件</h1>");
    %>
    </body>
    </html>
    

JSP 存取資料庫

存取資料庫的常見方式有兩種,分別是利用 JDBC-ODBC 以及純 JDBC 驅動程式兩種。JDBC-ODBC 的好處在於不必另外安裝驅動程式以避免 不必要的麻煩,但是其事前的準備動作(例如設定 DSN 等)以及存取速度比 純 JDBC 慢則是它的缺點。在這一段文件,我們分別提供一個範例程式來說明 這兩種方式。
  1. JDBC-ODBC 驅動程式:
    • 網頁:假設 DBServ.jsp 已經安裝於 tomcat/webapps/test/ 這個目錄內, 而且你已經依據MySQL Server 簡介的介紹 安裝了 MySQL 並且設定好了 ODBC。
      DSN:
      User ID:
      Password:
    • 程式
      <%@ page contentType="text/html;charset=Big5"
              language="java"
              import="java.sql.*" %>
      
      <%
          String dsn = request.getParameter("dsn");
          String uid = request.getParameter("uid");
          String pwd = request.getParameter("pwd");
      
          // initialize query string
          String aQuery = null;
          Connection conn = null;
      
          try
          {
            // load the JDBC-ODBC bridge driver
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
      
            // connect to Database
            aQuery = "select * from Product";
            conn = DriverManager.getConnection("jdbc:odbc:"+ dsn, uid, pwd);
      %>
      
            <html><head><title>
      <%
            out.println("Database " + dsn + " Query");
      %>
            </title></head><body>
      
      <%
            // Construct a SQL statement and submit it
            Statement aStatement = conn.createStatement();
            ResultSet rs = aStatement.executeQuery(aQuery);
      
            // Get info about the query results
            ResultSetMetaData rsmeta = rs.getMetaData();
            int cols = rsmeta.getColumnCount();
      
            // contruct table  
            out.println("<table border=\"1\">");
            out.println("<tr>");
      
            // Display column headers
            for(int i=1; i<=cols; i++)
            {
              out.print("<th>");
              out.print(rsmeta.getColumnLabel(i));
              out.print("</th>");
            }
            out.println("\n</tr>");
      
            // Display query results.
            while(rs.next())
            {
              out.print("<tr>");
              for(int i=1; i<=cols; i++)
              {
                out.print("<td>");
                out.print(rs.getString(i));
                out.print("</td>"); 
              }
              out.println("</tr>");
            }
      
            // Clean up
            rs.close();
            aStatement.close();
            conn.close();
          }
          // a better exception handling can be used here.
          catch (Exception e)
          {
            System.out.println("Exception Occurs: " + e);
          }
      %>
          </body></html>
      
    • 請仔細的比較這個 JSP 檔跟之前的 servlet 有什麼不同?看到這裡你 應該慢慢有「要學好 JSP 還是得學好 Java」的感覺吧!
    • 如果你需要 import 更多的類別,你可以以 "," 當作類別名稱之間的 分隔記號。
    • 練習題:請將之前的 Worker 範例轉換成 JSP 版。使用者可以從 網頁輸入工作時數後,然後由 JSP 計算出薪資。切記,請保持 Worker 這個類別。(提示:可以以 import 的方式把 Worker 這個類別載入。)
  2. 純 JDBC 驅動程式:
    • 網頁:假設 JDBCServ.jsp 已經安裝於 tomcat/webapps/test/ 這個目錄 內,而且你已經依據MySQL Server 簡介的介紹 安裝了 MySQL 並且已經在 Tomcat 內安置好了 JDBC 驅動程式。
      User ID:
      Password:
    • 程式
      <%@ page contentType="text/html;charset=Big5"
              language="java"
              import="java.sql.*" %>
      
      <%
          String uid = request.getParameter("uid");
          String pwd = request.getParameter("pwd");
      
          // 初始化變數
          String aQuery = null;
          Connection conn = null;
      
          try
          {
            // 載入 JDBC 的驅動程式
            Class.forName("com.mysql.jdbc.Driver");
      
            // 欲執行之 SQL 查詢語法
            aQuery = "select * from Product";
      
            // 產生程式和資料庫之間的連線
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", uid, pwd);
      %>
      
            <html><head><title>
            查詢產品資料表的結果
            </title></head><body>
      
      <%
            // 查詢產品資料表的結果
            Statement aStatement = conn.createStatement();
            ResultSet rs = aStatement.executeQuery(aQuery);
      
            // 取得產品資料表的 metadata
            ResultSetMetaData rsmeta = rs.getMetaData();
            int cols = rsmeta.getColumnCount();
      
            // 建立 HTML table 來顯示資料表的內容
            out.println("<table border=\"1\">");
            out.println("<tr>");
      
            // 顯示欄位名稱
            for(int i=1; i<=cols; i++) {
              out.print("<th>");
              out.print(rsmeta.getColumnLabel(i));
              out.print("</th>");
            }
            out.println("\n</tr>");
      
            // 顯示資料表內的內容
            while(rs.next()) {
              out.print("<tr>");
              for(int i=1; i<=cols; i++) {
                out.print("<td>");
                out.print(rs.getString(i));
                out.print("</td>"); 
              }
              out.println("</tr>");
            }
      
            // 結束前,把該關閉的關閉
            rs.close();
            aStatement.close();
            conn.close();
          }
          // 可以考慮設計成比較好的例外處理:例如載入驅動程式錯誤、連接資料庫失敗等
          catch (Exception e) {
            System.out.println("發生錯誤: " + e.getMessage());
          }
      %>
          </body></html>
      

JSP 的生命週期

JSP 的生命週期跟 servelt 很像,根據 Sun 的文件:一旦 JSP 的網頁被轉換成 servlet(ie. translate)以及編譯之後,這個 JSP servlet 的生命週期基本上 就會遵循 servlet 的生命週期。JSP 生命週期如下所述:
  1. 如果 JSP 網頁的 servlet 物件不存在,那麼 tomcat 會:
    1. 載入這個 JSP servlet 的類別
    2. 產生並初始化這個類別的物件
    3. 一旦物件形成了之後,tomcat 會經由 jspInit 方法來呼叫這個物件
  2. 如果 JSP 網頁的 servlet 物件存在,那麼 tomcat 會呼叫這個物件的 _jspService 方法,並將 requestresponse 的物件傳遞給它。
如果 tomcat 需要移除某個JSP 網頁的 servlet 物件,它會呼叫該物件的 jspDestroy 方法。 在了解的 JSP 的生命週期之後,我們可以讓包含有資料庫連線的 JSP 網頁更有效率。 仔細觀察之前 JSP 與資料庫的範例:在該範例中(其實所有資料庫連線的 JSP 網頁 都可以檢查),每一次使用者要求執行該程式的時候,它都會產生 一個 connection 物件,這樣子一來,由於 (1) 產生 connection 的成本很高, 且 (2) 每一個資料庫伺服器能接受的 connection 數又有上限的限制, 這會造成這個程式非常沒有效率。根據之前所說明的 JSP 生命週期,我們能不能 重新修改這個程式使其更有效率?
  • 網頁:我們假設你已經將 NewDBServ.jsp 安裝於 tomcat/webapps/test/ 目錄內,而且已經設定好了 dsn 為 csie 的 ODBC。
    Price >
  • 程式:這個程式裡面有幾件需要注意的事項
    • 第一個就是 <%! 的用法:之前所說的 JSP 程式碼都是屬於 _jspService() 這個方法內的;如果我們需要宣告一些程式碼,而這些 程式碼不屬於 _jspService() 內的,我們必須加上 <%!。 或者從物件導向的角度來看,凡是宣告在 <%! 內的變數即為該 servlet 類別的資料成員,該類別的所有方法成員都可以存取它們;而宣告在 <%! 內的方法也是該類別的方法成員。
    • 為了避免 connection 不斷的被產生、連接、以及結束,我們在 jspInit() 產生一個 connection 物件;由於同樣的 JSP servlet 物件 (在這個範例中就是 NewDBServ)共用宣告出來的 connection 物件,如此 JSP 程式的執行速度會變快。
    • 變數 count 在這個程式中只是用來表現出 jspInit() 只被執行一次。
    <%@page contentType="text/html;charset=Big5"
            language="java"
            import="java.sql.*" %>
    
    <html>
    
    <!-- 加上 ! 宣告成為類別的屬性,否則便成為方法內的變數 -->
    <%! private Connection conn = null;
        private int count = 0;
    %>
    
    <%!
        // 用來產生一個資料庫的連線,提供所有使用這個 JSP 檔的要求使用
        public void jspInit() {
          count = 10;
          try {
            // 載入 JDBC-ODBC 驅動程式
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    
            // 請記得設定適當的 UID 和 PWD。
            conn = DriverManager.getConnection("jdbc:odbc:csie", UID, PWD);
          }
          catch (Exception e) {
            System.out.println("Fail to connect to database.");
          }
        }
    
        // 在這個 JSP 檔從 tomcat 卸載時,將之前的資料庫連線結束掉
        public void jspDestroy() {
          try {
            conn.close();
          } catch (Exception e) {
            System.out.println("Fail to close connection.");
          }
        }
    %>
    
    <%
        count++;
    
        String salary = request.getParameter("price");
    
        // initialize query string
        String aQuery = null;
    
        try {
          // connect to Database
          aQuery = "select * from Product where Price > " + salary;
    %>
    
          <head><title>Database Query</title></head>
          <body>
          <h3>Accessed <%=count%> times</h3>
    
    <%
          // Construct a SQL statement and submit it
          Statement aStatement = conn.createStatement();
          ResultSet rs = aStatement.executeQuery(aQuery);
    
          // Get info about the query results
          ResultSetMetaData rsmeta = rs.getMetaData();
          int cols = rsmeta.getColumnCount();
    
          // contruct table  
          out.println("<table border=\"1\">");
          out.println("<tr>");
    
          // Display column headers
          for(int i=1; i<=cols; i++) {
            out.print("<th>");
            out.print(rsmeta.getColumnLabel(i));
            out.print("</th>");
          }
          out.println("\n</tr>");
    
          // Display query results.
          while(rs.next()) {
            out.print("<tr>");
            for(int i=1; i<=cols; i++) {
              out.print("<td>");
              out.print(rs.getString(i));
              out.print("</td>"); 
            }
            out.println("</tr>");
          }
    
          // Clean up
          rs.close();
          aStatement.close();
        }
        // a better exception handling can be used here.
        catch (Exception e) {
          System.out.println("Exception Occurs: " + e);
        }
    %>
        </body></html>
    

  • 如果你有許多個 JSP 網頁都需要宣告 conn 的物件以及相同的資料庫 連結,比較好的方法就是把 <%! 內的敘述把他包成一個 檔案(例如 initdestroy.jsp 並把這個檔案放到 tomcat/webapps/test/ 目錄內),然後把這些敘述從 NewDBServ.jsp 內去除,並加入
    <%@include file="initdestroy.jsp" %>
    

從 web.xml 讀取初始值

在之前連結資料庫的範例中,我們都需要經由 HTML 表單元件中指定某些 參數(例如,表格或者 dsn 名稱)或者執行程式前的某些特定的初始值 (例如,JDBC 的驅動程式名稱)。這些初始值當然可以像之前的範例中 將它設定在程式碼中,但是這樣的作法不太有彈性。假設 dsn 或者 JDBC 驅動程式的名稱必須依據環境的變動而改變的時候,我們必須找出所有的程式碼, 然後在更改完正確的名稱之後,還要重新編譯程式,這樣一來非常沒效率; 更何況,如果漏了改幾個程式,這所衍生的麻煩不知道有多少。 為了解決這個問題,大多數系統開發人員會利用設定檔的方式來進行; 也就是說,我們把這些可能改變的資訊,放在一個設定檔中,然後每次執行 程式的時候,該程式會從設定檔讀取最新的資訊。Java 常用的設定檔 有兩個格式:一個是 使用 Properties 來初始化你的程式(Properties 是 java.util.Properties 物件), 另一個是 XML,而 tomcat 對於所有 JSP/servlet 的設定是由 web.xml 負責。
web.xml 內的資料分成兩種:一種是只能讓某些特定 JSP/servlet 讀取的資料 (在本範例中就是 HelloParam.jsp), 另一種是允許在某特定目錄(在本範例中就是 tomcat/webapps/test; 其實它比較正式的說法是"某特定 application"、"某特定 web application"、 或者"某特定 context")內所有 JSP/servlet 都能讀取的資料。 首先,我們先說明第一種資料的存取方式。
如果要讀取 web.xml 內設定給某特定 JSP/servlet(在本範例中為 HelloParam.jsp )的資料,我們利用 JSP 定義的一個內建物件 config, 而這個物件的類別是 javax.servlet.ServletConfig。
  • web.xml: web.xml 的根元素為 <web-app>,它的存放位置在 tomcat/webapps/test/WEB-INF 目錄內。在這個檔案內,我們設定了 兩對資料:一對是名為 dsn,而值為 csie;另一對是名為 param2,而值為 你好嗎。每一對資料由 <init-param> 所描述, 而名稱由 <param-name> 所描述,值由 <param-value> 所描述。由於這些設定是給 HelloParam.jsp 用的,所以這些資料的描述就成為 <servlet> 的子元素; <servlet> 還有兩個子元素需要設定:一個是 <servlet-name>(當作"該 servlet"的名稱),另一個是 <jsp-file>(用來指定 JSP 的檔案名稱,在名稱之前必須加上 "/")。 有了以上設定,我們還需要告訴 tomcat 當使用者輸入某個 URL 的時候,它會去 執行剛剛設定的 JSP 檔案;因此,我們需要加上 <servlet-mapping> 元素,該元素包含兩個子元素:一個是 servlet 的名稱,它的設定方式與之前的 <servlet-name> 必須完全相同;另一個就是指定 URL 的方式, 在本範例中,即為 <url-patterm>/HelloParam.jsp</url-patterm>
    <?xml version="1.0" encoding="Big5"?>
    <web-app>
      <servlet>
        <servlet-name>ReadParameters</servlet-name>
        <jsp-file>/HelloParam.jsp</jsp-file>
        <init-param>
          <param-name>dsn</param-name>
          <param-value>csie</param-value>
        </init-param>
        <init-param>
          <param-name>param2</param-name>
          <param-value>你好嗎</param-value>
        </init-param>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>ReadParameters</servlet-name>
        <url-pattern>/HelloParam.jsp</url-pattern>
      </servlet-mapping>
    </web-app>
    
  • HelloParam.jsp 的原始碼如下所示。在程式中,有兩個 config 物件的 方法:一個是 getParameter(name),它是用來存取名為 name 的值;如果 name 為 "dsn",則該名稱的值 csie 會被回傳。 另一個是 getServletName(),它是用來取得 <servlet-name> 的內容。
    <%@page contentType="text/html;charset=Big5" %>
    <html>
    <body>
    <%
      String p1 = config.getInitParameter("dsn");
      String p2 = config.getServletName();
      String p3 = config.getInitParameter("param2");
    %>
      <h3 align="center"><%=p1%></h3>
      <h3 align="center"><%=p2%></h3>
      <h3 align="center"><%=p3%></h3>
    </body>
    </html>
    
    執行 HelloParam.jsp 之後,其結果如下所示:

  • 練習題: 請把 web.xml 內 <url-pattern> 改成 <url-pattern>/hhh.jsp</url-pattern>,然後在瀏覽器 輸入 http://localhost:8080/test/hhh.jsp 並觀察其執行結果。想想看, 這樣做有什麼好處?



如果要讀取 web.xml 內設定給"該 application"的資料,我們可以利用 JSP 定義的一個內建物件 application,而這個物件的類別是 javax.servlet.ServletContext。
  • web.xml: 除了之前的設定之外,我們加上了一些在"該 application"內所有 JSP/servlet 使用的資料,這些設定特別由綠色標示出來,內容如下:
    <?xml version="1.0" encoding="Big5"?>
    <web-app>
    
      <context-param>
        <param-name>jdbc_driver</param-name>
        <param-value>sun.jdbc.odbc.JdbcOdbcDriver</param-value>
      </context-param>
      <context-param>
        <param-name>db_server</param-name>
        <param-value>mysql</param-value>
      </context-param>
      <context-param>
        <param-name>db_port</param-name>
        <param-value>3306</param-value>
      </context-param>
    
    
      <servlet>
        <servlet-name>ReadParameters</servlet-name>
        <jsp-file>/HelloParam.jsp</jsp-file>
        <init-param>
          <param-name>dsn</param-name>
          <param-value>csie</param-value>
        </init-param>
        <init-param>
          <param-name>param2</param-name>
          <param-value>你好嗎</param-value>
        </init-param>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>ReadParameters</servlet-name>
        <url-pattern>/HelloParam.jsp</url-pattern>
      </servlet-mapping>
    </web-app>
    
    從 web.xml 的綠色部分可以看得出來,其設定的方式跟之前的類似,不一樣的 只是標籤的名稱不同:分別用 <context-param> 來設定一對 資料,資料名稱以及值仍然由 <param-name><param-value>來描述。
  • 由於定義方式不同,程式碼當然略有不同:
    <!-- HelloContext.jsp -->
    <%@page contentType="text/html;charset=Big5" %>
    <html>
    <body>
    <%
      String p1 = application.getInitParameter("jdbc_driver");
      String p2 = application.getInitParameter("db_server");
      String p3 = application.getInitParameter("db_port");
    %>
      <h3 align="center"><%=p1%></h3>
      <h3 align="center"><%=p2%></h3>
      <h3 align="center"><%=p3%></h3>
    </body>
    </html>
    
    仔細觀察的話,讀者會發現,HelloParam.jsp 和 HelloContext.jsp 不一樣的地方 只在於前者使用 config 存取資料,而後者使用 application。執行該網頁的結果 如下:

  • 請注意: 無論是 config 或者是 application,如果你希望在 <%! 的區塊內使欲用的話,在使用前你必須先取得它,取得方式如下:
    // 取得 config
    ServletConfig config = getServletConfig();
    
    // 取得 application
    ServletContext application = getServletContext();
    
  • 練習題: 由於兩種資料的有效範圍不同,請完成下列兩項工作:
    1. 請修改 HelloParam.jsp 使其能夠存取 <context-param> 的資料。
    2. 請修改 HelloContext.jsp 確認其無法存取 <init-param> 的資料。








Cookie

The HTTP protocol does not support persistent information that could help a web server determine that a request is from a particular client. 例如,某些網頁需要知道使用者是否 已經登入,若已經登入便可以看網頁的內容,否則要求使用者登入。 一般來說,有兩種方式可以讓 web server 經由 persistent information 來判斷,而這兩種方法為 Cookie 和 Session。Cookie 是儲存於客戶端(browser 內)的資料。由於這是一種由 伺服器端的程式傳送資料到客戶端的一種簡易的方式,因此被大量 的使用於 web 為主的系統中,例如客戶輸入的資料,客戶的偏好 等等。這些資料先由伺服器端的程式寫到客戶端,然後之後的聯繫便可以 從這些資料來提供不同的服務。例如我們可以將使用者的帳號以及 密碼存放在 cookie 內,下一次使用者要登入的時候,就可以從 cookie 來判斷,如果已經存在就不需要再輸入一次。
不過,cookie 也有一些限制,例如一個 cookie 不可以超過 4KB, 而瀏覽器也會對 cookie 的總數作限制,cookie 有可能會有不見的 時候,所以程式寫作的時候要謹慎。另外,由於 cookie 大家都能 讀取,因此也有安全上的考量。
  • 讀取 cookie:建議先執行這個程式來了解究竟 cookie 寫進去了沒, 等到下一個程式(SetCookies)執行完後,再執行一次並比較兩次的不同。
    <!--
       UseCookies.jsp
    -->
    
    <%@ page language="java" contentType="text/html;charset=Big5" %>
    
    <html>
    <head><title>Use Cookies</title></head>
    <body>
    
    <%
        Cookie [] cookie = request.getCookies();
        if(cookie.length == 0)
    %>
          <h1>沒有 Cookie</h1>
    <%  else 
        {
          out.println("<h1>大家好</h1>");
          out.println("<ul>");
          for(int i=0; i<cookie.length; i++)
            out.println("<li> " + cookie[i].getName() + "=" +
                        cookie[i].getValue());
          out.println("</ul>");
        }
    %>
    </body>
    </html>
    
  • 寫入 cookie
    <!--
       SetCookies.jsp
    -->
    
    <%@ page language="java" contentType="text/html;charset=Big5" %>
    
    <html>
    <head><title>Set Cookies</title></head>
    <body>
    
    <%
        Cookie myck1 = new Cookie("UID", "123");
        response.addCookie(myck1);
        Cookie myck2 = new Cookie("PWD", "abc");
        response.addCookie(myck2);
    %>
    <h1>寫入 Cookies</h1>
    </body>
    </html>
    

Custom Tag Library

Custom Tag Library 是一種 JSP 的特殊使用的標籤,它允許開發人員 以自訂的標籤名稱來(如 XML )達成某些目標。為了達成上述之目標, 你需要定義一個 TLD 檔(tag library descriptor)來描述開發人員自訂 標籤的相關訊息,例如標籤名稱 name,tag 所符合的 tlibversionjspversion 等。除此之外,你也需要定義一個 tag 的處理器, 也就是說當 jsp engine 處理到這個自訂的 tag 之後,他所需要去呼叫 的程式,而由這個程式來處理這個 tag 的內容以及功能。 在以下的例子中,我們希望定義一個 hello 的標籤,而這個 標籤內有一個 name 的屬性,我們的 jsp engine 碰到 hello 時,就會把 name 的屬性以 table 的方式呈現。這個例題是從 Sue Spielman 的文章修改而成。
  • TLD 檔: tagclass 用來定義 tag 處理器的名稱。tag 的名稱 定義在 <tag><name>hello</name><tag>, 而這個 tag 有一個屬性定義在 <attribute><name>name</name></attribute>
    <?xml version="1.0" encoding="Big5" ?>
    <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" 
                            "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
    <taglib>
      <tlibversion>1.0</tlibversion>
      <jspversion>1.1</jspversion>
      <shortname>Hello 系列</shortname>
      <info>Hello 系列:沒 body 的範例</info>
    
      <tag>
        <name>hello</name>
        <tagclass>Hello.Hello1</tagclass>
        <bodycontent>empty</bodycontent>
        <attribute>
          <name>name</name>
          <required>false</required>
        </attribute>
      </tag>
    </taglib>
    
  • tag 處理器: 處理器至少要定義所有自訂 tag 的屬性的 setter 以及 getter。另外,還有 doStartTag() 來定義遇到 tag 的時候要如何處理, 以及 doEndTag() 來定義 tag 結束後要如何處理。
    package Hello;
    
    import javax.servlet.jsp.*;
    import javax.servlet.jsp.tagext.*;
    
    /**
     * This is a simple tag example to show how content is added to the
     * output stream when a tag is encountered in a JSP page. 
     */
    public class Hello1 extends TagSupport {
      private String name=null;
    
      // Getter/Setter for the attribute name
      public void setName(String value){
        name = value;
      }
    
      public String getName(){
        return(name);
      }
    
      /**
       * doStartTag is called by the JSP container when the tag is encountered
       */
      public int doStartTag() {
        try {
          JspWriter out = pageContext.getOut();
          out.println("<table border=\"1\">");
          if (name != null)
            out.println("<tr><td> Hello " + name + " </td></tr>");
          else
            out.println("<tr><td> Hello World </td></tr>");
        } catch (Exception ex) {
          throw new Error("All is not well in the world.");
        }
    
        // Must return SKIP_BODY because we are not supporting a body for this 
        // tag.
        return SKIP_BODY;
      }
    
      /**
       * doEndTag is called by the JSP container when the tag is closed
       */
      public int doEndTag(){
        try {
          JspWriter out = pageContext.getOut();
          out.println("</table>");
        } catch (Exception ex){
          throw new Error("All is not well in the world.");
        }
    
        return SKIP_BODY;
      }
    }
    
  • JSP 檔:
    <%@ taglib uri="Hello.tld" prefix="sample" %>
    <html>
    <head>
    <title>Your Standard Hello World Demo</title>
    </head>
    <body bgcolor="#ffffff">
      <hr />
      <sample:hello name="Sue"/>
      <hr />
    </body>
    </html>
    


Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu












處理 SOAP Header

處理 SOAP Header

This document is provided as is. You are welcomed to use it for non-commercial purpose.

Written by: 國立中興大學資管系呂瑞麟

請勿轉貼


目錄

  1. 處理 SOAP Header
    1. 首先建立一個沒有 SOAP Header 的 web service 以及 client
  2. SOAP Header 的處理流程
  3. 開發 .NET client 來處理 SOAP Header
    1. 開發一個回傳 SOAP Header 的 service 以及使用 SOAP Header 的 .NET client
    2. 開發 .NET client 來處理 AXIS service 傳來的 SOAP Header



處理 SOAP Header

我們在之前的說明中,著重在如何開發 Axis 的服務以及客戶端程式。 可是在實務上,我們需要更多的功能:例如,SOAP 封包內如果包含機密 內容而不希望被惡意人士擷取、或者我們希望能夠控制只允許某些特定人士 才能使用服務等等。為了能夠達成以上目標,我們必須能夠善用 SOAP Header。 參考資料:

AXIS 和 .NET 的互通性

AXIS 和 .NET 的互通性

This document is provided as is. You are welcomed to use it for non-commercial purpose.

Written by: 國立中興大學資管系呂瑞麟

請勿轉貼


目錄

  1. 開發 ASP.NET 的客戶端來存取 AXIS 的服務: simple datatypes
  2. 開發與使用回傳字串陣列的 AXIS 服務
    1. 開發 AXIS 客戶端程式
    2. 開發 .NET 客戶端程式
  3. 開發與使用回傳 JavaBean 的 AXIS 服務
    1. 開發 AXIS 客戶端程式
    2. 開發 .NET 客戶端程式
  4. 開發與使用回傳 JavaBean 陣列的 AXIS 服務
    1. 開發 AXIS 客戶端程式
    2. 開發 .NET 客戶端程式



開發 ASP.NET 的客戶端來存取 AXIS 的服務

開發 Web Services 的最主要的目的之一,就是希望能達到跨平台性, 然而可惜的是:事實並非如此,因為每一個廠商的 implementation 或多或少有一些差異。目前,在 Java 的世界,Axis 是最常用的 web service 平台(根據 Axis 的 FAQ,採用 Axis 技術的商用軟體包含 BEA's Weblogic Platform, Borland, IBM WebSphere, Oracle Web Services Manager, JBoss 等);而在微軟的世界裡, .NET 卻是最常見的 web services 平台;因此,這兩者之間的 interoperability (稱為互通性)就更形重要。在前一節的文章中,我已經介紹了如何開發 Axis 客戶端 程式來存取 .NET 的服務;在本文中,,我會敘述如何開發 ASP.NET 的客戶端程式 來存取 Axis 的服務 我參考的主要資料來源為: 為了達到跨平台性,有幾個地方要特別注意的:
  1. 如之前說過的,傳送的 SOAP 資料格式必須設定成 wrapped/literal。這是因為 .NET 的預設傳送格式是 wrapped/literal,而 AXIS 卻不是。
  2. 目前確定可以交換的資料型態包含 Java 的 String、boolean、byte、short、int、long、float、以及 double。另外,上述資料型態的陣列,JavaBean,以及 JavaBean 的陣列也可以正確的傳送。
  3. 避免交換 multi-dimensional 和 jagged 陣列,以及避免利用 overloading 來 定義 web services。
我們的測試環境是:Windows XP Professional SP2 + IIS 5.1 + .NET Framework 2.0 + .NET Framework SDK 2.0。 開發的步驟包含:
  1. 利用 WSDL 檔案來產生 ASP.NET 的 proxy 檔案(這部份的概念跟 Axis 相同, ASP.NET 的程式也是利用 proxy 類別來跟遠端的服務來連結的,在本範例中, QueryTimeService.vb 就是 proxy 類別)。產生 proxy 類別的方式很簡單, 你只要使用 .NET Framework SDK 2.0 所提供的 wsdl.exe 即可;執行的指令為 wsdl /l:vb /protocol:SOAP http://localhost:8080/axis/services/TimeService?wsdl。你也可以不必使用 HTTP 存取 WSDL 檔,WSDL 檔也可以是一個 local 的檔案。由於我們開發的程式是以 VB 來撰寫,因此才有 /l:vb 的選項。
  2. 利用剛剛產生的原始碼來編譯並產生 .dll 檔(稱為 assembly)。 執行的指令為 vbc /out:bin\QueryTimeService.dll /t:library /r:System.Web.Services.dll /r:System.Xml.dll QueryTimeService.vb;QueryTimeService.vb 是前一個步驟所產生的原始碼,而這個步驟會產生一個 QueryTimeService.dll 的 .dll 檔。而這個 .dll 檔的儲存位置在目前這個路徑底下,名稱為 bin 的子目錄 內(由於執行 ASP.NET 的程式時,ASP 會自動從 bin 子目錄內去尋找所需的 .dll 檔,因此我們建議不要設定其它的輸出路徑)。假設我們執行第一和第二步驟 的路徑是 c:\Inetpub\wwwroot,則 QueryTimeService.vb 位於 c:\Inetpub\wwwroot,而 QueryTimeService.dll 位於 c:\Inetpub\wwwroot\bin
  3. 開發 ASP.NET 的客戶端程式。如果剛剛 QueryTimeService.dll 位於 c:\Inetpub\wwwroot\bin,則下列的 ASP.NET 的程式必須置放於 c:\Inetpub\wwwroot
    <%@Page Language="VB" %>
    <script runat=server>
    Sub Page_Load(o As Object, e As EventArgs)
      ' 這個是 Axis 的 service
      Const URL as String ="http://hostname:8080/axis/services/TimeService"
    
      ' QueryTimeService 的類別已經由 wsdl.exe 和 vbc.exe 產生
      ' 這個 QueryTimeService.dll 必須放在這個 .aspx 檔的同一個
      ' 目錄底下的 bin 目錄;依照我們的範例,就是放在
      ' c:\Inetpub\wwwroot\bin 內
      Dim service as New QueryTimeService()
    
      ' 設定服務的 URL
      service.Url = URL
    
      ' 呼叫服務的 getTime()
      Dim Answer As String = service.getTime()
    
      Label1.Text = Answer
    End Sub
    </script>
    <html>
    <body>
    <h2 align="center">ASP SOAP Client</h2>
    <hr/>
    <font color="gray" size="5">
    <asp:Label id="Label1" runat="server" />
    </font>
    <p/><p/>
    </body>
    </html>
    
  4. 執行這個 ASP.NET 的程式會得到如下的畫面:

開發與使用回傳字串陣列的 AXIS 服務

由之前的討論,Axis 和 .NET 之間可以互傳字串陣列、符合 JavaBean 規格 的物件、以及物件陣列,我們就依序測試。首先測試的是字串陣列:
  1. AXIS service: ArrayService.java 該服務會將服務的時間,以年、月、日、 時、分、秒的字串陣列回傳。請注意,由於 AXIS 的預設狀況 為 "Request" scope,也就是每一次 service 被呼叫的時候,AXIS 都會產生 一個新的 ArrayService 的物件,因此,每一次呼叫這個 service 都會回傳 最新的時間。
    import java.util.*;
    
    public class ArrayService {
      private String[] date;
    
      public ArrayService() {
        Calendar now = Calendar.getInstance();
        date = new String[6];
        date[0] = String.valueOf(now.get(Calendar.YEAR));
        date[1] = String.valueOf(now.get(Calendar.MONTH) + 1);
        date[2] = String.valueOf(now.get(Calendar.DATE));
        date[3] = String.valueOf(now.get(Calendar.HOUR));
        date[4] = String.valueOf(now.get(Calendar.MINUTE));
        date[5] = String.valueOf(now.get(Calendar.SECOND));
      }
    
      public String[] getTimeArray() {
        return date;
      }
    }
    
  2. 註冊服務,該服務的 WSDD 檔 deploy.wsdd 如下所示:
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <service name="ArrayService" provider="java:RPC" style="wrapped" use="literal">
        <parameter name="className" value="ArrayService" />
        <parameter name="allowedMethods" value="*" />
      </service>
    </deployment>
    

  3. 將 service 佈置好了以後,就可以開始開發客戶端程式。
  4. 開發 AXIS 的客戶端程式
    1. 我們利用 AXIS 所提供的 WSDL2Java 的工具來產生所需要的 proxy 類別,然後開發客戶端程式如下:
    2. ArrayClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      import _aa._bb._cc._cc.axis.services.ArrayService.*;
      
      public class ArrayClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          ArrayServiceService service = new ArrayServiceServiceLocator();
          ArrayService call = service.getArrayService();
          
          String[] result = call.getTimeArray();
      
          System.out.println(result[0]);
          System.out.println(result[1]);
          System.out.println(result[2]);
        }
      }
      
  5. 開發 .NET 的客戶端程式
    1. 將 service 佈置好了以後,就可以開始開發客戶端程式。過程跟之前所說的 一樣,先利用 wsdl.exe 以及 service 的 WSDL 檔來產生 proxy 類別,然後 在依照 proxy 類別產生 .dll 檔。
    2. VBArrayClient.aspx
      <%@Page Language="VB" %>
      <script runat="server">
      Sub Page_Load(o As Object, e As EventArgs)
        ' 這個是 Axis 的 service
        Const URL as String ="http://hostname:8080/axis/services/StringArray"
      
        Dim service as New ArrayServiceService()
      
        ' 設定服務的 URL
        service.Url = URL
      
        ' 呼叫服務的 getTime()
        Dim Answer() As String = service.getTimeArray()
      
        Label1.Text = Answer(0)
        Label2.Text = Answer(1)
        Label3.Text = Answer(2)
        Label4.Text = Answer(3)
        Label5.Text = Answer(4)
        Label6.Text = Answer(5)
      End Sub
      </script>
      <HTML>
      <BODY>
      <h2 align="center">ASP SOAP Client</h2>
      <hr/>
      <font color="gray" size="5">
      年/月/日: <asp:Label id="Label1" runat="server" />/<asp:Label id="Label2" runat="server" />/<asp:Label id="Label3" runat="server" /><br/>
      時/分/秒: <asp:Label id="Label4" runat="server" />/<asp:Label id="Label5" runat="server" />/<asp:Label id="Label6" runat="server" /><br/>
      </font>
      <p/><p/>
      </body>
      </html>
      

開發與使用回傳 JavaBean(或者複雜的資料型態)的 AXIS 服務

就像之前文件的說明,我們不能任意的傳送 Java Objects,目前只建議 傳送 JavaBean。但是,傳送的 JavaBean 被 SOAP 訊息正確的表達之後 (也就是所謂的 serialize 或者 unmarshall), 我們必須注意 .NET 可以正確的解讀(也就是所謂的 deserialize 或者 unmarshall)。 由於 serialize/deserialize 的問題,我一直無法正確的讀取資料, 這一個問題困擾了我半天,終於在輸入正確的 keywords 之後,Goolge 大神 引導我到這一篇解答: Solved: null responses with interop between Axis service and .NET client。 我們以範例來說明,要如何設計一個符合 JavaBean 的物件,讓 .NET 的 client 能夠正確的從 Axis service 讀取。
  1. 定義一個 JavaBean: DateBean.java 這個物件將會被 Axis service 傳送。
    import java.util.*;
    
    public class DateBean {
      private int year, month, day;
    
      public DateBean() {
        Calendar now = Calendar.getInstance();
        year = now.get(Calendar.YEAR);
        month = now.get(Calendar.MONTH);
        day = now.get(Calendar.DATE);
      }
    
      public int getYear() {
        return year;
      }
    
      public int getMonth() {
        return month + 1;
      }
    
      public int getDay() {
        return day;
      }
    
      public void setYear(int y) {
        year = y;
      }
    
      public void setMonth(int m) {
        month = m;
      }
    
      public void setDay(int d) {
        day = d;
      }
    }
    
  2. 定義服務:我們開發一個簡單的 BeanService.java。請特別留意,在 getDate() 這個方法回傳的是一個 DateBean 物件。
    public class BeanService {
      public DateBean getDate() {
        DateBean d = new DateBean();
        return d;
      }
    }
    
  3. 定義 wsdd 檔:這個檔案有幾個需要特別注意的地方:
    1. 我們必須增加一個 beanMapping 宣告,這個宣告就是下列範例中呈現暗紅色 的這一段。宣告中,明確的說明 DateBean 物件是一個以 Java 所寫出來的物件, 而且它的 QName 是 myNS:DateBean。
    2. 由於 AXIS 和 .NET 的轉換過程中,非常容易出現 namespace 對不上的情形, 而因為我們採用的是先開發 JavaBean 才由 AXIS 轉成相對的 WSDL 檔,又因為 AXIS 的預設行為就是產生一個名為 http://DefaultNamespace 的 targetNamespace,所以在為 DateBean 定義 namespace 的時候,我們就採用 http://DefaultNamespace;否則 .NET 客戶端所接受到的資料會是 0。 希望以後能夠找得到一種經由 AXIS 的設定,可以採用我們自訂的 namespace 的方式。
  4. <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <service name="BeanService" provider="java:RPC" style="wrapped" use="literal">
        <parameter name="className" value="BeanService" />
        <parameter name="allowedMethods" value="*" />
    
        <beanMapping qname="myNS:DateBean" xmlns:myNS="http://DefaultNamespace"
                     languageSpecificType="java:DateBean" />
    
      </service>
    </deployment>
    
  5. 將 service 佈置好了以後,就可以開始開發客戶端程式。
  6. 開發 AXIS 的客戶端程式
    1. 過程跟之前所說的一樣,先利用 WSDL2Java 以及 service 的 WSDL 檔來產生 proxy 類別,然後把這些類別 import 到客戶端程式即可。由於我們在 WSDD 內定義 DateBean 的 namespace 是 DefaultNamespace,因此 WSDL2Java 會產生一個 DateBean.java 的程式在 DefaultNamespace 的子目錄內,所以程式中也記得 要把 DefaultNamespace import 進來。
    2. BeanClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      import _aa._bb._cc._dd.axis.services.BeanService.*;
      import DefaultNamespace.*;
      
      public class BeanClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          String URL = "http://aa.bb.cc.dd:8080/axis/services/BeanService";
      
          BeanServiceService service = new BeanServiceServiceLocator();
          BeanService call = service.getBeanService();
          
          DateBean result = call.getDate();
      
          System.out.println(result.getYear());
          System.out.println(result.getMonth());
          System.out.println(result.getDay());
        }
      }
      
  7. 開發 .NET 的客戶端程式
    1. 過程跟之前所說的一樣,先利用 wsdl.exe 以及 service 的 WSDL 檔來產生 proxy 類別,然後依照 proxy 類別產生 .dll 檔。跟 Axis 不一樣的地方,.NET 的 wsdl.exe 並不會單獨在 DefaultNamespace 目錄內,產生 DateBean 的 proxy 類別。
    2. VBBeanClient.aspx
      <%@Page Language="VB" %>
      <script runat="server">
      Sub Page_Load(o As Object, e As EventArgs)
        ' 這個是 Axis 的 service
        Const URL as String ="http://hostname:8080/axis/services/BeanService"
      
        Dim service as New BeanServiceService()
      
        ' 設定服務的 URL
        service.Url = URL
      
        ' 呼叫服務的 getDate()
        Dim Answer As DateBean = service.getDate()
      
        Label1.Text = Answer.year
        Label2.Text = Answer.month
        Label3.Text = Answer.day
      End Sub
      </script>
      <HTML>
      <BODY>
      <h3 align="center">ASP.NET 2.0 SOAP Client</h3>
      <h4 align="center">Process JavaBeans</h4>
      <hr/>
      <font color="gray" size="5">
      年/月/日: <asp:Label id="Label1" runat="server" />/<asp:Label id="Label2" runat="server" />/<asp:Label id="Label3" runat="server" /><br/>
      </font>
      <p/>
      </body>
      </html>
      
    3. 執行後的畫面如下:

開發與使用回傳 JavaBean 陣列的 AXIS 服務

  1. 定義 JavaBean:TimeBean.java
    import java.util.*;
    
    public class TimeBean {
      private int hour, minute, second;
    
      public TimeBean() {
        Calendar now = Calendar.getInstance();
        hour = now.get(Calendar.HOUR);
        minute = now.get(Calendar.MINUTE);
        second = now.get(Calendar.SECOND);
      }
    
      public int getHour() {
        return hour;
      }
    
      public int getMinute() {
        return minute;
      }
    
      public int getSecond() {
        return second;
      }
    
      public void setHour(int h) {
        hour = h;
      }
    
      public void setMinute(int m) {
        minute = m;
      }
    
      public void setSecond(int s) {
        second = s;
      }
    }
    
  2. 開發服務:TimeArray.java 該服務會產生兩個 TimeBean 物件,並以 物件陣列的方式回傳給 client 端。
    import java.io.*;
    
    public class TimeArray {
      public TimeBean[] getTimeArray() throws Exception {
        TimeBean[] res = new TimeBean[2];
        res[0] = new TimeBean();
    
        // 只是為了確保兩個 TimeBean 的物件差一秒
        Thread.sleep(1000);
    
        res[1] = new TimeBean();
        return res;
      }
    }
    
  3. 定義 wsdd 檔
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
      <service name="BeanArray" provider="java:RPC" style="wrapped" use="literal">
        <parameter name="className" value="TimeArray" />
        <parameter name="allowedMethods" value="*" />
        <beanMapping qname="myNS:TimeBean" xmlns:myNS="http://DefaultNamespace"
                     languageSpecificType="java:TimeBean" />
      </service>
    </deployment>
    
  4. 將 service 佈置好了以後,就可以開始開發客戶端程式。
  5. 開發 AXIS 客戶端程式
    1. 過程跟之前所說的一樣,先利用 WSDL2Java 以及 service 的 WSDL 檔來產生 proxy 類別,然後將這些類別檔 import 到客戶端程式。
    2. BeanArrayClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      import _aa._bb._cc._dd.axis.services.BeanArray.*;
      import DefaultNamespace.*;
      
      public class BeanArrayClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          String URL = "http://aa.bb.cc.dd:8080/axis/services/BeanArray";
      
          TimeArrayService service = new TimeArrayServiceLocator();
          TimeArray call = service.getBeanArray();
          
          TimeBean[] result = call.getTimeArray();
      
          System.out.print(result[0].getHour() + ":");
          System.out.print(result[0].getMinute() + ":");
          System.out.println(result[0].getSecond());
          System.out.print(result[1].getHour() + ":");
          System.out.print(result[1].getMinute() + ":");
          System.out.println(result[1].getSecond());
        }
      }
      
  6. 開發 .NET 客戶端程式
    1. 過程跟之前所說的 一樣,先利用 wsdl.exe 以及 service 的 WSDL 檔來產生 proxy 類別,然後 在依照 proxy 類別產生 .dll 檔。
    2. 開發 .NET 客戶端程式
      <%@Page Language="VB" %>
      <script runat="server">
      Sub Page_Load(o As Object, e As EventArgs)
        ' 這個是 Axis 的 service
        Const URL as String ="http://hostname:8080/axis/services/BeanArray"
      
        Dim service as New TimeArrayService()
      
        ' 設定服務的 URL
        service.Url = URL
      
        ' 呼叫服務的 getDate()
        Dim Answer() As TimeBean = service.getTimeArray()
      
        Label1.Text = Answer(0).hour
        Label2.Text = Answer(0).minute
        Label3.Text = Answer(0).second
      
        Label4.Text = Answer(1).hour
        Label5.Text = Answer(1).minute
        Label6.Text = Answer(1).second
      End Sub
      </script>
      <HTML>
      <BODY>
      <h3 align="center">ASP.NET 2.0 SOAP Client</h3>
      <h4 align="center">Process JavaBean Arrays</h4>
      <hr/>
      <font color="gray" size="5">
      時/分/秒: <asp:Label id="Label1" runat="server" />/<asp:Label id="Label2" runat="server" />/<asp:Label id="Label3" runat="server" /><br/>
      時/分/秒: <asp:Label id="Label4" runat="server" />/<asp:Label id="Label5" runat="server"/>/<asp:Label id="Label6" runat="server" /><br/>
      </font>
      <p/>
      </body>
      </html>
      


Written by: 國立中興大學資管系呂瑞麟






呼叫遠端 .NET 的服務

呼叫遠端 .NET 的服務

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟

請勿轉貼


在之前的範例中,我們以 Axis 開發 service 並依據該 service 開發 client。在這一篇文章中,我們試著以 Axis 來開發遠端 .NET 服務的 client 程式。我們在另一篇文章會探討如何以 .NET 來開發遠端 Axis 服務的 client 程式。 開發 client 程式的方式跟以前的說明類似,至於可能遇見的狀況,我們 以範例說明。目前提供各式各樣的 web services 的入口網頁有 XMethods (似乎已經結束營業了) 、Lightweight Service Registry、以及 WebserviceX.NET
  1. 範例一:Whois Service 這是一個由 .NET 開發的服務,其提供一個 operations: getWhoIS()。getWhoIS() 需要一個參數,就是查詢的 domain name。

開發回傳字串陣列、物件、以及物件陣列的服務

開發回傳字串陣列、物件、以及物件陣列的服務

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟
請勿轉貼


在之前的範例中,我們可以觀察到只要交換資料的資料型態是基本資料型態 (如 short, int, float, double 等)以及字串,client 的程式都非常 簡單。可是如果是陣列,或者物件(尤其是日期的物件),程式的開發有沒有 困難?
  1. 開發一個會回傳字串陣列的服務,該服務提供一 String[] getTimeArray(String[] input) operation,而該 operation 接受一個字串陣列的參數,並將回傳一 字串陣列。
    1. 服務端:StringArray.java
      import java.util.*;
      
      public class StringArray {
        private String[] date;
      
        public StringArray() {
          Calendar now = Calendar.getInstance();
          date = new String[6];
          date[0] = String.valueOf(now.get(Calendar.YEAR));
          date[1] = String.valueOf(now.get(Calendar.MONTH) + 1);
          date[2] = String.valueOf(now.get(Calendar.DATE));
          date[3] = String.valueOf(now.get(Calendar.HOUR));
          date[4] = String.valueOf(now.get(Calendar.MINUTE));
          date[5] = String.valueOf(now.get(Calendar.SECOND));
        }
      
        public String[] getTimeArray(String[] input) {
          for(int i=0; i<date.length; i++) {
            date[i] = date[i] + " " + input[i];
          }
      
          return date;
        }
      }
      
    2. 產生一個註冊用的 WSDD 檔並註冊,跟之前的類似,故省略。
    3. 利用 WSDL2Java 產生必要的 proxy 類別,跟之前的類似,故省略。
    4. 產生客戶端程式:ArrayClient.java
      import java.rmi.*;
      import javax.xml.rpc.*;
      
      import localhost.axis.services.StrArrayService.*;
      
      public class ArrayClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          StringArrayService service = new StringArrayServiceLocator();
          StringArray call = service.getStrArrayService();
          
          String[] input = {"年","月","日","時","分","秒"};
          String[] result = call.getTimeArray(input);
      
          for(int i=0; i<result.length; i++)
            System.out.print(result[i] + " ");
          System.out.println();
        }
      }
      


  2. 開發一個回傳物件以及物件陣列的服務,該服務提供 Worker getWorker()、Worker[] getArray()、以及 void setWorker(Worker w) 的 operations.
    1. 服務端:WorkerService.java
      import java.util.*;
      
      public class WorkerService {
        private Worker worker;
      
        public WorkerService() {
          worker = new Worker();
        }
      
        public Worker getWorker() {
          return worker;
        }
      
        public void setWorker(Worker w) {
          worker = w;
        }
      
        public Worker[] getArray() {
          Calendar c = Calendar.getInstance();
          c.set(1978, Calendar.MAY, 17);
          Worker w2 = new Worker("Dave Mentis", 60, c);
          Worker[] w = {new Worker(), w2};
      
          return w;
        }
      }
      
    2. 自訂的類別: Worker.java(為了避免麻煩,我們將 Worker.java 依據 JavaBean 的規範設計,細節討論,請參考 AXIS 與 .NET 互通性)。另外,我們在 Worker.java 也增加了一個日期的資料成員,根據經驗,請不要使用 java.util.Date,而應該使用 java.util.Calendar 或者 java.util.GregorianCalendar。請注意,所謂 JavaBean 的設計規範是說:當開發人員設計一個類別時,請務必設計該類別的預設建構元、 包含所有資料成員當作參數的建構元、以及每一個資料成員都有一對 get/set 方法。 以 Worker.java 為例,由於它有三個資料成員,所以我們需要設計出一個預設建構元 public Worker()、包含所有資料成員當作參數的建構元 public Worker(String n, int h, Calendar c)、資料成員 name 有一對 setName()/getName() 方法、資料成員 hours 有一對 setHours()/getHours() 方法、以及資料成員 birthday 有一對 setBirthday()/getBirthday() 方法。
      import java.util.*;
      
      public class Worker {
        private String name;
        private int hours;
        private Calendar birthday;
      
        public Worker() {
          name = "Eric Lu";
          hours = 40;
          birthday = Calendar.getInstance();
          birthday.set(1988, Calendar.JULY, 15);
        }
      
        public Worker(String n, int h, Calendar c) {
          setName(n);
          setHours(h);
          setBirthday(c);
        }
      
        public String getName() {
          return name;
        }
      
        public int getHours() {
          return hours;
        }
      
        public Calendar getBirthday() {
          return birthday;
        }
      
        public void setName(String n) {
          name = n;
        }
      
        public void setHours(int h) {
          hours = h;
        }
      
        public void setBirthday(Calendar d) {
          birthday = d;
        }
      }
      
    3. WSDD 檔:由於在 WorkerService 中,使用了自訂的類別 Worker,因此在 WSDD 的定義上,需要增加 beanMapping 的定義,該定義會使得 WSDL2Java 產生 恰當的 Worker 檔在 client 端。另外,由於 WSDL 檔內的 targetNamespace 是預設的 http://DefaultNamespace,最好在定義 beanMapping 的時候, 也設定適當的命名空間。
      <deployment xmlns="http://xml.apache.org/axis/wsdd/"
          xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <service name="ObjectService" provider="java:RPC" style="wrapped" use="literal">
          <parameter name="className" value="WorkerService" />
          <parameter name="allowedMethods" value="*" />
      
          <beanMapping qname="tns:Worker" xmlns:tns="http://DefaultNamespace"
                       languageSpecificType="java:Worker" />
      
        </service>
      </deployment>
      
    4. 客戶端程式 WorkerClient.java:過程跟之前所說的一樣,先利用 WSDL2Java 以及 service 的 WSDL 檔來產生 proxy 類別,然後把這些類別 import 到客戶端程式 即可。跟之前的範例有一點不太一樣的地方是,這次我們定義了 Worker, 由於我們在 WSDD 內定義它的 namespace 是 DefaultNamespace,因此 WSDL2Java 會產生一個 Worker.java 的程式在 DefaultNamespace 的子目錄內。
      import java.rmi.*;
      import javax.xml.rpc.*;
      
      import localhost.axis.services.ObjectService.*;
      import DefaultNamespace.*;
      import java.util.*;
      
      public class WorkerClient {
        public static void main(String[] args) throws RemoteException, 
                                                      ServiceException {
          WorkerServiceService service = new WorkerServiceServiceLocator();
          WorkerService call = service.getObjectService();
      
          // 先利用 service 設定一個 Worker 物件
          Calendar c = Calendar.getInstance();
          c.set(1970, Calendar.JUNE, 22);
          Worker s = new Worker(c, 55, "Mary Ann"); 
          call.setWorker(s);
          
          // 再取回剛剛的 Worker 物件(???) 
          Worker w = call.getWorker();
          System.out.print(w.getName() + "\t");
          System.out.print(w.getHours() + "\t");
          c = w.getBirthday();
          System.out.println(c.get(Calendar.YEAR) + "/" +
                             (c.get(Calendar.MONTH) + 1) + "/" +
                             c.get(Calendar.DATE) + "\n");
      
          // 取得一個預設的 Worker[] 陣列
          Worker[] ww = call.getArray();
          for(int i=0; i < ww.length; i++) {
            System.out.print(ww[i].getName() + "\t");
            System.out.print(ww[i].getHours() + "\t");
            c = ww[i].getBirthday();
            System.out.println(c.get(Calendar.YEAR) + "/" +
                               (c.get(Calendar.MONTH) + 1) + "/" +
                               c.get(Calendar.DATE));
          }
        }
      }
      
      這個客戶端程式首先利用 setWorker() 設定一個 Worker 物件到 service 端, 然後,試圖從 service 端將剛剛設定好的 Worker 物件取回;最後,再從 service 端取得一個 Worker 物件陣列。 這個程式跟以往的程式大同小異,但是有兩個地方要特別注意,這兩個地方 以棗紅色字體顯示:(1) 雖然在 service 端,Worker 建構元的 參數順序為 name, hours, birthday,但是在 client 端,Worker 建構元的 參數順序卻是 birthday, hours, name。這是因為在 ObjectService 的 WSDL 中(如下圖所示),Worker 的三個 element 依序是 birthday, hours, name。 因此,客戶端的程式必須以 WSDL 的方式撰寫;這其實很合理,因為大部分時間, 程式設計人員是無法知道遠端服務是如何開發的。
      (2) 雖然在程式在第二段中試圖利用 getWorker() 的方式想把剛剛第一段 設定的 Worker 物件取回(也就是 Many Ann),但是執行程式之後,其結果 如以下畫面:
      這個結果清楚顯示,每一次 service 被呼叫,該 service 物件(也就是 WorkerService)都會被重新產生一次,因此取回的 Worker 物件還是預設 建構元所產生的物件。雖然這個特性會使得部分應用變得不方便,但是如果 service 端的服務都配合著資料庫的使用,這個不方便應可以降到最低。
      依據 Axis 的文件,上述情形(每次呼叫 service,該 service 物件都會 重新被產生)稱之為 "request",這是 Axis 的預設行為;Axis 還提供其它 兩種行為模式:一個是 "session"(每一個 session 只有一個 service 物件), 另一個是 "application"(這個 client application 都只使用一個 service 物件)。 由於有可能會有多個程式同時呼叫該 service 物件的方法,使用 "session" 和 "application" 的開發人員必須自行控制可能發生的情形。
      若需要更改 service 物件的產生方式會以 application 為 scope,我們需要 更改 wsdd 的內容,更新後的 WSDD 檔的內容如下:
      <deployment xmlns="http://xml.apache.org/axis/wsdd/"
          xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
        <service name="ObjectService" provider="java:RPC" style="wrapped" use="literal">
      
          <parameter name="scope" value="Application" />
      
          <parameter name="className" value="WorkerService" />
          <parameter name="allowedMethods" value="*" />
          <beanMapping qname="tns:Worker" xmlns:tns="http://DefaultNamespace"
                       languageSpecificType="java:Worker" />
        </service>
      </deployment>
      
      我們建議在註冊(deploy)這個服務之前,請記得先將之前的舊服務先 移除(undeploy)掉;在不更改其他的程式之下,重新執行 WorkerClient, 你將會看到如下的畫面:
      
      
      練習題: 如果將 WorkerService 的 getArray() 中的 Worker[] w = {new Worker(), w2}; 改成 Worker[] w = {worker, w2};,請問在不同的 scope 設定下,輸出的結果 有什麼變化?為什麼?






Written by: 國立中興大學資管系呂瑞麟



使用 Axis TCP Monitor 來監測 SOAP 訊息

使用 Axis TCP Monitor 來監測 SOAP 訊息

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟
請勿轉貼


SOAP 訊息(SOAP messages)是否已經被傳送出去,或者 SOAP 訊息是否已經被接受, 或者被傳送的 SOAP 訊息的內容以及格式為何,這個時候我們就可以利用 Axis TCP Monitor 來監測 SOAP 訊息。使用 TCP Monitor 的方式很簡單,你只要 在 Axis 伺服器端,產生如下的檔案,並將其命名為 tcpmon.bat
@echo off
set TEMP=%CLASSPATH%
set CLASSPATH=%CLASSPATH%;d:\tomcat\webapps\axis\WEB-INF\lib\axis.jar;d:\tomcat\webapps\axis\WEB-INF\lib\axis-ant.jar;d:\tomcat\webapps\axis\WEB-INF\lib\commons-discovery-0.2.jar;d:\tomcat\webapps\axis\WEB-INF\lib\commons-logging-1.0.4.jar;d:\tomcat\webapps\axis\WEB-INF\lib\jaxrpc.jar;d:\tomcat\webapps\axis\WEB-INF\lib\log4j-1.2.8.jar;d:\tomcat\webapps\axis\WEB-INF\lib\saaj.jar;d:\tomcat\webapps\axis\WEB-INF\lib\wsdl4j-1.5.1.jar
java org.apache.axis.utils.tcpmon
set CLASSPATH=%TEMP%
完成後,請執行 tcpmon.bat 即可,執行後,你就可以看到如下的畫面:

在進一步說明 TCPMonitor 的使用方式之前,我們先說明 TCPMonitor 的概念: 在沒有 TCPMonitor 之前,client 會直接呼叫 service;如果 service 的名稱 為 http://mytest.com:8080/testService,那麼 client 程式必須將該服務 名稱設定在程式內。如果想利用 TCPMonitor 來監看送出以及回傳的 SOAP 訊息, client 必須先呼叫 TCPMonitor,然後由 TCPMonitor 呼叫 service;而 service 的回傳資料也會先傳給 TCPMonitor,然後再由 TCPMonitor 回傳給 client。 為了能輕易的完成上列的工作,在畫面中,我們要特別注意兩個比較重要的設定: Listen Port# 以及 Act as a Listener 的 Target Hostname 和 Target Port#。 以之前的範例來說,我們必須在 Listen Port# 的欄位任意輸入一個大於 1024 的整數, 例如 1234,而且在 Target Hostname 要設定為 mytest.com,Target Port# 要設定為 8080;如此一來,client 程式必須將服務名稱改成 http://127.0.0.1:1234/testService,然後當 TCPMonitor 接收到 client 傳來的 SOAP 訊息時,它會將該 SOAP 訊息 轉傳給 http://mytest.com:8080/testService。
假設你已經 deploy 了剛剛的 TimeService,請在 Listen Port# 輸入 1234。 由於 TimeService 服務在 localhost:8080,因此 Target Hostname 和 Target Port# 不需要修改;然後請點選 "Add" 鈕,這時候,程式會新增一個 tab, 請選擇這個 tab 開始監測 SOAP 訊息。
最後,請在瀏覽器內輸入 URL:http://localhost:1234/axis/services/TimeService?method=getTime 來觀察 TCP Monitor, 你應該可以看到類似以下的畫面:


從上述的範例,我們可以知道,雖然我們送出去的要求是到 port 1234,但是 TCP Monitor 會把它轉向 port 8080,然後 web service 執行的結果,會由 port 8080 傳回來,這時候 TCP Monitor 又會把這個訊息轉給 port 1234。 這樣子一來,不但程式可以得到服務,我們又可以經由 TCP Monitor 知道 SOAP 的訊息。

Written by: 國立中興大學資管系呂瑞麟