2010年8月17日 星期二

[Struts2]-Hibernate Integration

在談論Hibernate前我們要先了解JPA(Java Persistence API)

Java的物件導向模型與關聯資料庫模型之間有相當程度的不匹配,而一些物件與資料庫資料同步、更新,也就是所謂的Java永續儲存(Persistence)問題,在過去,Object/Relational Mapping(ORM)有 JBoss Hibernate、Oracle TopLink等解決方案,而JPA為吸收這些方案的經驗,所製訂出的Java永續儲存標準。

使用JPA,底層可以使用不同廠商的ORM實作,而介面則是JPA的標準,若您使用NetBeans+Glassfish,則預設的底層實作為TopLink,JBoss的工具其底層實作則為Hibernate。

要在Struts2使用Hibernate Plugin,必須有下列libs:
antlr-2.7.6.jar
commons-collections-3.1.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-lang-2.3.jar
commons-logging-1.1.jar
dom4j-1.6.1.jar
ejb3-persistence.jar
freemarker-2.3.13.jar
hibernate3.jar
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate-validator.jar
hsqldb.jar
javassist-3.9.0.GA.jar
jta-1.1.jar
junit-3.8.1.jar
log4j-1.2.15.jar
ognl-2.6.11.jar
slf4j-api-1.5.8.jar
slf4j-log4j12-1.5.8.jar
struts2-convention-plugin-2.1.6.jar
struts2-core-2.1.6.jar
struts2-fullhibernatecore-plugin-1.4-GA.jar
xwork-2.1.2.jar
另外在struts2.xml的package中,要繼承hibernate-default如下:
<struts>
<package name="user" extends="hibernate-default">
<action name="saveOrUpdateUser" method="saveOrUpdate" class="web.UserAction">
<result name="success" type="redirect">listUser</result>
</action>
<action name="listUser" method="list" class="web.UserAction">
<result name="success">/register.jsp</result>
</action>
<action name="editUser" method="edit" class="web.UserAction">
<result name="success">/register.jsp</result>
</action>
<action name="deleteUser" method="delete" class="web.UserAction">
<result name="success" type="redirect">listUser</result>
</action>
</package>
</struts>
上面的struts.xml也透露出了struts2的另一個應用,DispatchAction functionality
action可以指定到class內的method,而不一定是預設的execute method。

接下來我們設定hibernate.cfg.xml來做O/R Mapping,
hibernate.cfg.xml主要是設定資料庫連線資訊,如url、driver、帳密等,
並註明這個連線資訊mapping到那些object。
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
        <property name="hibernate.connection.username">root</property>
        <property name="connection.password">syscom</property>
        <property name="connection.pool_size">1</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">create</property>
        <mapping class="domain.User"/>
    </session-factory>
</hibernate-configuration>
這裡的mapping是直接mapping到一個User類別。

直接mapping到物件的話要使用annotation。使用Hibernate Annotations,
不需要使用HBM映射檔案,而是直接在POJO上使用Annotation設定對映關係,
如下的User.java:
package domain;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity //說明這是一個映射物件
@Table(name = "USER") //mapping到那一個table
public class User implements Serializable {

  private Long id;
  private String name;
  private String gender;
  private String country;
  private String aboutYou;
  private Boolean mailingList;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "USER_ID") //非必要,在欄位名稱與屬性名稱不同時使用
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @Column(name = "USER_NAME")
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Column(name = "USER_GENDER")
  public String getGender() {
    return gender;
  }

  public void setGender(String gender) {
    this.gender = gender;
  }

  @Column(name = "USER_COUNTRY")
  public String getCountry() {
    return country;
  }

  public void setCountry(String country) {
    this.country = country;
  }

  @Column(name = "USER_ABOUT_YOU")
  public String getAboutYou() {
    return aboutYou;
  }

  public void setAboutYou(String aboutYou) {
    this.aboutYou = aboutYou;
  }

  @Column(name = "USER_MAILING_LIST")
  public Boolean getMailingList() {
    return mailingList;
  }

  public void setMailingList(Boolean mailingList) {
    this.mailingList = mailingList;
  }
}
所有的Annotation都是在javax.persistence這個package之下,這是標準的 JPA Annotations,
Hibernate實作了這些Annotations,其中id是個特殊的屬性,Hibernate會使用它來作為主鍵識別,你可以定義主鍵產生的方式,這邊設定為自動產生主鍵,依賴於資料庫自動增生主鍵的機制,也就是相當於在XML中設定native。

