|
JBUILDER的光標定位不準確的最佳解決方案是:進入%JBUILDER_HOME%/bin目錄下,用寫字板編輯jbuilder.config,把下面的配置加進去: |
Ant的簡介:類似make工具,但可以支持多平臺
Ant的安裝:配置ant的準備工作:ant_home 指Ant的安裝目錄,在path中加入%ant_home%/bin,用于命令行下
運 行ant
Ant的結構:主要是通過對build.xml的配置,
Ant內置任務: 描述
property 設置name/value的屬性
mkdir 創建目錄
copy 拷貝
delete 刪除
javac 編繹
war 打包
下面是一個簡單build.xml的示例:
<project name="bookstore" default="about" basedir=".">
<target name="init">
<tstamp/>
<property name="build" value="build" />
<property name="src" value="src" />
<property environment="myenv" />
<property name="servletpath" value="${myenv.CATALINA_HOME}/common/lib/servlet-api.jar" />
<property name="mysqlpath" value="WEB-INF/lib/mysqldriver.jar" />
<mkdir dir="${build}" />
<mkdir dir="${build}\WEB-INF" />
<mkdir dir="${build}\WEB-INF\classes" />
<copy todir="${build}" >
<fileset dir="${basedir}" >
<include name="*.jsp" />
<include name="*.bmp" />
<include name="WEB-INF/**" />
<exclude name="build.xml" />
</fileset>
</copy>
</target>
<target name="compile" depends="init">
<javac srcdir="${src}"
destdir="${build}/WEB-INF/classes"
classpath="${servletpath}:${mysqlpath}">
</javac>
</target>
<target name="bookstorewar" depends="compile">
<war warfile="${build}/bookstore.war" webxml="${build}/WEB-INF/web.xml">
<lib dir="${build}/WEB-INF/lib"/>
<classes dir="${build}/WEB-INF/classes"/>
<fileset dir="${build}"/>
</war>
</target>
<target name="about" >
<echo>
This build.xml file contains targets
for building bookstore web application
</echo>
</target>
</project>
從示例我們看出來,整個xml是一個project,project下有幾個為init,compile,的target
運行時首先在這個目錄下打開dos窗口,以這個xml為準,如果你只運行ant那么只會輸出echo中的內容
因為project的default是about; 如果運行ant complie 它會執行兩個target: init 和complie,因為complie是依靠init
的。
用了以后發現ant 原來很簡單,當然現在只是學了個皮毛而已。
RMI,遠程方法調用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java應用程序的方便途徑。RMI是非常容易使用的,但是它非常的強大。
RMI的基礎是接口,RMI構架基于一個重要的原理:定義接口和定義接口的具體實現是分開的。下面我們通過具體的例子,建立一個簡單的遠程計算服務和使用它的客戶程序
一個正常工作的RMI系統由下面幾個部分組成:
● 遠程服務的接口定義
● 遠程服務接口的具體實現
● 樁(Stub)和框架(Skeleton)文件
● 一個運行遠程服務的服務器
● 一個RMI命名服務,它允許客戶端去發現這個遠程服務
● 類文件的提供者(一個HTTP或者FTP服務器)
● 一個需要這個遠程服務的客戶端程序
下面我們一步一步建立一個簡單的RMI系統。首先在你的機器里建立一個新的文件夾,以便放置我們創建的文件,為了簡單起見,我們只使用一個文件夾存放客戶端和服務端代碼,并且在同一個目錄下運行服務端和客戶端。
如果所有的RMI文件都已經設計好了,那么你需要下面的幾個步驟去生成你的系統:
1、 編寫并且編譯接口的Java代碼
2、 編寫并且編譯接口實現的Java代碼
3、 從接口實現類中生成樁(Stub)和框架(Skeleton)類文件
4、 編寫遠程服務的主運行程序
5、 編寫RMI的客戶端程序
6、 安裝并且運行RMI系統
1、 接口
第一步就是建立和編譯服務接口的Java代碼。這個接口定義了所有的提供遠程服務的功能,下面是源程序:
//Calculator.java
//define the interface
import java.rmi.Remote;
public interface Calculator extends Remote
{
public long add(long a, long b)
throws java.rmi.RemoteException;
public long sub(long a, long b)
throws java.rmi.RemoteException;
public long mul(long a, long b)
throws java.rmi.RemoteException;
public long div(long a, long b)
throws java.rmi.RemoteException;
}
注意,這個接口繼承自Remote,每一個定義的方法都必須拋出一個RemoteException異常對象。
建立這個文件,把它存放在剛才的目錄下,并且編譯。
>javac Calculator.java
2、 接口的具體實現
下一步,我們就要寫遠程服務的具體實現,這是一個CalculatorImpl類文件:
//CalculatorImpl.java
//Implementation
import java.rmi.server.UnicastRemoteObject
public class CalculatorImpl extends UnicastRemoteObject implements Calculator
{
// 這個實現必須有一個顯式的構造函數,并且要拋出一個RemoteException異常
public CalculatorImpl()
throws java.rmi.RemoteException {
super();
}
public long add(long a, long b)
throws java.rmi.RemoteException {
return a + b;
}
public long sub(long a, long b)
throws java.rmi.RemoteException {
return a - b;
}
public long mul(long a, long b)
throws java.rmi.RemoteException {
return a * b;
}
public long div(long a, long b)
throws java.rmi.RemoteException {
return a / b;
}
}
同樣的,把這個文件保存在你的目錄里然后編譯他。
這個實現類使用了UnicastRemoteObject去聯接RMI系統。在我們的例子中,我們是直接的從UnicastRemoteObject這個類上繼承的,事實上并不一定要這樣做,如果一個類不是從UnicastRmeoteObject上繼承,那必須使用它的exportObject()方法去聯接到RMI。
如果一個類繼承自UnicastRemoteObject,那么它必須提供一個構造函數并且聲明拋出一個RemoteException對象。當這個構造函數調用了super(),它久激活UnicastRemoteObject中的代碼完成RMI的連接和遠程對象的初始化。
3、 樁(Stubs)和框架(Skeletons)
下一步就是要使用RMI編譯器rmic來生成樁和框架文件,這個編譯運行在遠程服務實現類文件上。
>rmic CalculatorImpl
在你的目錄下運行上面的命令,成功執行完上面的命令你可以發現一個Calculator_stub.class文件,如果你是使用的Java2SDK,那么你還可以發現Calculator_Skel.class文件。
4、 主機服務器
遠程RMI服務必須是在一個服務器中運行的。CalculatorServer類是一個非常簡單的服務器。
//CalculatorServer.java
import java.rmi.Naming;
public class CalculatorServer {
public CalculatorServer() {
try {
Calculator c = new CalculatorImpl();
Naming.rebind("rmi://localhost:1099/CalculatorService", c);
} catch (Exception e) {
System.out.println("Trouble: " + e);
}
}
public static void main(String args[]) {
new CalculatorServer();
}
}
建立這個服務器程序,然后保存到你的目錄下,并且編譯它。
5、 客戶端
客戶端源代碼如下:
//CalculatorClient.java
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
public class CalculatorClient {
public static void main(String[] args) {
try {
Calculator c = (Calculator)
Naming.lookup(
"rmi://localhost
/CalculatorService");
System.out.println( c.sub(4, 3) );
System.out.println( c.add(4, 5) );
System.out.println( c.mul(3, 6) );
System.out.println( c.div(9, 3) );
}
catch (MalformedURLException murle) {
System.out.println();
System.out.println(
"MalformedURLException");
System.out.println(murle);
}
catch (RemoteException re) {
System.out.println();
System.out.println(
"RemoteException");
System.out.println(re);
}
catch (NotBoundException nbe) {
System.out.println();
System.out.println(
"NotBoundException");
System.out.println(nbe);
}
catch (
java.lang.ArithmeticException
ae) {
System.out.println();
System.out.println(
"java.lang.ArithmeticException");
System.out.println(ae);
}
}
}
保存這個客戶端程序到你的目錄下(注意這個目錄是一開始建立那個,所有的我們的文件都在那個目錄下),并且編譯他。
6、 運行RMI系統
現在我們建立了所有運行這個簡單RMI系統所需的文件,現在我們終于可以運行這個RMI系統啦!來享受吧。
我們是在命令控制臺下運行這個系統的,你必須開啟三個控制臺窗口,一個運行服務器,一個運行客戶端,還有一個運行RMIRegistry。
首先運行注冊程序RMIRegistry,你必須在包含你剛寫的類的那么目錄下運行這個注冊程序。
>rmiregistry
好,這個命令成功的話,注冊程序已經開始運行了,不要管他,現在切換到另外一個控制臺,在第二個控制臺里,我們運行服務器CalculatorService,因為RMI的安全機制將在服務端發生作用,所以你必須增加一條安全策略。以下是對應安全策略的例子
grant {
permission java.security.AllPermission "", "";
};
注意:這是一條最簡單的安全策略,它允許任何人做任何事,對于你的更加關鍵性的應用,你必須指定更加詳細安全策略。
現在為了運行服務端,你需要除客戶類(CalculatorClient.class)之外的所有的類文件。確認安全策略在policy.txt文件之后,使用如下命令來運行服務器。
> java -Djava.security.policy=policy.txt CalculatorServer
這個服務器就開始工作了,把接口的實現加載到內存等待客戶端的聯接。好現在切換到第三個控制臺,啟動我們的客戶端。
為了在其他的機器運行客戶端程序你需要一個遠程接口(Calculator.class) 和一個stub(CalculatorImpl_Stub.class)。 使用如下命令運行客戶端
prompt> java -Djava.security.policy=policy.txt CalculatorClient
如果所有的這些都成功運行,你應該看到下面的輸出:
1
9
18
3
如果你看到了上面的輸出,恭喜你,你成功了,你已經成功的創建了一個RMI系統,并且使他正確工作了。即使你運行在同一個計算機上,RMI還是使用了你的網絡堆棧和TCP/IP去進行通訊,并且是運行在三個不同的Java虛擬機上。這已經是一個完整的RMI系統。
在一個有密碼保護的Web應用中,正確處理用戶退出過程并不僅僅只需調用HttpSession的invalidate()方法。現在大部分瀏覽器上都有后退和前進按鈕,允許用戶后退或前進到一個頁面。如果在用戶在退出一個Web應用后按了后退按鈕瀏覽器把緩存中的頁面呈現給用戶,這會使用戶產生疑惑,他們會開始擔心他們的個人數據是否安全。許多Web應用強迫用戶退出時關閉整個瀏覽器,這樣,用戶就無法點擊后退按鈕了。還有一些使用javascript,但在某些客戶端瀏覽器這卻不一定起作用。這些解決方案都很笨拙且不能保證在任一情況下100%有效,同時,它也要求用戶有一定的操作經驗。
這篇文章以示例闡述了正確解決用戶退出問題的方案。作者Kevin Le首先描述了一個密碼保護Web應用,然后以示例程序解釋問題如何產生并討論解決問題的方案。文章雖然是針對JSP頁面進行闡述,但作者所闡述的概念很容易理解切能夠為其他Web技術所采用。最后作者展示了如何用Jakarta Struts優雅地解決這一問題。
大部分Web應用不會包含象銀行賬戶或信用卡資料那樣機密的信息,但一旦涉及到敏感數據,我們就需要提供一類密碼保護機制。舉例來說,一個工廠中工人通過Web訪問他們的時間安排、進入他們的訓練課程以及查看他們的薪金等等。此時應用SSL(Secure Socket Layer)有點殺雞用牛刀的感覺,但不可否認,我們又必須為這些應用提供密碼保護,否則,工人(也就是Web應用的使用者)可以窺探到工廠中其他雇員的私人機密信息。
與上述情形相似的還有位處圖書館、醫院等公共場所的計算機。在這些地方,許多用戶共同使用幾臺計算機,此時保護用戶的個人數據就顯得至關重要。設計良好編寫優秀的應用對用戶專業知識的要求少之又少。
我們來看一下現實世界中一個完美的Web應用是如何表現的:一個用戶通過瀏覽器訪問一個頁面。Web應用展現一個登陸頁面要求用戶輸入有效的驗證信息。用戶輸入了用戶名和密碼。此時我們假設用戶提供的身份驗證信息是正確的,經過了驗證過程,Web應用允許用戶瀏覽他有權訪問的區域。用戶想退出時,點擊退出按鈕,Web應用要求用戶確認他是否則真的需要退出,如果用戶確定退出,Session結束,Web應用重新定位到登陸頁面。用戶可以放心的離開而不用擔心他的信息會泄露。另一個用戶坐到了同一臺電腦前,他點擊后退按鈕,Web應用不應該出現上一個用戶訪問過的任何一個頁面。事實上,Web應用在第二個用戶提供正確的驗證信息之前應當一直停留在登陸頁面上。
通過示例程序,文章向您闡述了如何在一個Web應用中實現這一功能。
JSP示例
為了更為有效地闡述實現方案,本文將從展示一個示例應用logoutSampleJSP1中碰到的問題開始。這個示例代表了許多沒有正確解決退出過程的Web應用。logoutSampleJSP1包含了下述jsp頁面:login.jsp, home.jsp, secure1.jsp, secure2.jsp, logout.jsp, loginAction.jsp, and logoutAction.jsp。其中頁面home.jsp, secure1.jsp, secure2.jsp, 和logout.jsp是不允許未經認證的用戶訪問的,也就是說,這些頁面包含了重要信息,在用戶登陸之前或者退出之后都不應該出現在瀏覽器中。login.jsp包含了用于用戶輸入用戶名和密碼的form。logout.jsp頁包含了要求用戶確認是否退出的form。loginAction.jsp和logoutAction.jsp作為控制器分別包含了登陸和退出代碼。
第二個示例應用logoutSampleJSP2展示了如何解決示例logoutSampleJSP1中的問題。然而,第二個應用自身也是有疑問的。在特定的情況下,退出問題還是會出現。
第三個示例應用logoutSampleJSP3在第二個示例上進行了改進,比較完善地解決了退出問題。
最后一個示例logoutSampleStruts展示了Struts如何優美地解決登陸問題。
注意:本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant瀏覽器上測試通過。
Login action
Brian Pontarelli的經典文章《J2EE Security: Container Versus Custom》討論了不同的J2EE認證途徑。文章同時指出,HTTP協議和基于form的認證并未提供處理用戶退出的機制。因此,解決途徑便是引入自定義的安全實現機制。
自定義的安全認證機制普遍采用的方法是從form中獲得用戶輸入的認證信息,然后到諸如LDAP (lightweight directory access protocol)或關系數據庫的安全域中進行認證。如果用戶提供的認證信息是有效的,登陸動作往HttpSession對象中注入某個對象。HttpSession存在著注入的對象則表示用戶已經登陸。為了方便讀者理解,本文所附的示例只往HttpSession中寫入一個用戶名以表明用戶已經登陸。清單1是從loginAction.jsp頁面中節選的一段代碼以此闡述登陸動作:
Listing 1 //... //initialize RequestDispatcher object; set forward to home page by default RequestDispatcher rd = request.getRequestDispatcher("home.jsp"); //Prepare connection and statement rs = stmt.executeQuery("select password from USER where userName = '" + userName + "'"); if (rs.next()) { //Query only returns 1 record in the result set; only 1 password per userName which is also the primary key if (rs.getString("password").equals(password)) { //If valid password session.setAttribute("User", userName); //Saves username string in the session object } else { //Password does not match, i.e., invalid user password request.setAttribute("Error", "Invalid password."); rd = request.getRequestDispatcher("login.jsp"); } } //No record in the result set, i.e., invalid username else { request.setAttribute("Error", "Invalid user name."); rd = request.getRequestDispatcher("login.jsp"); } } //As a controller, loginAction.jsp finally either forwards to "login.jsp" or "home.jsp" rd.forward(request, response); //... |
Listing 2 //... session.removeAttribute("User"); session.invalidate(); //... |
Listing 3 //... String userName = (String) session.getAttribute("User"); if (null == userName) { request.setAttribute("Error", "Session has ended. Please login."); RequestDispatcher rd = request.getRequestDispatcher("login.jsp"); rd.forward(request, response); } //... //Allow the rest of the dynamic content in this JSP to be served to the browser //... |
//... response.setHeader("Cache-Control","no-cache"); //Forces caches to obtain a new copy of the page from the origin server response.setHeader("Cache-Control","no-store"); //Directs caches not to store the page under any circumstance response.setDateHeader("Expires", 0); //Causes the proxy cache to see the page as "stale" response.setHeader("Pragma","no-cache"); //HTTP 1.0 backward compatibility String userName = (String) session.getAttribute("User"); if (null == userName) { request.setAttribute("Error", "Session has ended. Please login."); RequestDispatcher rd = request.getRequestDispatcher("login.jsp"); rd.forward(request, response); } //... |
//... RequestDispatcher rd = request.getRequestDispatcher("home.jsp"); //Forward to homepage by default //... if (rs.getString("password").equals(password)) { //If valid password long lastLogonDB = rs.getLong("lastLogon"); if (lastLogonForm > lastLogonDB) { session.setAttribute("User", userName); //Saves username string in the session object stmt.executeUpdate("update USER set lastLogon= " + lastLogonForm + " where userName = '" + userName + "'"); } else { request.setAttribute("Error", "Session has ended. Please login."); rd = request.getRequestDispatcher("login.jsp"); } } else { //Password does not match, i.e., invalid user password request.setAttribute("Error", "Invalid password."); rd = request.getRequestDispatcher("login.jsp"); } //... rd.forward(request, response); //... |
public abstract class BaseAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setHeader("Cache-Control","no-cache"); //Forces caches to obtain a new copy of the page from the origin server response.setHeader("Cache-Control","no-store"); //Directs caches not to store the page under any circumstance response.setDateHeader("Expires", 0); //Causes the proxy cache to see the page as "stale" response.setHeader("Pragma","no-cache"); //HTTP 1.0 backward compatibility if (!this.userIsLoggedIn(request)) { ActionErrors errors = new ActionErrors(); errors.add("error", new ActionError("logon.sessionEnded")); this.saveErrors(request, errors); return mapping.findForward("sessionEnded"); } return executeAction(mapping, form, request, response); } protected abstract ActionForward executeAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; private boolean userIsLoggedIn(HttpServletRequest request) { if (request.getSession().getAttribute("User") == null) { return false; } return true; } } |
<action path="/secure1" type="com.kevinhle.logoutSampleStruts.Secure1Action" scope="request"> <forward name="success" path="/WEB-INF/jsps/secure1.jsp"/> <forward name="sessionEnded" path="/login.jsp"/> </action> |
public class Secure1Action extends BaseAction { public ActionForward executeAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { HttpSession session = request.getSession(); return (mapping.findForward("success")); } } |
Beanutils用了魔術般的反射技術,實現了很多夸張有用的功能,都是C/C++時代不敢想的。無論誰的項目,始終一天都會用得上它。我算是后知后覺了,第一回看到它的時候居然錯過。
在這框架滿天飛的年代,不能事事都保證執行getter,setter函數了,有時候屬性是要需要根據名字動態的取得的,就像這樣:
BeanUtils.getProperty(myBean,"code");而BeanUtils更強的功能是直接訪問內嵌對象的屬性,只要使用逗號分割。
BeanUtils.getProperty(orderBean, "address.city");
其他類庫的BeanUtils通常都很簡單,不能訪問內嵌的對象,所以經常要用Jakata的BeanUtils替換它們。
BeanUtils還支持List和Map類型的屬性。如下面的語法即可取得顧客列表中第一個顧客的名字
BeanUtils.getProperty(orderBean, "customers[1].name");
其中BeanUtils會使用ConvertUtils類把字符串轉為Bean屬性的真正類型,方便從HttpServletRequest等對象中提取bean,或者把bean輸出到頁面。
而PropertyUtils就會原色的保留Bean原來的類型。
還是通過反射,動態設定Bean按照哪個屬性來排序,而不再需要在bean的Compare接口進行復雜的條件判斷。 List peoples = ...; // Person對象的列表 Collections.sort(peoples, new BeanComparator("age")); }}} 如果要支持多個屬性的復合排序,如"Order By lastName,firstName" ArrayList sortFields = new ArrayList();
sortFields.add(new BeanComparator("lastName"));
sortFields.add(new BeanComparator("firstName"));
ComparatorChain multiSort = new ComparatorChain(sortFields);
Collections.sort(rows,multiSort);
其中ComparatorChain屬于jakata commons-collections包。
如果age屬性不是普通類型,構造函數需要再傳入一個comparator對象為age變量排序。
String a = request.getParameter("a"); bean.setA(a); String b = ....不妨改為
MyBean bean = ...; HashMap map = new HashMap(); Enumeration names = request.getParameterNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); map.put(name, request.getParameterValues(name)); } BeanUtils.populate(bean, map);其中BeanUtils的populate方法或者getProperty,setProperty方法其實都會調用convert進行轉換。
但Converter只支持一些基本的類型,甚至連java.util.Date類型也不支持。而且它比較笨的一個地方是當遇到不認識的類型時,居然會拋出異常來。
對于Date類型,我參考它的sqldate類型實現了一個Converter,而且添加了一個設置日期格式的函數。要把這個Converter注冊,需要如下語句:
ConvertUtilsBean convertUtils = new ConvertUtilsBean(); DateConverter dateConverter = new DateConverter(); convertUtils.register(dateConverter,Date.class); //因為要注冊converter,所以不能再使用BeanUtils的靜態方法了,必須創建BeanUtilsBean實例 BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils,new PropertyUtilsBean()); beanUtils.setProperty(bean, name, value); !4 其他功能 4.1 PropertyUtils,當屬性為Collection,Map時的動態讀取: Collection: 提供index {{{ BeanUtils.getIndexedProperty(orderBean,"items",1);或者
BeanUtils.getIndexedProperty(orderBean,"items[1]");
Map: 提供Key Value
BeanUtils.getMappedProperty(orderBean, "items","111");//key-value goods_no=111
或者
BeanUtils.getMappedProperty(orderBean, "items(111)");4.2 PropertyUtils,獲取屬性的Class類型
public static Class getPropertyType(Object bean, String name)
4.3 ConstructorUtils,動態創建對象
public static Object invokeConstructor(Class klass, Object arg)
4.4 DynaClass動態類
Struts的Token(令牌)機制能夠很好的解決表單重復提交的問題,基本原理是:服務器端在處理到達的請求之前,會將請求中包含的令牌值與保存在當前用戶會話中的令牌值進行比較,看是否匹配。在處理完該請求后,且在答復發送給客戶端之前,將會產生一個新的令牌,該令牌除傳給客戶端以外,也會將用戶會話中保存的舊的令牌進行替換。這樣如果用戶回退到剛才的提交頁面并再次提交的話,客戶端傳過來的令牌就和服務器端的令牌不一致,從而有效地防止了重復提交的發生。
這時其實也就是兩點,第一:你需要在請求中有這個令牌值,請求中的令牌值如何保存,其實就和我們平時在頁面中保存一些信息是一樣的,通過隱藏字段來保存,保存的形式如: 〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,這個value是TokenProcessor類中的generateToken()獲得的,是根據當前用戶的session id和當前時間的long值來計算的。第二:在客戶端提交后,我們要根據判斷在請求中包含的值是否和服務器的令牌一致,因為服務器每次提交都會生成新的Token,所以,如果是重復提交,客戶端的Token值和服務器端的Token值就會不一致。下面就以在數據庫中插入一條數據來說明如何防止重復提交。
在Action中的add方法中,我們需要將Token值明確的要求保存在頁面中,只需增加一條語句:saveToken(request);,如下所示:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
//前面的處理省略
saveToken(request);
return mapping.findForward("add");
}在Action的insert方法中,我們根據表單中的Token值與服務器端的Token值比較,如下所示:
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
if (isTokenValid(request, true)) {
// 表單不是重復提交
//這里是保存數據的代碼
} else {
//表單重復提交
saveToken(request);
//其它的處理代碼
}
}
其實使用起來很簡單,舉個最簡單、最需要使用這個的例子:
一般控制重復提交主要是用在對數據庫操作的控制上,比如插入、更新、刪除等,由于更新、刪除一般都是通過id來操作(例如:updateXXXById, removeXXXById),所以這類操作控制的意義不是很大(不排除個別現象),重復提交的控制也就主要是在插入時的控制了。
先說一下,我們目前所做項目的情況:
目前的項目是用Struts+Spring+Ibatis,頁面用jstl,Struts復雜View層,Spring在Service層提供事務控制,Ibatis是用來代替JDBC,所有頁面的訪問都不是直接訪問jsp,而是訪問Structs的Action,再由Action來Forward到一個Jsp,所有針對數據庫的操作,比如取數據或修改數據,都是在Action里面完成,所有的Action一般都繼承BaseDispatchAction,這個是自己建立的類,目的是為所有的Action做一些統一的控制,在Struts層,對于一個功能,我們一般分為兩個Action,一個Action里的功能是不需要調用Struts的驗證功能的(常見的方法名稱有add,edit,remove,view,list),另一個是需要調用Struts的驗證功能的(常見的方法名稱有insert,update)。
就拿論壇發貼來說吧,論壇發貼首先需要跳轉到一個頁面,你可以填寫帖子的主題和內容,填寫完后,單擊“提交”,貼子就發表了,所以這里經過兩個步驟:
1、轉到一個新增的頁面,在Action里我們一般稱為add,例如:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
//這一句是輸出調試信息,表示代碼執行到這一段了
log.debug(":: action - subject add");
//your code here
//這里保存Token值
saveToken(request);
//跳轉到add頁面,在Structs-config.xml里面定義,例如,跳轉到subjectAdd.jsp
return mapping.findForward("add");
}
2、在填寫標題和內容后,選擇 提交 ,會提交到insert方法,在insert方法里判斷,是否重復提交了。
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){
if (isTokenValid(request, true)) {
// 表單不是重復提交
//這里是保存數據的代碼
} else {
//表單重復提交
saveToken(request);
//其它的處理代碼
}
}
下面更詳細一點(注意,下面所有的代碼使用全角括號):
1、你想發貼時,點擊“我要發貼”鏈接的代碼可以里這樣的:
〈html:link action="subject.do?method=add"〉我要發貼〈/html:link〉
subject.do 和 method 這些在struct-config.xml如何定義我就不說了,點擊鏈接后,會執行subject.do的add方法,代碼如上面說的,跳轉到subjectAdd.jsp頁面。頁面的代碼大概如下:
〈html:form action="subjectForm.do?method=insert"〉
〈html:text property="title" /〉
〈html:textarea property="content" /〉
〈html:submit property="發表" /〉
〈html:reset property="重填" /〉
〈html:form〉
如果你在add方法里加了“saveToken(request);”這一句,那在subjectAdd.jsp生成的頁面上,會多一個隱藏字段,類似于這樣〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,
2、點擊發表后,表單提交到subjectForm.do里的insert方法后,你在insert方法里要將表單的數據插入到數據庫中,如果沒有進行重復提交的控制,那么每點擊一次瀏覽器的刷新按鈕,都會在數據庫中插入一條相同的記錄,增加下面的代碼,你就可以控制用戶的重復提交了。
if (isTokenValid(request, true)) {
// 表單不是重復提交
//這里是保存數據的代碼
} else {
//表單重復提交
saveToken(request);
//其它的處理代碼
}
注意,你必須在add方法里使用了saveToken(request),你才能在insert里判斷,否則,你每次保存操作都是重復提交。
記住一點,Struts在你每次訪問Action的時候,都會產生一個令牌,保存在你的Session里面,如果你在Action里的函數里面,使用了saveToken(request);,那么這個令牌也會保存在這個Action所Forward到的jsp所生成的靜態頁面里。
如果你在你Action的方法里使用了isTokenValid,那么Struts會將你從你的request里面去獲取這個令牌值,然后和Session里的令牌值做比較,如果兩者相等,就不是重復提交,如果不相等,就是重復提交了。
由于我們項目的所有Action都是繼承自BaseDispatchAction這個類,所以我們基本上都是在這個類里面做了表單重復提交的控制,默認是控制add方法和insert方法,如果需要控制其它的方法,就自己手動寫上面這些代碼,否則是不需要手寫的,控制的代碼如下:
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String parameter = mapping.getParameter();
String name = request.getParameter(parameter);
if (null == name) { //如果沒有指定 method ,則默認為 list
name = "list";
}
if ("add".equals(name)) {
if ("add".equals(name)) {
saveToken(request);
}
} else if ("insert".equals(name)) {
if (!isTokenValid(request, true)) {
resetToken(request);
saveError(request, new ActionMessage("error.repeatSubmit"));
log.error("重復提交!");
return mapping.findForward("error");
}
}
return dispatchMethod2(mapping, form, request, response, name);
}
}
SELECT COUNT(*) AS Expr1 FROM History
DELETE FROM History WHERE (Id > 0)
truncate table youtable;
為什么要用TRUNCATE TABLE 語句代替DELETE語句?當你使用TRUNCATE TABLE語句時,記錄的刪除是不作記錄的。也就是說,這意味著TRUNCATE TABLE 要比DELETE快得多。
我今年39歲了,25歲研究生畢業,工作14年,回頭看看,應該說走了不少的彎路,有一些經驗和教訓。現在開一個小公司,賺的錢剛夠養家糊口的。看看這些剛畢業的學生,對前景也很迷茫,想拋磚引玉,談談自己的看法,局限于理工科的學生,我對文科的不懂,身邊的朋友也沒有這一類型的。滾動公告欄的實現
用到如下html標簽:
《MARQUEE id=mar onmouseout=this.start(); direction=up height=150 name="mar"
onmouseover="this.stop();" scrollAmount="1" scrollDelay="0"
style="LINE-HEIGHT: 100%; PADDING-LEFT: 0px; PADDING-RIGHT:0px"width="100%">
1凈化社會環境
2也是凈化社會平步青云
3凈化社會環境
4也是凈化社會平步青云
5凈化社會環境
6也是凈化社會平步青云
7凈化社會環境
8也是凈化社會平步青云
其中marquee中height中的屬性可以更改每次顯示的行數,……
天涼好個秋