使用Hibernate另一個酷炫的好處是我們不用事先建立資料表,只要hibernate.cfg.xml設定檔
內的hbm2ddl.autocreate參數值設為create,基於JPA的概念,
Hibernate底層會幫我們完成這一切。自動建立的資料表如下:
這裡要特別注意的是,如果hbm2ddl.autocreate參數值設為create,則每次系統重啟時資料表
會被清空,如果要保留原本資料表內的資料,參數值則必須設為update。

接著我們回頭來看前面的struts.xml,
可以看出這是一個新增、刪除、修改使用者資料的範例,
接下來我們將這個範例檔補齊。
首先編寫前端的render頁面register.jsp:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Registration Page</title>
<s:head />
<style type="text/css">
@import url(style.css);
</style>
</head>
<body>
<s:form action="saveOrUpdateUser">
<s:push value="user">
<s:hidden name="id" />
<s:textfield name="name" label="User Name" />
<s:radio name="gender" label="Gender" list="{'Male','Female'}" />
<s:select name="country" list="{'India','USA','UK'}" headerKey=""
headerValue="Select" label="Select a country" />
<s:textarea name="aboutYou" label="About You" />
<s:checkbox name="mailingList"
label="Would you like to join our mailing list?" />
<s:submit />
</s:push>
</s:form>

<s:if test="userList.size() > 0">
<div class="content">
<table class="userTable" cellpadding="5px">
<tr class="even">
<th>Name</th>
<th>Gender</th>
<th>Country</th>
<th>About You</th>
<th>Mailing List</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<s:iterator value="userList" status="userStatus">
<tr
class="<s:if test="#userStatus.odd == true ">odd</s:if><s:else>even</s:else>">
<td><s:property value="name" /></td>
<td><s:property value="gender" /></td>
<td><s:property value="country" /></td>
<td><s:property value="aboutYou" /></td>
<td><s:property value="mailingList" /></td>
<td><s:url id="editURL" action="editUser">
<s:param name="id" value="%{id}"></s:param>
</s:url> <s:a href="%{editURL}">Edit</s:a></td>
<td><s:url id="deleteURL" action="deleteUser">
<s:param name="id" value="%{id}"></s:param>
</s:url> <s:a href="%{deleteURL}">Delete</s:a></td>
</tr>
</s:iterator>
</table>
</div>
</s:if>
</body>
</html>
接下來是後端的action物件,UserAction.java:
package web;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import dao.UserDAO;
import dao.UserDAOImpl;
import domain.User;

public class UserAction extends ActionSupport implements ModelDriven {

  private User user = new User();
  private List userList = new ArrayList();
  private UserDAO userDAO = new UserDAOImpl();

  @Override
  public User getModel() {
    return user;
  }

  public String saveOrUpdate() {
    userDAO.saveOrUpdateUser(user);
    return SUCCESS;
  }

  public String list() {
    userList = userDAO.listUser();
    return SUCCESS;
  }

  public String delete() {
    HttpServletRequest request =
      (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
    userDAO.deleteUser(Long.parseLong(request.getParameter("id")));
    return SUCCESS;
  }

  public String edit() {
    HttpServletRequest request =
      (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
    user = userDAO.listUserById(Long.parseLong(request.getParameter("id")));
    return SUCCESS;
  }

  public User getUser() {
    return user;
  }

  public void setUser(User user) {
    this.user = user;
  }

  public List getUserList() {
    return userList;
  }

  public void setUserList(List userList) {
    this.userList = userList;
  }
}
在action的method內是用一個userDao類別來封裝物件與db之間的transaction。
同時在這個action我們可以看到struts2的另一個特殊用法,ModelDriven Action
繼承ModelDriven interface後,action invoke過程就會涉入modelDriven interceptor
此時framework就可以自動傳送前端表單資料到所覆寫的getModel() method所回傳的物件。
UserDao.java
package dao;

import java.util.List;
import domain.User;

public interface UserDAO {
  public void saveOrUpdateUser(User user);
  public List listUser();
  public User listUserById(Long userId);
  public void deleteUser(Long userId);
}
UserDaoImpl.java
package dao;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.googlecode.s2hibernate.struts2.plugin.annotations.SessionTarget;
import com.googlecode.s2hibernate.struts2.plugin.annotations.TransactionTarget;
import domain.User;

public class UserDAOImpl implements UserDAO {

  @SessionTarget
  Session session;
  @TransactionTarget
  Transaction transaction;

  @Override
  public void saveOrUpdateUser(User user) {
    try {
      session.saveOrUpdate(user);
    } catch (Exception e) {
      transaction.rollback();
      e.printStackTrace();
    }
  }

  @Override
  public void deleteUser(Long userId) {
    try {
      User user = (User) session.get(User.class, userId);
      session.delete(user);
    } catch (Exception e) {
      transaction.rollback();
      e.printStackTrace();
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public List listUser() {
    List courses = null;
    try {
      courses = session.createQuery("from User").list();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return courses;
  }

  @Override
  public User listUserById(Long userId) {
    User user = null;
    try {
      user = (User) session.get(User.class, userId);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return user;
  }
}

UserDAOImpl是本篇另一個重點,它說明如何利用annotation來取得物件與db之間的
操控權session、transaction。
利用@SessionTarget、@TransactionTarget這兩個annotation可以從hibernate.cfg.xml設定檔中,取得相對應的session、transaction控制物件。

除了利用annotation來取得session外,我們也能用傳統的方式,編寫一個session factory,
這裡要注意的是,POJO類別(User.java)內如果是使用annotation來設定對映關係的話,
就必須要利用AnnotationConfiguration類別來取得SessionFactory。

下面我們編寫一個HibernateUtil class來專門取得SessionFactory。
package dao;

import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.SessionFactory;

public class HibernateUtil {
  private static final SessionFactory sessionFactory;

  static {
    try {
      sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    } catch (Throwable ex) {
      System.err.println("Initial SessionFactory creation failed." + ex);
      throw new ExceptionInInitializerError(ex);
    }
  }

  public static SessionFactory getSessionFactory() {
    return sessionFactory;
  }
}
接著我們就能從SessionFactory內取得session及transaction操控物件。

下面就是整個範例的操作畫面:

2010年8月16日 星期一

[Struts2]-OGNL存取ActionContext內非Value Stack內之物件

參考前一篇[Struts2]-實作Interceptor
result頁面可以利用OGNL語法從ActionContext的ValueStack中取得欄位物件
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
<title>Hello World</title>
</head>
<body>
<h2><s:property value="message"/></h2>
</body>
</html>
此時property tag中的value屬性就是利用OGNL語法從hello action中取得message field。
因為當action處理完後欄位資料後會將欄位物件置於ActionContext的ValueStack中,
此時的前端頁面就是利用OGNL從ValueStack中取出相對應的欄位值。

在ActionContext中,除了ValueStack內的物件外還有其它的scope物件可以存取,
如下圖:


 名称作用例子
parameters包含當前HTTP請求參數的Map#parameters.id[0]作用相當於request.getParameter("id")
request包含當前HttpServletRequest的屬性(attribute)的Map#request.userName相當於request.getAttribute("object name")
session包含當前HttpSession的屬性(attribute)的Map#session.userName相當於session.getAttribute("object name")
application包含當前應用的ServletContext的屬性(attribute)的Map#application.userName相當於application.getAttribute("object name")
attr用於按request > session > application順序訪問其屬性(attribute)#attr.userName相當於按顺序在以上三個範圍(scope)内讀取object name屬性,直到找到為止

接下來我們實際編寫一個action(OgnlAction.java)來存取這些scope物件。
package example;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsStatics;

public class OgnlAction extends ActionSupport {

  private ActionContext actionContext;
  private Map application;
  private Map session;
  private Map parameters;
  private HttpServletRequest request;
  private HttpServletResponse response;

  @Override
  public String execute() {
    actionContext = ActionContext.getContext();
    application = actionContext.getApplication();
    session = actionContext.getSession();
    parameters = actionContext.getParameters();

    request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
    //private HttpServletRequest request = ServletActionContext.getRequest();
    response = (HttpServletResponse) actionContext.get(StrutsStatics.HTTP_RESPONSE);

    application.put("message", "message from application");
    session.put("message", "message from session");
    parameters.put("message", "message from parameters");
    request.setAttribute("message", "message from request");
    return SUCCESS;
  }
}

我們可以利用ActionContext類別來取得這些scope物件,
另外也能使用ServletActionContext這個輔助類別來取得
HttpServletRequest、HttpServletResponse 。
這裡要注意的是,ActionContext物件必須要在execute method內取得,
如果在method外初始的話,此時內部的application等scope物件都還是null。

接下來編寫一個Ognl.jsp來當做前端頁面。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Struts OGNL Demo</title>
</head>
<body>
<h3>訪問ActionContext物件</h3>
<p>parameters: <s:property value="#parameters.message" /></p>
<p>request.message: <s:property value="#request.message" /></p>
<p>session.message: <s:property value="#session.message" /></p>
<p>application.message: <s:property value="#application.message" /></p>
<p>attr.message: <s:property value="#attr.message" /></p>
<hr/>
</body>
</html>
這裡可以發現,OGNL預設就是取得ValueStack內的物件,如果要取得其它的scope物件,
前面則要加上#符號。

最後是struts.xml的設定
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="Ognl" class="example.OgnlAction">
<result name="success">/example/Ognl.jsp</result>
</action>
</package>
</struts>
輸入連結後/example/Ognl.actiont可以看到下面的結果:
除了parameters外其餘的結果都如我們預料,
筆者猜測也許parameters是取得當前http request的參值,
無法事後更動。

當我們直接在請求頁面的連結後加入參數時,就正常了。

2010年8月15日 星期日

[Struts2]-實作Interceptor

首先先來了解struts2 framework的運作機制
要部署struts2 framework我們必須在web.xml中加入這段
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
當 FilterDispatcher 根據 action name 所對應到 struts.xml 中的 Action class 之後,
Struts2 核心會先呼叫 Interceptor Stack 中的所有 interceptors。
接著再呼叫 Action class 中的 execute() method,完成後再呼叫 Result 幫助我們排版,
這過程中都會使用到 OGNL 到 ValueStack 中幫助我們取得必要的資料。

Interceptor Stack
這裡我們先將重點擺在Interceptor。Interceptor Stack 是在整個流程中第一個被呼叫的,
也是最後一個被呼叫的。Interceptor Stack 中包含一集合的 interceptors,
而 interceptors 的排列方式是以 stack 的方式排列。

下面我們先寫一個簡單無牽扯自訂interceptor的範例。
首先我們先新增一個Login.jsp頁面。
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>login</title>
</head>
<body>
<s:form action="/example/Hello" method="post">
<s:textfield name="user_name" label="User Name:"/>
<s:submit/>
</s:form>
</body>
</html>
這個頁面會呼叫hello action,所以我們接下來編寫一個HelloWorld.java來處理這個呼叫。
package example;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.StrutsStatics;

public class HelloWorld extends ActionSupport {
  private String user_name;
  private String message;

  @Override
  public String execute() throws Exception {
    message = "Hello:" + user_name;
    return SUCCESS;
  }

  public String getUser_name() {
    return user_name;
  }

  public void setUser_name(String user_name) {
    this.user_name = user_name;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
}
hello action被呼叫時會自動執行excute這個method,當處理完後會藉由return "String"告訴
framework要將結果導向那個result頁面。

接下來我們再編寫一個HellowWorld.jsp來作為輸出頁面。
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
<title>Hello World</title>
</head>
<body>
<h2><s:property value="message"/></h2>
</body>
</html>

至於如何將Login.jsp與HelloWorld.java做連結以及利用result標韱來控制輸出頁面呢?
則是在struts.xml中設定。
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="Hello" class="example.HelloWorld">
<result name="success">/example/HelloWorld.jsp</result>
<result name="login">/example/LOGIN.jsp</result>
</action>
</package>
</struts>
上述例子表示,
當action return "success"時會導向HelloWorld.jsp,
當return "login"時會導向Login.jsp。
這裡有兩個需要注意的地方:
1.在jsp頁面中呼叫action時,必須加入其package的namespace為prefix,
    如action="/example/Hello";
2.result的name屬性值是有區分大小寫的。


下面為這個範例的執行結果:
輸入名稱後
會出現歡迎頁面

接下來進入這篇的重點,我們要在這個流程中加入自訂的interceptor,
編寫一個AuthorizationInterceptor.java,
自訂的interceptor須繼承AbstractInterceptor介面。
package example;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class AuthorizationInterceptor extends AbstractInterceptor {
  @Override
  public String intercept(ActionInvocation ai) throws Exception {
    return ai.invoke();
  }
}
當interceptor發生作用時會呼叫intercept這個method。
而利用ActionInvocation的getInvocationContext()method,
我們可以得到儲存全文流程資料的ActionContext物件,這時就可以自由發揮了。

回頭看第一張framework流程圖,interceptor可以invoke原本request呼叫的action,
也可以直接return result頁面,
在這個例子中我們在interceptor中不做任何事,直接invoke action。

要讓自訂的interceptor發揮作用還需要在struts.xml中加以指明。
<struts>
<package name="example" namespace="/example" extends="struts-default">
<interceptors>
<interceptor name ="auth" class ="example.AuthorizationInterceptor"/>
</interceptors>
<action name="Hello" class="example.HelloWorld">
<interceptor-ref name ="auth"/>
<result name="success">/example/HelloWorld.jsp</result>
<result name="login">/example/LOGIN.jsp</result>
</action>
</package>
</struts>
表示呼叫hello action時必須先經過auth這個interceptor。

我們再一次的執行這個範例,確發現了意外的結果。
Login.jsp所輸入的使用者名稱無法帶到所呼叫的hello action,導致HelloWorld.jsp
頁面輸出時user_name欄位值為null。
這個原因是因為當我們自訂的interceptor呼叫invoke method時,整個flow會回到
framework的開頭階段,此時hello action並非由Login.jsp直接呼叫,
導致Login.jsp中的textfield欄位自動調用HellowWorld.java中的setUser_name()的機制失效。

此時hello action(HelloWorld.java)就必須自行由request中取出Login.jsp傳來的parameter。
將HellowWorld.java的execute method內容改寫如下:
public String execute() throws Exception {
ActionContext actionContext = ActionContext.getContext();
HttpServletRequest request= 
(HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
message = "Hello:" + request.getParameter("user_name");
return SUCCESS;
}
接著再一次執行修改過後的範例,就又可以看到正確的輸出結果了。

2010年8月5日 星期四

[AJAX]-動態清單

這裡要記錄如何用Ajax來達到動態清單的功能
Ajax 是 Asynchronous JavaScript  and XML 的簡稱,這指出了 Ajax 的核心觀念
(Asynchronous)與所使用到的主要兩個技術(JavaScript、XML)。
要達到 Asynchronous 利用的就是 JavaScript 中的XMLHttpRequest 物件。

XMLHttpRequest:
在 JavaScript 中利用new XMLHttpRequest()語法建立。
包含下列幾個標準屬性-

onreadystatechange 
參考至callback函式,readyState每次改變時,都會呼叫onreadystatechange所參考的函式。

readyState 
會有0到4的數值,分別表示不同的請求狀態:
 0 = 未初始化的連線(uninitialized),還沒呼叫open()
 1 = 載入中(loading),呼叫open(),還沒呼叫send()
 2 = 已載入(loaded),呼叫send(),請求header/status準備好
 3 = 互動中(interactive),正在與伺服器互動中
 4 = 請求完成(completed),完成請求

responseText 
伺服器傳來的請求回應文字,會設定給這個屬性。

responseXML
伺服器傳來的請求回應如果是XML,會成為DOM設定給這個屬性。

status 
伺服器回應的狀態碼,例如200是OK,404為Not Found…

statusText 
伺服器回應的狀態文字。
接下來我們實際寫個web頁面來發送XMLHttpRequest
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>動態載入清單</title>
<script type="text/javascript" src=dynamicList.js></script>
</head>
<body>
語言…
<br/>
<select id="langs" onchange="refreshList();">
<option value="func">功能說明</option>
<option value="java">Java</option>
<option value="c#">C#</option>
</select>
<br/><br/>
相關技術…
<br/>
<select id="techs" size="6" style="width:300px;"/>
</body>
</html>
主要功能是選擇 id 為 langs 的 select html 元件時,會利用 Ajax 去後端 server 取得
相關資料,非同步的更動 id 為 techs 的 select html元件。
另外主要功能的JavaScript是利用
<script type="text/javascript" src=dynamicList.js></script>來引入。
var xmlHttp;

window.onload = refreshList;

function createXMLHttpRequest() {
  if(window.XMLHttpRequest) {
    xmlHttp = new XMLHttpRequest();
  }
  else if(window.ActiveXObject) {
    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
  }
}

function prepareXML() {
  var xml = "<langs>";
  var lang = document.getElementById("langs").value;
  xml = xml + "<lang>" + lang + "<\/lang>";
  xml = xml + "<\/langs>";
  return xml;
}

function refreshList() {
  var xml = prepareXML();
  var url = "refreshservlet";

  createXMLHttpRequest();
  xmlHttp.onreadystatechange = handleStateChange;
  xmlHttp.open("POST", url);
  xmlHttp.setRequestHeader("Content-Type", "text/xml");
  xmlHttp.send(xml);  
}

function handleStateChange() {
  if(xmlHttp.readyState == 4) {
    if(xmlHttp.status == 200) {
      clearList();
      updateList();
    }
  }
}

// 清除上一次的顯示結果
function clearList() { 
  var techs = document.getElementById("techs");
  while(techs.childNodes.length > 0) {
    techs.removeChild(techs.childNodes[0]);
  }
}

// 以回應更新資料
function updateList() {
  var results = xmlHttp.responseXML.getElementsByTagName("tech");
  var techs = document.getElementById("techs");

  var option = null;
  for(var i = 0; i < results.length; i++) {
    option = document.createElement("option");
    option.appendChild(document.createTextNode(results[i].firstChild.nodeValue));
    techs.appendChild(option);
  }
}
由上面我們可以看到將XMLHttpRequest的onreadystatechange屬性
註冊到handleStateChange method,並利用post機制傳送XML格式的資料。
而所謂的XML格式資料其實就是符合XML規格的字串資料。
因為非同步的資料皆由XMLHttpRequest所控制,所以server回傳資料
也是由 XMLHttpRequest 的 responseXML 屬性取得。

接下來我們用一個servlet來接收並回傳由client端發送來的資料。
public class RefreshServlet extends HttpServlet {
  private static Map inMemoryDB = new HashMap();

  public void init() throws ServletException {
    inMemoryDB.put("func", new String[] { "非同步", "動態清單" });
    inMemoryDB.put("java", 
    new String[] { "JSP", "J2EE", "Spring", "Hibernate" });
    inMemoryDB.put("c#", new String[] { "ASP.Net", "ADO.Net" });
  }

  protected void doPost(HttpServletRequest request, 
    HttpServletResponse response) throws ServletException, IOException {
    String xml = readXMLFromRequestBody(request);
    Document xmlDoc = null;

    try {
      DocumentBuilder builder = 
      DocumentBuilderFactory.newInstance().newDocumentBuilder();
      xmlDoc = builder.parse(new ByteArrayInputStream(xml.getBytes()));
    } catch (ParserConfigurationException e) {
      System.out.println(e);
    } catch (SAXException e) {
      System.out.println(e);
    }

    String responseXML = prepareXMLResponse(xmlDoc);

    response.setContentType("text/xml;charset=utf-8");
    response.getWriter().print(responseXML);
    response.getWriter().close();
  }

  private String readXMLFromRequestBody(HttpServletRequest request) {
    StringBuffer xml = new StringBuffer();

    try {
      BufferedReader reader = request.getReader();
      String line = null;
      while ((line = reader.readLine()) != null) {
        xml.append(line);
      }
    } catch (Exception e) {
      System.out.println("XML讀取有誤…" + e.toString());
    }
    return xml.toString();
  }

  private String prepareXMLResponse(Document xmlDoc) {
    NodeList langNodes = xmlDoc.getElementsByTagName("langs");
    String lang = langNodes.item(0).getFirstChild().getTextContent();

    StringBuffer xml = new StringBuffer();
    xml.append("<techs>");
    String[] techs = (String[])inMemoryDB.get(lang);
    for (int i = 0; i < techs.length; i++) {
      xml.append("<tech>");
      xml.append(techs[i]);
      xml.append("</tech>");
    }
    xml.append("</techs>");
    return xml.toString();
  }
}

因為XML也是字串資料,所以我們先用reader讀入
BufferedReader reader = request.getReader();
String line = null; 
while ((line = reader.readLine()) != null) {
     xml.append(line);
 }
接著利用 DocumentBuilder 來解析每個tag的資料
DocumentBuilder builder =
     DocumentBuilderFactory.newInstance().newDocumentBuilder(); 

xmlDoc = builder.parse(new ByteArrayInputStream(xml.getBytes()));

最後也是利用 StringBuffer 將查詢到的資料組成XML格式的字串資料回傳。