2008年4月19日
#
c3p0很容易使用的開源專業(yè)級jdbc數(shù)據(jù)庫緩沖池。
它是sourceforge上的一個開源項目,
項目在
http://sourceforge.net/projects/c3p0
他的眾多特性這里就不一一介紹了。
比較爽的一點就是
當(dāng)Connection歸還緩沖池時,c3p0會很小心的關(guān)閉
這條連接打開的Statement和ResultSet,免去了使用時
自己動手小心翼翼的關(guān)閉。
c3p0使用非常簡單,這里給一個例子
package common.db;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;
public final class ConnectionManager {
?private static ConnectionManager instance;
?
?public ComboPooledDataSource ds;
?private static String c3p0Properties="c3p0.properties";
?
?private ConnectionManager() throws Exception {
??Properties p = new Properties();
??p.load(this.getClass().getResourceAsStream(c3p0Properties));
??ds = new ComboPooledDataSource();
?}
?
?public static final ConnectionManager getInstance() {
??if (instance == null) {
???try {
????instance = new ConnectionManager();
???} catch (Exception e) {
????e.printStackTrace();
???}
??}
??return instance;
?}
?
?public synchronized final Connection getConnection() {
??try {
???return ds.getConnection();
??} catch (SQLException e) {
???e.printStackTrace();
??}
??return null;
?}
?protected void finalize() throws Throwable {
??DataSources.destroy(ds);?//關(guān)閉datasource
??super.finalize();
?}
?
}
然后在ConnectionManager類的目錄下再創(chuàng)建一個配置文件c3p0.properties
內(nèi)容如下:
#db login parameters
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost/test?useUnicode=no&characterEncoding=GBK
user=test
password=test
#pool parameters
initialPoolSize=2
maxPoolSize=5
#maxIdleTime=10
#idleConnectionTestPeriod=5
autoCommitOnClose=true
完整的配置文件參數(shù)參看c3p0的文檔
使用connection時很簡單
Connection conn = ConnectionManager.getInstance().getConnection();
...
最后 conn.close() 即可,
如題。研究中...以前沒有注意過。
*匹配除了換行之外的所有字符
合法IP的正則表達(dá)式 ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
\s匹配任意的空白符、(空格、制表符、換行符、中文全角空格)
\w匹配字母或數(shù)字或下劃線或漢字
表1.常用的元字符代碼 | 說明 |
---|
. | 匹配除換行符以外的任意字符 |
\w | 匹配字母或數(shù)字或下劃線或漢字 |
\s | 匹配任意的空白符 |
\d | 匹配數(shù)字 |
\b | 匹配單詞的開始或結(jié)束 |
^ | 匹配字符串的開始 |
$ | 匹配字符串的結(jié)束 |
表2.常用的限定符代碼/語法 | 說明 |
---|
* | 重復(fù)零次或更多次 |
+ | 重復(fù)一次或更多次 |
? | 重復(fù)零次或一次 |
{n} | 重復(fù)n次 |
{n,} | 重復(fù)n次或更多次 |
{n,m} | 重復(fù)n到m次 |
后向引用
使用小括號指定一個子表達(dá)式后,匹配這個子表達(dá)式的文本(也就是此分組捕獲的內(nèi)容)可以在表達(dá)式或其它程序中作進(jìn)一步的處理。默認(rèn)情況下,每個分組會自動擁有一個組號,規(guī)則是:從左向右,以分組的左括號為標(biāo)志,第一個出現(xiàn)的分組的組號為1,第二個為2,以此類推。
后向引用用于重復(fù)搜索前面某個分組匹配的文本。例如,\1代表分組1匹配的文本。難以理解?請看示例:
\b(\w+)\b\s+\1\b可以用來匹配重復(fù)的單詞,像go go, 或者kitty kitty。這個表達(dá)式首先是一個單詞,也就是單詞開始處和結(jié)束處之間的多于一個的字母或數(shù)字(\b(\w+)\b),這個單詞會被捕獲到編號為1的分組中,然后是1個或幾個空白符(\s+),最后是分組1中捕獲的內(nèi)容(也就是前面匹配的那個單詞)(\1)。
你也可以自己指定子表達(dá)式的組名。要指定一個子表達(dá)式的組名,請使用這樣的語法:(?<Word>\w+)(或者把尖括號換成'也行:(?'Word'\w+)),這樣就把\w+的組名指定為Word了。要反向引用這個分組捕獲的內(nèi)容,你可以使用\k<Word>,所以上一個例子也可以寫成這樣:\b(?<Word>\w+)\b\s+\k<Word>\b。
使用小括號的時候,還有很多特定用途的語法。下面列出了最常用的一些:
表4.常用分組語法分類 | 代碼/語法 | 說明 |
---|
捕獲 | (exp) | 匹配exp,并捕獲文本到自動命名的組里 |
---|
(?<name>exp) | 匹配exp,并捕獲文本到名稱為name的組里,也可以寫成(?'name'exp) |
(?:exp) | 匹配exp,不捕獲匹配的文本,也不給此分組分配組號 |
零寬斷言 | (?=exp) | 匹配exp前面的位置 |
---|
(?<=exp) | 匹配exp后面的位置 |
(?!exp) | 匹配后面跟的不是exp的位置 |
(?<!exp) | 匹配前面不是exp的位置 |
注釋 | (?#comment) | 這種類型的分組不對正則表達(dá)式的處理產(chǎn)生任何影響,用于提供注釋讓人閱讀 |
---|
表5.懶惰限定符代碼/語法 | 說明 |
---|
*? | 重復(fù)任意次,但盡可能少重復(fù) |
+? | 重復(fù)1次或更多次,但盡可能少重復(fù) |
?? | 重復(fù)0次或1次,但盡可能少重復(fù) |
{n,m}? | 重復(fù)n到m次,但盡可能少重復(fù) |
{n,}? | 重復(fù)n次以上,但盡可能少重復(fù) |
表6.常用的處理選項名稱 | 說明 |
---|
IgnoreCase(忽略大小寫) | 匹配時不區(qū)分大小寫。 |
Multiline(多行模式) | 更改^和$的含義,使它們分別在任意一行的行首和行尾匹配,而不僅僅在整個字符串的開頭和結(jié)尾匹配。(在此模式下,$的精確含意是:匹配\n之前的位置以及字符串結(jié)束前的位置.) |
Singleline(單行模式) | 更改.的含義,使它與每一個字符匹配(包括換行符\n)。 |
IgnorePatternWhitespace(忽略空白) | 忽略表達(dá)式中的非轉(zhuǎn)義空白并啟用由#標(biāo)記的注釋。 |
RightToLeft(從右向左查找) | 匹配從右向左而不是從左向右進(jìn)行。 |
ExplicitCapture(顯式捕獲) | 僅捕獲已被顯式命名的組。 |
ECMAScript(JavaScript兼容模式) | 使表達(dá)式的行為與它在JavaScript里的行為一致。 |
表7.尚未詳細(xì)討論的語法代碼/語法 | 說明 |
---|
\a | 報警字符(打印它的效果是電腦嘀一聲) |
\b | 通常是單詞分界位置,但如果在字符類里使用代表退格 |
\t | 制表符,Tab |
\r | 回車 |
\v | 豎向制表符 |
\f | 換頁符 |
\n | 換行符 |
\e | Escape |
\0nn | ASCII代碼中八進(jìn)制代碼為nn的字符 |
\xnn | ASCII代碼中十六進(jìn)制代碼為nn的字符 |
\unnnn | Unicode代碼中十六進(jìn)制代碼為nnnn的字符 |
\cN | ASCII控制字符。比如\cC代表Ctrl+C |
\A | 字符串開頭(類似^,但不受處理多行選項的影響) |
\Z | 字符串結(jié)尾或行尾(不受處理多行選項的影響) |
\z | 字符串結(jié)尾(類似$,但不受處理多行選項的影響) |
\G | 當(dāng)前搜索的開頭 |
\p{name} | Unicode中命名為name的字符類,例如\p{IsGreek} |
(?>exp) | 貪婪子表達(dá)式 |
(?<x>-<y>exp) | 平衡組 |
(?im-nsx:exp) | 在子表達(dá)式exp中改變處理選項 |
(?im-nsx) | 為表達(dá)式后面的部分改變處理選項 |
(?(exp)yes|no) | 把exp當(dāng)作零寬正向先行斷言,如果在這個位置能匹配,使用yes作為此組的表達(dá)式;否則使用no |
(?(exp)yes) | 同上,只是使用空表達(dá)式作為no |
(?(name)yes|no) | 如果命名為name的組捕獲到了內(nèi)容,使用yes作為表達(dá)式;否則使用no |
(?(name)yes) | 同上,只是使用空表達(dá)式作為no |
Lucene是一個高性能的java全文檢索工具包,它使用的是倒排文件索引結(jié)構(gòu)。該結(jié)構(gòu)及相應(yīng)的生成算法如下:
0)設(shè)有兩篇文章1和2
文章1的內(nèi)容為:Tom?lives?in?Guangzhou,I?live?in?Guangzhou?too.
文章2的內(nèi)容為:He?once?lived?in?Shanghai.
1)由于lucene是基于關(guān)鍵詞索引和查詢的,首先我們要取得這兩篇文章的關(guān)鍵詞,通常我們需要如下處理措施
a.我們現(xiàn)在有的是文章內(nèi)容,即一個字符串,我們先要找出字符串中的所有單詞,即分詞。英文單詞由于用空格分隔,比較好處理。中文單詞間是連在一起的需要特殊的分詞處理。
b.文章中的”in”,?“once”?“too”等詞沒有什么實際意義,中文中的“的”“是”等字通常也無具體含義,這些不代表概念的詞可以過濾掉
c.用戶通常希望查“He”時能把含“he”,“HE”的文章也找出來,所以所有單詞需要統(tǒng)一大小寫。
d.用戶通常希望查“l(fā)ive”時能把含“l(fā)ives”,“l(fā)ived”的文章也找出來,所以需要把“l(fā)ives”,“l(fā)ived”還原成“l(fā)ive”
e.文章中的標(biāo)點符號通常不表示某種概念,也可以過濾掉
在lucene中以上措施由Analyzer類完成
經(jīng)過上面處理后
????文章1的所有關(guān)鍵詞為:[tom]?[live]?[guangzhou]?[i]?[live]?[guangzhou]
????文章2的所有關(guān)鍵詞為:[he]?[live]?[shanghai]
2)?有了關(guān)鍵詞后,我們就可以建立倒排索引了。上面的對應(yīng)關(guān)系是:“文章號”對“文章中所有關(guān)鍵詞”。倒排索引把這個關(guān)系倒過來,變成:“關(guān)鍵詞”對“擁有該關(guān)鍵詞的所有文章號”。文章1,2經(jīng)過倒排后變成
關(guān)鍵詞???文章號
guangzhou??1
he?????????2
i???????????1
live???????1,2
shanghai???2
tom?????????1
通常僅知道關(guān)鍵詞在哪些文章中出現(xiàn)還不夠,我們還需要知道關(guān)鍵詞在文章中出現(xiàn)次數(shù)和出現(xiàn)的位置,通常有兩種位置:a)字符位置,即記錄該詞是文章中第幾個字符(優(yōu)點是關(guān)鍵詞亮顯時定位快);b)關(guān)鍵詞位置,即記錄該詞是文章中第幾個關(guān)鍵詞(優(yōu)點是節(jié)約索引空間、詞組(phase)查詢快),lucene中記錄的就是這種位置。
加上“出現(xiàn)頻率”和“出現(xiàn)位置”信息后,我們的索引結(jié)構(gòu)變?yōu)椋?br />關(guān)鍵詞???文章號[出現(xiàn)頻率]???出現(xiàn)位置
guangzhou?1[2]???????????????3,6
he???????2[1]???????????????1
i?????????1[1]???????????????4
live??????1[2],2[1]???????????2,5,2
shanghai??2[1]???????????????3
tom??????1[1]???????????????1
以live 這行為例我們說明一下該結(jié)構(gòu):live在文章1中出現(xiàn)了2次,文章2中出現(xiàn)了一次,它的出現(xiàn)位置為“2,5,2”這表示什么呢?我們需要結(jié)合文章號和出現(xiàn)頻率來分析,文章1中出現(xiàn)了2次,那么“2,5”就表示live在文章1中出現(xiàn)的兩個位置,文章2中出現(xiàn)了一次,剩下的“2”就表示live是文章2中第 2個關(guān)鍵字。
????
以上就是lucene索引結(jié)構(gòu)中最核心的部分。我們注意到關(guān)鍵字是按字符順序排列的(lucene沒有使用B樹結(jié)構(gòu)),因此lucene可以用二元搜索算法快速定位關(guān)鍵詞。
????
實現(xiàn)時?lucene將上面三列分別作為詞典文件(Term?Dictionary)、頻率文件(frequencies)、位置文件 (positions)保存。其中詞典文件不僅保存有每個關(guān)鍵詞,還保留了指向頻率文件和位置文件的指針,通過指針可以找到該關(guān)鍵字的頻率信息和位置信息。
????Lucene中使用了field的概念,用于表達(dá)信息所在位置(如標(biāo)題中,文章中,url中),在建索引中,該field信息也記錄在詞典文件中,每個關(guān)鍵詞都有一個field信息(因為每個關(guān)鍵字一定屬于一個或多個field)。
???? 為了減小索引文件的大小,Lucene對索引還使用了壓縮技術(shù)。首先,對詞典文件中的關(guān)鍵詞進(jìn)行了壓縮,關(guān)鍵詞壓縮為<前綴長度,后綴>,例如:當(dāng)前詞為“阿拉伯語”,上一個詞為“阿拉伯”,那么“阿拉伯語”壓縮為<3,語>。其次大量用到的是對數(shù)字的壓縮,數(shù)字只保存與上一個值的差值(這樣可以減小數(shù)字的長度,進(jìn)而減少保存該數(shù)字需要的字節(jié)數(shù))。例如當(dāng)前文章號是16389(不壓縮要用3個字節(jié)保存),上一文章號是16382,壓縮后保存7(只用一個字節(jié))。
????
????下面我們可以通過對該索引的查詢來解釋一下為什么要建立索引。
假設(shè)要查詢單詞?“l(fā)ive”,lucene先對詞典二元查找、找到該詞,通過指向頻率文件的指針讀出所有文章號,然后返回結(jié)果。詞典通常非常小,因而,整個過程的時間是毫秒級的。
而用普通的順序匹配算法,不建索引,而是對所有文章的內(nèi)容進(jìn)行字符串匹配,這個過程將會相當(dāng)緩慢,當(dāng)文章數(shù)目很大時,時間往往是無法忍受的。
?
一、 寫jsp頁面的時候,在struts2中,用的是s標(biāo)記,先引入標(biāo)記: <%@ taglib prefix="s" uri="/struts-tags"%> 二、 struts2的標(biāo)簽和1是完全不同的。 struts2的標(biāo)簽分為兩大類:非UI標(biāo)志和UI標(biāo)志 struts1 將標(biāo)志庫按功能分成HTML、Tiles、Logic和Bean等幾部分 下面就介紹strut2的具體標(biāo)簽: 1、UI UI標(biāo)志又可以分為表單UI和非表單UI兩部分。表單UI部分基本與Struts 1.x相同,都是對HTML表單元素的包裝。不過,Struts 2.0加了幾個我們經(jīng)常在項目中用到的控件如:datepicker、doubleselect、timepicker、optiontransferselect等。因為這些標(biāo)志很多都經(jīng)常用到,而且參數(shù)也很多,要在一篇文章詳細(xì)說明并非易事。 下面主要是ui標(biāo)簽的一些用法 form: <s:form action="exampleSubmit" method="post" enctype="multipart/form-data"> <s:submit /> ??? <s:reset />
</s:form>可以上傳文件的form。 textfield: <s:textfield ??????????? label="姓名:" ??????????? name="name" ??????????? tooltip="Enter your Name here" /> datepicker: <s:datepicker ??????????? tooltip="Select Your Birthday" ??????????? label="生日" ??????????? name="birthday" /> textarea: <s:textarea ??????????? tooltip="Enter your remart" ??????????? label="備注" ??????????? name="remart" ??????????? cols="20" ??????????? rows="3"/> select: <s:select ??????????? tooltip="Choose user_type" ??????????? label="" ??????????? list="#{'free':'免費','vip':'收費'}" value="#{'free':'免費'}"?? ?????????? name="bean.user_type" ??????????? emptyOption="true" ??????????? headerKey="None" ??????????? headerValue="None"/> <s:select ??????????? tooltip="Choose user_type" ??????????? label="" ??????????? list="#{'free':'免費','vip':'收費'}" value="#{'free':'免費'}"?? ?????????? name="bean.user_type" ??????????? emptyOption="true" ??????????? headerKey="None" ??????????? headerValue="None"/> <s:select list="venderList" listKey="id" listValue="name" value="%{profile.companyName}" name="companyName" cssClass="sel_style_w_180"/>?? 挺好用的 checkboxlist: <s:checkboxlist ??????????? tooltip="Choose your Friends" ??????????? label="朋友" ??????????? list="{'Patrick', 'Jason', 'Jay', 'Toby', 'Rene'}" ??????????? name="friends"/> checkbox: ?? <s:checkbox ??????????? tooltip="Confirmed that your are Over 18" ??????????? label="年齡" ??????????? name="legalAge" ??????????? value="18"/> file: ?? <s:file ??????????? tooltip="Upload Your Picture" ??????????? label="Picture" ??????????? name="picture" /> a: <s:a href="getP.jsp">超鏈接提交</s:a> date : <s:date name="ad_end_time" format="yyyy-MM-dd"/>
2、非UI if、elseif和else 描述: 執(zhí)行基本的條件流轉(zhuǎn)。 參數(shù): 名稱必需默認(rèn)類型描述備注test是Boolean決定標(biāo)志里內(nèi)容是否顯示的表達(dá)式else標(biāo)志沒有這個參數(shù)id否Object/String用來標(biāo)識元素的id。在UI和表單中為HTML的id屬性 例子: <%@ page c %> <%@ taglib prefix="s" uri="/struts-tags" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> ??? <head> ??????? <title>Condition Flow</title> ??? </head> ??? <body> ??????? <h3>Condition Flow</h3>???????????? ??????? <!-- ??????????? 這里有點小技巧: ??????????? 本來可以用#parameters.name[0]來獲得,請求中name的值。但是,在我實現(xiàn)include例子時, ??????????? 無論我用param標(biāo)志給name賦任何值,#parameters里面不會含有任何值,所以#parameters.name也為空值。 ???????????? ??????????? 其原因為: ??????????? 當(dāng)使用include標(biāo)志時,被包含的頁面(included)里#parameters拿到的是包含頁面里的請求參數(shù)。 ???????????? ??????????? 因此,這里必須手工調(diào)用request.getParameter("name")。 ??????? --> ??? <s:iterator value="linkList" status="bean"> ?? <tr> ??? <td class="data_tab_tdcl"> ???? <s:property value="#bean.Index+1" />??? </td> ??? <td class="data_tab_tdcl"><s:property value="link_title" /></td> ??? <td class="data_tab_tdcl"><s:property value="link_url" /></td> ??? <td class="data_tab_tdcl"> ??? <s:if test="link_type == 1"> ?????????????????? 文字 ??????????????? </s:if> ??????????????? <s:elseif test="link_type == 2"> ?????????????????? 圖片 ??????????????? </s:elseif> ???????????????? <s:else> ???????????????? ----- ?????????????? </s:else>??? ??? </td>
??? ??? </body> </html> 例1 condition.jsp iterator 描述: 用于遍歷集合(java.util.Collection)或枚舉值(java.util.Iterator)。 參數(shù): 名稱必需默認(rèn)類型描述status否String如果設(shè)置此參數(shù),一個IteratorStatus的實例將會壓入每個遍歷的堆棧value否Object/String要遍歷的可枚舉的(iteratable)數(shù)據(jù)源,或者將放入新列表(List)的對象id否Object/String用來標(biāo)識元素的id。在UI和表單中為HTML的id屬性 例子: <%@ page c %> <%@ page import="java.util.List" %> <%@ page import="java.util.ArrayList" %> <%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <% ??? List list = new ArrayList(); ??? list.add("Max"); ??? list.add("Scott"); ??? list.add("Jeffry"); ??? list.add("Joe"); ??? list.add("Kelvin"); ??? request.setAttribute("names", list); %> <html> ??? <head> ??????? <title>Iterator</title> ??? </head> ??? <body> ??????? <h3>Names: </h3> ??????? <!-- ??????????? 1、此處的空property元素用于獲得當(dāng)前iterator的值 ??????????? 2、status被設(shè)成stuts,在iterator的里面就可以通過#stuts取得IteratorStatus的對象。IteratorStatus類包含當(dāng)前序號信息,如是否第一個或最后一個,是否為奇數(shù)序號。這些信息在我們做格式化的時候,顯得非常有用。 ??????? --> ??????? <ol> ??????????? <s:iterator value="#request.names" status="stuts">???????????????? ??????????????? <s:if test="#stuts.odd == true"> ??????????????????? <li>White <s:property /></li> ??????????????? </s:if> ??????????????? <s:else> ??????????????????? <li style="background-color:gray"><s:property /></li> ??????????????? </s:else> ??????????? </s:iterator> ??????? </ol> ??? </body> </html>
|
摘要: EJB
方面
?
94
、
EJB2.0
...
閱讀全文
在我裝完Wincvs之后,提示我沒有安裝python,但后來我又裝了python2.4,可Wincvs還是說配置不對.到底要怎么配置python呢?期待著您的回復(fù),謝謝你了!
???
TCL or Python are not available, shell is disabled。 |
有的網(wǎng)友說是版本的問題,我今天安裝時也是一樣,裝了好三四個版本,都一樣,最后在CSDN上找到了答案。
解決辦法:
在admin-〉Preferences->wincvs中有關(guān)于python的設(shè)置, ?
? 其中python是指你的python虛擬機(jī)的位置,一般是python2X.dll的位置。在你來說就是python24.dll的位置,一般這個文件會在你的系統(tǒng)文件中找到。 ?
? Tcl則一般會在python文件架的dlls子文件架中找到,一般名稱為tclxx.dll,在你大概就是tcl84.dll。如果是安裝python2.3版本的話,會自動找到,不需要設(shè)置。
設(shè)置好了,WinCVS輸出窗口:
Python 2.5.2 (r252:60911, Feb 21 2008, 13:11:45) [MSC v.1310 32 bit (Intel)] on win32Tk is available, Tk-macros are enabledTCL is available, shell is enabled : help (select and press enter) |
AbstractFactory模式和可擴(kuò)展性
假如要實現(xiàn)較好的可擴(kuò)展性,AbstractFactory模式確實是一件利器。如上面所說,假如要創(chuàng)建的Forum接口的不同實現(xiàn),而又不想更改代碼的話,就需要用到抽象工廠了。再Jive中,AuthorizationFactory類是一個抽象類,用來創(chuàng)建Authorization對象。這是一個抽象工廠,可以通過不同的子類來創(chuàng)建不同的Authorization對象。這個工廠的實現(xiàn)方法是:
在AuthorizationFactory中使用一個private static變量factory,用來引用具體的抽象工廠的實例:
private static AuthorizationFactory factory = null;
用一個private static的String,來指明具體的抽象工廠的子類類名:
private static String className ="com.coolservlets.forum.database.DbAuthorizationFactory";
然后是用一個private static的loadAuthorizationFactory方法來給這個factory變量賦值,生成具體的抽象工廠類:
private static void loadAuthorizationFactory() {
if (factory == null) {
synchronized(className) {
if (factory == null) {
String classNameProp = PropertyManager.getProperty(
"AuthorizationFactory.className"
);
if (classNameProp != null) {
className = classNameProp;
}
try {
Class c = Class.forName(className);
factory = (AuthorizationFactory)c.newInstance();
}
catch (Exception e) {
System.err.println("Exception loading class: " + e);
e.printStackTrace();
}
}
}
}
}
在static的getAuthorization方法返回一個Authorization的過程中,先初始化工廠類factory變量,然后用factory的createAuthorization方法來創(chuàng)建:
public static Authorization getAuthorization(String username,
String passWord) throws UnauthorizedException
{
loadAuthorizationFactory();
return factory.createAuthorization(username, password);
}
不同的子類有不同的createAuthorization方法的實現(xiàn)。比如在DbAuthorizationFactory這個AuthorizationFactory的數(shù)據(jù)庫實現(xiàn)子類中,createAuthorization方法是這樣實現(xiàn)的:
public Authorization createAuthorization(String username, String password)
throws UnauthorizedException
{
if (username == null password == null) {
throw new UnauthorizedException();
}
password = StringUtils.hash(password);
int userID = 0;
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(AUTHORIZE);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
if (!rs.next()) {
throw new UnauthorizedException();
}
userID = rs.getInt(1);
}
catch( SQLException sqle ) {
System.err.println("Exception in DbAuthorizationFactory:" + sqle);
sqle.printStackTrace();
throw new UnauthorizedException();
}
finally {
try { pstmt.close(); }
catch (Exception e) { e.printStackTrace(); }
try { con.close(); }
catch (Exception e) { e.printStackTrace(); }
}
return new DbAuthorization(userID);
}
在這個類中,可以看到抽象類和具體的子類之間的關(guān)系,它們是如何協(xié)作的,又是如何劃分抽象方法和非抽象方法的,這都是值得注重的地方。一般的,抽象方法需要子類來實現(xiàn),而抽象類中的非抽象方法應(yīng)該所有子類所能夠共享的,或者可是說,是定義在抽象方法之上的較高層的方法。這確實是一個抽象工廠的好例子!雖然實現(xiàn)的方法已經(jīng)和GOF中給出的實現(xiàn)相差較遠(yuǎn)了,但思想沒變,這兒的實現(xiàn),也確實是要巧妙的些。
還有就是靜態(tài)方法的使用,使得這個類看起來有些Singleton的意味。這使得對于AbstractFactory的創(chuàng)建變得簡單。
在AuthorizationFactory中定義的其它方法,涉及到具體的如何創(chuàng)建Authorization,都是作為abstract方法出現(xiàn),具體實現(xiàn)留給子類來完成。
這樣,在需要生成一個Authorization的時候,只需要調(diào)用AuthorizationFactory的靜態(tài)方法getAuthorization就可以了,由子類實現(xiàn)了具體的細(xì)節(jié)。
其它的,如同上面講到的,在創(chuàng)建Forum的時候用的ForumFactory,具有同上面一樣的實現(xiàn),這就是模式之所以稱為模式的所在了。
資料引用:http://www.knowsky.com/365144.html
?
題目: IOC 后臺機(jī)制學(xué)習(xí)
給定:
配置文件 config.txt, 文件內(nèi)容
className = test.JavaBean1
field = username
value = ABC
該文件中的三個值會隨時可能變化, 唯一不變的是 className 指定的都是一個 JavaBean(為了簡化, 我們假定里面已經(jīng)有一個 username 屬性, 例如:
class JavaBeanxxxx {
?? ?private String username;
?? ?public String getUsername() {
?? ??? ?return username;
?? ?}
?? ?public void setUsername(String uname) {
?? ??? ?this.username = uname;
?? ?}
}
要求: 寫一段代碼, 讀取配置文件 config.txt, 然后實現(xiàn)把 className 指定的 JavaBean 類加載(注意這個類名是可以修改的, 可配置的), 然后生成一個實例,
并把配置文件中field字段指定的值作為這個實例的屬性名(這里是username)所對應(yīng)的值設(shè)置為 ABC(字符串), 并且要讀出最后設(shè)置的值.
此題已經(jīng)被 TigerTian 解答出來, 歡迎學(xué)習(xí), 也感謝 TigerTian:
package?com.gcoresoft.ioc;
import?java.io.*;
import?java.lang.reflect.*;
import?java.util.*;
import?java.beans.*;
public?class?IOCStudy?{
????
????//Load?the?properties?file
????private?Properties?prop=new?Properties();
????
????public?void?loadPropFile(String?filename)
????{
????????try
????????{
????????????FileInputStream?fin=new?FileInputStream(filename);
????????????prop.load(fin);
????????????fin.close();
????????}catch(Exception?e){
????????????System.out.println(e.toString());
????????}
????}
????
????private?String?getValueByName(String?Name)
????{
????????return?prop.getProperty(Name);
????}
????
????public?static?void?main(String[]?args)
????{
????????IOCStudy?ioc=new?IOCStudy();
????????ioc.loadPropFile("E:\\Work\\GetInIOC\\src\\com\\gcoresoft\\ioc\\Config.txt");
????????try
????????{
????????????Class?bean=Class.forName(ioc.getValueByName("className"));
????????????try?{
????????????????java.beans.BeanInfo?info=java.beans.Introspector.getBeanInfo(bean);
????????????????java.beans.PropertyDescriptor?pd[]=info.getPropertyDescriptors();
????????????????try?{
????????????????????Method?mSet=null,mRead=null;
????????????????????Object?obj=bean.newInstance();
????????????????????for(int?i=0;i<pd.length;i++)
????????????????????????if(pd[i].getName().equalsIgnoreCase(ioc.getValueByName("field")))
????????????????????????{
????????????????????????????mSet=pd[i].getWriteMethod();
????????????????????????????mRead=pd[i].getReadMethod();????????????????????????????
????????????????????????}
????????????????????try?{
????????????????????????mSet.invoke(obj,?ioc.getValueByName("value"));
????????????????????????String?str=(String)mRead.invoke(obj,?null);
????????????????????????System.out.println(str);
????????????????????}?catch?(IllegalArgumentException?e)?{
????????????????????????//?TODO?Auto-generated?catch?block
????????????????????????e.printStackTrace();
????????????????????}?catch?(InvocationTargetException?e)?{
????????????????????????//?TODO?Auto-generated?catch?block
????????????????????????e.printStackTrace();
????????????????????}
????????????????}?catch?(InstantiationException?e)?{
????????????????????//?TODO?Auto-generated?catch?block
????????????????????e.printStackTrace();
????????????????}?catch?(IllegalAccessException?e)?{
????????????????????//?TODO?Auto-generated?catch?block
????????????????????e.printStackTrace();
????????????????}
????????????????
????????????}?catch?(IntrospectionException?e)?{
????????????????//?TODO?Auto-generated?catch?block
????????????????e.printStackTrace();
????????????}
????????????
????????????
????????}catch(ClassNotFoundException?e){
????????????System.out.println(e.toString());
????????}
????????
????}
????
}
引言
在J2EE的整個發(fā)展歷程中,現(xiàn)在正是一個非常時刻。從很多方面來說,J2EE都是一個偉大的成功:它成功地在從前沒有標(biāo)準(zhǔn)的地方建立了標(biāo)準(zhǔn);大大提升了企業(yè)級軟件的開放程度,并且得到了整個行業(yè)和開發(fā)者的廣泛認(rèn)可。然而,J2EE在一些方面已經(jīng)開始捉襟見肘。J2EE應(yīng)用開發(fā)的成本通常很高。J2EE應(yīng)用項目至少和從前的非J2EE項目一樣容易失敗——如果不是更容易失敗的話。這樣的失敗率高得讓人難以接受。在這樣的失敗率之下,軟件開發(fā)幾乎變成了碰運氣。而在J2EE遭遇失敗的場景中,EJB通常都扮演著重要的角色。因此,J2EE社群不斷地向著更簡單的解決方案、更少使用EJB的方向發(fā)展
[1]。然而,每個應(yīng)用程序都需要一些基礎(chǔ)設(shè)施,拒絕使用EJB并不意味著拒絕EJB所采用的基礎(chǔ)設(shè)施解決方案。那么,如何利用現(xiàn)有的框架來提供這些基礎(chǔ)設(shè)施服務(wù)呢,伴隨著這個問題的提出,一個輕量級的J2EE解決方案出現(xiàn)了,這就是Spring Framework。
Spring是為簡化企業(yè)級系統(tǒng)開發(fā)而誕生的,Spring框架為J2EE應(yīng)用常見的問題提供了簡單、有效的解決方案,使用Spring,你可以用簡單的POJO(Plain Old Java Object)來實現(xiàn)那些以前只有EJB才能實現(xiàn)的功能。這樣不只是能簡化服務(wù)器端開發(fā),任何Java系統(tǒng)開發(fā)都能從Spring的簡單、可測試和松耦合特征中受益。可以簡單的說,Spring是一個輕量級的反向控制(IoC)和面向切面編程(AOP)容器框架
[3]。Spring IoC,借助于依賴注入設(shè)計模式,使得開發(fā)者不用理會對象自身的生命周期及其關(guān)系,而且能夠改善開發(fā)者對J2EE模式的使用;Spring AOP,借助于Spring實現(xiàn)的攔截器,開發(fā)者能夠?qū)崿F(xiàn)以聲明的方式使用企業(yè)級服務(wù),比如安全性服務(wù)、事務(wù)服務(wù)等。Spring IoC和 Spring ; AOP組合,一起形成了Spring,這樣一個有機(jī)整體,使得構(gòu)建輕量級的J2EE架構(gòu)成為可能,而且事實證明,非常有效。
沒有Spring IoC的Spring AOP是不完善的,沒有Spring AOP的Spring IoC是不健壯的。本文是以Spring架構(gòu)的成功的實際商務(wù)系統(tǒng)項目為背景,闡述了反向控制原理和面向切面的編程技術(shù)在Spring框架中的應(yīng)用,同時抽取適量代碼示意具體應(yīng)用,并和傳統(tǒng)開發(fā)模式進(jìn)行對比,展示了Spring framework的簡單,高效,可維護(hù)等優(yōu)點。
1、Spring IoC 1.1 反向控制原理 反向控制是Spring框架的核心。但是,反向控制是什么意思?到底控制的什么方面被反向了呢?2004年美國專家Martin Fowler發(fā)表了一篇論文《Inversion of Control Containers and the Dependency Injection pattern》闡述了這個問題,他總結(jié)說是獲得依賴對象的方式反向了,根據(jù)這個啟示,他還為反向控制提出了一個更貼切的名字:Dependency Injection(DI 依賴注入)。
通常,應(yīng)用代碼需要告知容器或框架,讓它們找到自身所需要的類,然后再由應(yīng)用代碼創(chuàng)建待使用的對象實例。因此,應(yīng)用代碼在使用實例之前,需要創(chuàng)建對象實例。然而,IoC模式中,創(chuàng)建對象實例的任務(wù)交給IoC容器或框架(實現(xiàn)了IoC設(shè)計模式的框架也被稱為IoC容器),使得應(yīng)用代碼只需要直接使用實例,這就是IoC。相對IoC 而言,“依賴注入”的確更加準(zhǔn)確的描述了這種設(shè)計理念。所謂依賴注入,即組件之間的依賴關(guān)系由容器在運行期決定,形象的來說,即由容器動態(tài)的將某種依賴關(guān)系注入到組件之中。
1.2 IoC在Spring中的實現(xiàn)
任何重要的系統(tǒng)都需要至少兩個相互合作的類來完成業(yè)務(wù)邏輯。通常,每個對象都要自己負(fù)責(zé)得到它的合作(依賴)對象。你會發(fā)現(xiàn),這樣會導(dǎo)致代碼耦合度高而且難于測試。使用IoC,對象的依賴都是在對象創(chuàng)建時由負(fù)責(zé)協(xié)調(diào)系統(tǒng)中各個對象的外部實體提供的,這樣使軟件組件松散連接成為可能。下面示意了Spring IoC 應(yīng)用,步驟如下:
(1)定義Action接口,并為其定義一個execute方法,以完成目標(biāo)邏輯。多年前,GoF在《Design Pattern:Elements of Reusable Object-Oriented Software》一書中提出“Programming to an Interface,not an implementation”的原則,這里首先將業(yè)務(wù)對象抽象成接口,正是為了實施這個原則。
(2)類UpperAction實現(xiàn)Action接口,在此類中,定義一個String型的域message,并提供相應(yīng)的setter和getter方法,實現(xiàn)的execute方法如下:
public String execute (String str) { return (getMessage () + str).toUpperCase () ; } |
(3)編寫Spring配置文件(bean.xml)
<beans> <bean id="TheAction" class="net.chen.spring.qs.UpperAction"> <property name="message"> <value>HeLLo</value> </property> </bean> </beans> |
(4)測試代碼
public void testQuickStart () { ApplicationContext ctx=new FileSystemXmlApplicationContext ("bean.xml"); Action a= (Action) ctx.getBean ("TheAction"); System.out.println (a. execute ("Rod Johnson")); } |
上面的測試代碼中,我們根據(jù)"bean.xml"創(chuàng)建了一個ApplicationContext實例,并從此實例中獲取我們所需的Action實現(xiàn),運行測試代碼,我們看到控制臺輸出:
仔細(xì)觀察一下上面的代碼,可以看到:
(1)我們的組件并不需要實現(xiàn)框架指定的接口,因此可以輕松的將組件從Spring中脫離,甚至不需要任何修改,這在基于EJB框架實現(xiàn)的應(yīng)用中是難以想象的。
(2)組件間的依賴關(guān)系減少,極大改善了代碼的可重用性。Spring的依賴注入機(jī)制,可以在運行期為組件配置所需資源,而無需在編寫組件代碼時就加以指定,從而在相當(dāng)程度上降低了組件之間的耦合。
Spring給我們帶來了如此這般的好處,那么,反過來,讓我們試想一下,如果不使用Spring框架,回到我們傳統(tǒng)的編碼模式,情況會是怎樣呢?
首先,我們必須編寫一個配置文件讀取類,以實現(xiàn)Message屬性的可配置化。
其次,得有一個Factory模式的實現(xiàn),并結(jié)合配置文件的讀寫完成Action的動態(tài)加載。于是,我們實現(xiàn)了一個ActionFactory來實現(xiàn)這個功能:
public class ActionFactory { public static Action getAction (String actionName) {Properties pro = new Properties (); try { pro.load (new FileInputStream ("config.properties")); String actionImplName =(String)pro.get(actionName); String actionMessage =(String) pro.get (actionName+"_msg"); Object obj =Class.forName (actionImplName).newInstance (); BeanUtils.setProperty(obj,"message",actionMessage); return (Action) obj; } catch (FileNotFoundException e) { …… } } |
配置文件則采用properties文件形式如下所示:
TheAction=net.chen.spring.qs.UpperAction TheAction_msg=HeLLo |
測試代碼也作相應(yīng)修改。現(xiàn)在不論實現(xiàn)的好壞,總之通過上面新增的多行代碼,終于實現(xiàn)了類似的功能。如果現(xiàn)在有了一個新的需求,這樣這個ActionFactory每次都新建一個類的實例,顯然這對系統(tǒng)性能不利,考慮到我們的兩個Action都是線程安全的,修改一下ActionFactory,保持系統(tǒng)中只有一個Action實例供其它線程調(diào)用。另外Action對象創(chuàng)建后,需要做一些初始化工作。修改一下ActionFactory,使其在創(chuàng)建Action實例之后,隨即就調(diào)用Action.init方法執(zhí)行初始化。Action的處理這樣就差不多了。下面我們來看看另外一個Factory
……
往往這些系統(tǒng)開發(fā)中最常見的需求,會導(dǎo)致我們的代碼迅速膨脹,而Spring IoC的出現(xiàn),則大大緩解了這樣的窘境。通過以上實例,可以看出,Spring IoC為我們提供了如下幾方面的優(yōu)勢:
(1)應(yīng)用組件不需要在運行時尋找其協(xié)作者,因此更易于開發(fā)和編寫應(yīng)用;
(2)由于借助于IoC容器管理組件的依賴關(guān)系,使得應(yīng)用的單元測試和集成測試更利于展開;
(3)通常,在借助于IoC容器關(guān)系業(yè)務(wù)對象的前提下,很少需要使用具體IoC容器提供的API,這使得集成現(xiàn)有的遺留應(yīng)用成為可能。
因此,通過使用IoC能夠降低組件之間的耦合度,最終,能夠提高類的重用性,利于測試,而且更利于整個產(chǎn)品或系統(tǒng)集成和配置。
2、Spring AOP
2.1 面向切面編程基礎(chǔ)
通常,系統(tǒng)由很多組件組成,每個組件負(fù)責(zé)一部分功能,然而,這些組件也經(jīng)常帶有一些除了核心功能之外的附帶功能 。系統(tǒng)服務(wù)如日志、事務(wù)管理和安全經(jīng)常融入到一些其他功能模塊中。這些系統(tǒng)服務(wù)通常叫做交叉業(yè)務(wù),這是因為它們總是分布在系統(tǒng)的很多組件中。通過將這些業(yè)務(wù)分布在多個組件中,給我們的代碼引入了雙重復(fù)雜性。
(1) 實現(xiàn)系統(tǒng)級業(yè)務(wù)的代碼在多個組件中復(fù)制。這意味著如果你要改變這些業(yè)務(wù)邏輯,你就必須到各個模塊去修改。就算把這些業(yè)務(wù)抽象成一個獨立模塊,其它模塊只是調(diào)用它的一個方法,但是這個方法調(diào)用也還是分布在很多地方。
(2) 組件會因為那些與自己核心業(yè)務(wù)無關(guān)的代碼變得雜亂。一個向地址錄中添加條目的方法應(yīng)該只關(guān)心如何添加地址,而不是關(guān)心它是不是安全或支持事務(wù)的。
此時,我們該怎么辦呢?這正是AOP用得著的地方。AOP幫助我們將這些服務(wù)模塊化,并把它們聲明式地應(yīng)用在需要它們的地方,使得這些組件更加專注于自身業(yè)務(wù),完全不知道其它涉及到的系統(tǒng)服務(wù)。
這里的概念切面,就是我們要實現(xiàn)的交叉功能,是應(yīng)用系統(tǒng)模塊化的一個方面或領(lǐng)域。切面的最常見例子就是日志記錄。日志記錄在系統(tǒng)中到處需要用到,利用繼承來重用日志模塊是不合適的,這樣,就可以創(chuàng)建一個日志記錄切面,并且使用AOP在系統(tǒng)中應(yīng)用。下圖展示了切面應(yīng)用方式

圖表 1 應(yīng)用切面
其中,通知Advice是切面的實際實現(xiàn)。連接點Joinpoint是應(yīng)用程序執(zhí)行過程中插入切面的地點,這個地點可以是方法調(diào)用,異常拋出,甚至可以是要修改的字段,切面代碼在這些地方插入到你的應(yīng)用流程中,添加新的行為。切入點Pointcut定義了Advice應(yīng)該應(yīng)用在那些連接點,通常通過指定類名和方法名,或者匹配類名和方法名式樣的正則表達(dá)式來指定切入點。
2.2 AOP在Spring中的實現(xiàn)
基于AOP,業(yè)界存在各種各樣的AOP實現(xiàn),比如,JBoss AOP、Spring AOP、AspectJ、Aspect Werkz等。各自實現(xiàn)的功能也不一樣。AOP實現(xiàn)的強(qiáng)弱在很大程度上取決于連接點模型。目前,Spring只支持方法級的連接點。這和一些其他AOP框架不一樣,如AspectJ和JBoss,它們還提供了屬性接入點,這樣可以防止你創(chuàng)建特別細(xì)致的通知,如對更新對象屬性值進(jìn)行攔截。然而,由于Spring關(guān)注于提供一個實現(xiàn)J2EE服務(wù)的框架,所以方法攔截可以滿足大部分要求,而且Spring的觀點是屬性攔截破壞了封裝,讓Advice觸發(fā)在屬性值改變而不是方法調(diào)用上無疑是破壞了這個概念。
Spring的AOP框架的關(guān)鍵點如下:
(1)Spring實現(xiàn)了AOP聯(lián)盟接口。在Spring AOP中,存在如下幾種通知(Advice)類型
Before通知:在目標(biāo)方法被調(diào)用前調(diào)用,涉及接口org.springframework.aop.MethodBeforeAdvice;
After通知:在目標(biāo)方法被調(diào)用后調(diào)用,涉及接口為org.springframework.aop.AfterReturningAdvice;
Throws通知:目標(biāo)方法拋出異常時調(diào)用,涉及接口org.springframework.aop.MethodBeforeAdvice;
Around通知:攔截對目標(biāo)對象方法調(diào)用,涉及接口為org.aopalliance.intercept.MethodInterceptor。
(2)用java編寫Spring通知,并在Spring的配置文件中,定義在什么地方應(yīng)用通知的切入點。
(3)Spring的運行時通知對象。代理Bean只有在第一次被應(yīng)用系統(tǒng)需要的時候才被創(chuàng)建。如果你使用的是ApplicationContext,代理對象在BeanFactory載入所有Bean的時候被創(chuàng)建。Spring有兩種代理創(chuàng)建方式。如果目標(biāo)對象實現(xiàn)了一個或多個接口暴露的方法,Spring將使用JDK的java.lang.reflect.Proxy類創(chuàng)建代理。這個類讓Spring動態(tài)產(chǎn)生一個新的類,它實現(xiàn)所需的接口,織入了通知,并且代理對目標(biāo)對象的所有請求。如果目標(biāo)對象沒有實現(xiàn)任何接口,Spring使用CGLIB庫生成目標(biāo)對象的子類。在創(chuàng)建這個子類的時候,Spring將通知織入,并且將對目標(biāo)對象的調(diào)用委托給這個子類。此時,需要將Spring發(fā)行包lib/cglib目錄下的JAR文件發(fā)布到應(yīng)用系統(tǒng)中。
2.3 Spring AOP的優(yōu)勢
借助于Spring AOP,Spring IoC能夠很方便的使用到非常健壯、靈活的企業(yè)級服務(wù),是因為Spring AOP能夠提供如下幾方面的優(yōu)勢:
(1)允許開發(fā)者使用聲明式企業(yè)服務(wù),比如事務(wù)服務(wù)、安全性服務(wù);EJB開發(fā)者都知道,EJB組件能夠使用J2EE容器提供的聲明式服務(wù),但是這些服務(wù)要借助于EJB容器,而Spring AOP卻不需要EJB容器,借助于Spring的事務(wù)抽象框架就可以這些服務(wù)。
(2)開發(fā)者可以開發(fā)滿足業(yè)務(wù)需求的自定義切面;
(3)開發(fā)Spring AOP Advice很方便。因為這些AOP Advice僅是POJO類,借助于Spring提供的ProxyFactoryBean,能夠快速的搭建Spring AOP Advice。
3、結(jié)語 本文詳細(xì)闡述了Spring背后的IoC原理和AOP技術(shù),以實際成功項目為背景,抽取簡短片斷,展示了Spring架構(gòu)J2EE應(yīng)用系統(tǒng)的原理。Spring IoC借助于依賴注入機(jī)制,減輕了組件之間的依賴關(guān)系,同時也大大提高了組件的可移植性,組件得到了更多的重用機(jī)會。借助于Spring AOP,使得開發(fā)者能聲明式、基于元數(shù)據(jù)訪問企業(yè)級服務(wù),AOP合理補(bǔ)充了OOP技術(shù),Spring AOP合理地補(bǔ)充了Spring IoC容器。Spring IoC與Spring AOP組合,使得Spring成為成功的J2EE架構(gòu)框架,并能與標(biāo)準(zhǔn)的EJB等標(biāo)準(zhǔn)對抗,EJB不再是必需品。Spring已經(jīng)沖入了J2EE的核心,將引領(lǐng)整個J2EE開發(fā)、架構(gòu)的方向。
著名的EJB領(lǐng)域頂尖的專家Richard Monson-Haefel在其個人網(wǎng)站:www.EJBNow.com中極力推薦的GoF的《設(shè)計模式》,原文如下:
Design Patterns
Most developers claim to experience an epiphany reading this book. If you've never read the Design Patterns book then you have suffered a very serious gap in your programming education that should be remedied immediately.
翻譯: 很多程序員在讀完這本書,宣布自己相當(dāng)于經(jīng)歷了一次"主顯節(jié)"(紀(jì)念那穌降生和受洗的雙重節(jié)日),如果你從來沒有讀過這本書,你會在你的程序教育生涯里存在一個嚴(yán)重裂溝,所以你應(yīng)該立即挽救彌補(bǔ)!
可以這么說:GoF設(shè)計模式是程序員真正掌握面向?qū)ο蠛诵乃枷氲谋匦拚n。雖然你可能已經(jīng)通過了SUN的很多令人炫目的技術(shù)認(rèn)證,但是如果你沒有學(xué)習(xí)掌握GoF設(shè)計模式,只能說明你還是一個技工。
在瀏覽《Thingking in Java》(第一版)時,你是不是覺得好象這還是一本Java基礎(chǔ)語言書籍?但又不純粹是,因為這本書的作者將面向?qū)ο蟮乃枷肭擅畹娜诤显贘ava的具體技術(shù)上,潛移默化的讓你感覺到了一種新的語言和新的思想方式的誕生。
但是讀完這本書,你對書中這些蘊含的思想也許需要一種更明晰更系統(tǒng)更透徹的了解和掌握,那么你就需要研讀GoF的《設(shè)計模式》了。
《Thingking in Java》(第一版中文)是這樣描述設(shè)計模式的:他在由Gamma, Helm和Johnson Vlissides簡稱Gang of Four(四人幫),縮寫GoF編著的《Design Patterns》一書中被定義成一個“里程碑”。事實上,那本書現(xiàn)在已成為幾乎所有OOP(面向?qū)ο蟪绦蛟O(shè)計)程序員都必備的參考書。(在國外是如此)。
GoF的《設(shè)計模式》是所有面向?qū)ο笳Z言(C++ Java C#)的基礎(chǔ),只不過不同的語言將之實現(xiàn)得更方便地使用。
GOF的設(shè)計模式是一座"橋"
就Java語言體系來說,GOF的設(shè)計模式是Java基礎(chǔ)知識和J2EE框架知識之間一座隱性的"橋"。
會Java的人越來越多,但是一直徘徊在語言層次的程序員不在少數(shù),真正掌握J(rèn)ava中接口或抽象類的應(yīng)用不是很多,大家經(jīng)常以那些技術(shù)只適合大型項目為由,避開或忽略它們,實際中,Java的接口或抽象類是真正體現(xiàn)Java思想的核心所在,這些你都將在GoF的設(shè)計模式里領(lǐng)略到它們變幻無窮的魔力。
GoF的設(shè)計模式表面上好象也是一種具體的"技術(shù)",而且新的設(shè)計模式不斷在出現(xiàn),設(shè)計模式自有其自己的發(fā)展軌道,而這些好象和J2EE .Net等技術(shù)也無關(guān)!
實際上,GoF的設(shè)計模式并不是一種具體"技術(shù)",它講述的是思想,它不僅僅展示了接口或抽象類在實際案例中的靈活應(yīng)用和智慧,讓你能夠真正掌握接口或抽象類的應(yīng)用,從而在原來的Java語言基礎(chǔ)上躍進(jìn)一步,更重要的是,GoF的設(shè)計模式反復(fù)向你強(qiáng)調(diào)一個宗旨:要讓你的程序盡可能的可重用。
這其實在向一個極限挑戰(zhàn):軟件需求變幻無窮,計劃沒有變化快,但是我們還是要尋找出不變的東西,并將它和變化的東西分離開來,這需要非常的智慧和經(jīng)驗。
而GoF的設(shè)計模式是在這方面開始探索的一塊里程碑。
J2EE等屬于一種框架軟件,什么是框架軟件?它不同于我們以前接觸的Java API等,那些屬于Toolkist(工具箱),它不再被動的被使用,被調(diào)用,而是深刻的介入到一個領(lǐng)域中去,J2EE等框架軟件設(shè)計的目的是將一個領(lǐng)域中不變的東西先定義好,比如整體結(jié)構(gòu)和一些主要職責(zé)(如數(shù)據(jù)庫操作 事務(wù)跟蹤 安全等),剩余的就是變化的東西,針對這個領(lǐng)域中具體應(yīng)用產(chǎn)生的具體不同的變化需求,而這些變化東西就是J2EE程序員所要做的。
由此可見,設(shè)計模式和J2EE在思想和動機(jī)上是一脈相承,只不過
1.設(shè)計模式更抽象,J2EE是具體的產(chǎn)品代碼,我們可以接觸到,而設(shè)計模式在對每個應(yīng)用時才會產(chǎn)生具體代碼。
2.設(shè)計模式是比J2EE等框架軟件更小的體系結(jié)構(gòu),J2EE中許多具體程序都是應(yīng)用設(shè)計模式來完成的,當(dāng)你深入到J2EE的內(nèi)部代碼研究時,這點尤其明顯,因此,如果你不具備設(shè)計模式的基礎(chǔ)知識(GoF的設(shè)計模式),你很難快速的理解J2EE。不能理解J2EE,如何能靈活應(yīng)用?
3.J2EE只是適合企業(yè)計算應(yīng)用的框架軟件,但是GoF的設(shè)計模式幾乎可以用于任何應(yīng)用!因此GoF的設(shè)計模式應(yīng)該是J2EE的重要理論基礎(chǔ)之一。
所以說,GoF的設(shè)計模式是Java基礎(chǔ)知識和J2EE框架知識之間一座隱性的"橋"。為什么說隱性的?
GOF的設(shè)計模式是一座隱性的"橋"
因為很多人沒有注意到這點,學(xué)完Java基礎(chǔ)語言就直接去學(xué)J2EE,有的甚至鴨子趕架,直接使用起Weblogic等具體J2EE軟件,一段時間下來,發(fā)現(xiàn)不過如此,挺簡單好用,但是你真正理解J2EE了嗎?你在具體案例中的應(yīng)用是否也是在延伸J2EE的思想?
如果你不能很好的延伸J2EE的思想,那你豈非是大炮轟蚊子,認(rèn)識到J2EE不是適合所有場合的人至少是明智的,但我們更需要將J2EE用對地方,那么只有理解J2EE此類框架軟件的精髓,那么你才能真正靈活應(yīng)用Java解決你的問題,甚至構(gòu)架出你自己企業(yè)的框架來。(我們不能總是使用別人設(shè)定好的框架,為什么不能有我們自己的框架?)
因此,首先你必須掌握GoF的設(shè)計模式。雖然它是隱性,但不是可以越過的。
?
關(guān)于本站“設(shè)計模式”
Java提供了豐富的API,同時又有強(qiáng)大的數(shù)據(jù)庫系統(tǒng)作底層支持,那么我們的編程似乎變成了類似積木的簡單"拼湊"和調(diào)用,甚至有人提倡"藍(lán)領(lǐng)程序員",這些都是對現(xiàn)代編程技術(shù)的不了解所至.
在真正可復(fù)用的面向?qū)ο缶幊讨?GoF的《設(shè)計模式》為我們提供了一套可復(fù)用的面向?qū)ο蠹夹g(shù),再配合Refactoring(重構(gòu)方法),所以很少存在簡單重復(fù)的工作,加上Java代碼的精煉性和面向?qū)ο蠹儩嵭?設(shè)計模式是java的靈魂),編程工作將變成一個讓你時刻體驗創(chuàng)造快感的激動人心的過程.
為能和大家能共同探討"設(shè)計模式",我將自己在學(xué)習(xí)中的心得寫下來,只是想幫助更多人更容易理解GoF的《設(shè)計模式》。由于原著都是以C++為例, 以Java為例的設(shè)計模式基本又都以圖形應(yīng)用為例,而我們更關(guān)心Java在中間件等服務(wù)器方面的應(yīng)用,因此,本站所有實例都是非圖形應(yīng)用,并且順帶剖析Jive論壇系統(tǒng).同時為降低理解難度,盡量避免使用UML圖.
如果你有一定的面向?qū)ο缶幊探?jīng)驗,你會發(fā)現(xiàn)其中某些設(shè)計模式你已經(jīng)無意識的使用過了;如果你是一個新手,那么從開始就培養(yǎng)自己良好的編程習(xí)慣(讓你的的程序使用通用的模式,便于他人理解;讓你自己減少重復(fù)性的編程工作),這無疑是成為一個優(yōu)秀程序員的必備條件.
整個設(shè)計模式貫穿一個原理:面對接口編程,而不是面對實現(xiàn).目標(biāo)原則是:降低耦合,增強(qiáng)靈活性.
所謂MIS(管理信息系統(tǒng)--Management Information System)系統(tǒng),是一個由人、計算機(jī)及其他外圍設(shè)備等組成的能進(jìn)行信息的收集、傳遞、存貯、加工、維護(hù)和使用的系統(tǒng)。它是一門新興的科學(xué),其主要任務(wù)是最大限度的利用現(xiàn)代計算機(jī)及網(wǎng)絡(luò)通訊技術(shù)加強(qiáng)企業(yè)的信息管理,通過對企業(yè)擁有的人力、物力、財力、設(shè)備、技術(shù)等資源的調(diào)查了解,建立正確的數(shù)據(jù),加工處理并編制成各種信息資料及時提供給管理人員,以便進(jìn)行正確的決策,不斷提高企業(yè)的管理水平和經(jīng)濟(jì)效益。目前,企業(yè)的計算機(jī)網(wǎng)絡(luò)已成為企業(yè)進(jìn)行技術(shù)改造及提高企業(yè)管理水平的重要手段。隨著我國與世界信息高速公路的接軌,企業(yè)通過計算機(jī)網(wǎng)絡(luò)獲得信息必將為企業(yè)帶來巨大的經(jīng)濟(jì)效益和社會效益,企業(yè)的辦公及管理都將朝著高效、快速、無紙化的方向發(fā)展。MIS系統(tǒng)通常用于系統(tǒng)決策,例如,可以利用MIS系統(tǒng)找出目前迫切需要解決的問題,并將信息及時反饋給上層管理人員,使他們了解當(dāng)前工作發(fā)展的進(jìn)展或不足。換句話說,MIS系統(tǒng)的最終目的是使管理人員及時了解公司現(xiàn)狀,把握將來的發(fā)展路徑。
一個完整的MIS應(yīng)包括:輔助決策系統(tǒng)(DSS)、工業(yè)控制系統(tǒng)(IPC)、辦公自動化系統(tǒng)(OA)以及數(shù)據(jù)庫、模型庫、方法庫、知識庫和與上級機(jī)關(guān)及外界交換信息的接口。其中,特別是辦公自動化系統(tǒng)(OA)、與上級機(jī)關(guān)及外界交換信息等都離不開Intranet的應(yīng)用。可以這樣說,現(xiàn)代企業(yè)MIS不能沒有 Intranet,但I(xiàn)ntranet的建立又必須依賴于MIS的體系結(jié)構(gòu)和軟硬件環(huán)境。
傳統(tǒng)的MIS系統(tǒng)的核心是CS (Client/Server——客戶端/服務(wù)器)架構(gòu),而基于Internet的MIS系統(tǒng)的核心是BS(Browser/Server——瀏覽器/服務(wù)器)架構(gòu)。BS架構(gòu)比起CS架構(gòu)有著很大的優(yōu)越性,傳統(tǒng)的MIS系統(tǒng)依賴于專門的操作環(huán)境,這意味著操作者的活動空間受到極大限制;而BS架構(gòu)則不需要專門的操作環(huán)境,在任何地方,只要能上網(wǎng),就能夠操作MIS系統(tǒng),這其中的優(yōu)劣差別是不言而喻的。
基于Internet上的 MIS系統(tǒng)是對傳統(tǒng)MIS系統(tǒng)概念上的擴(kuò)展,它不僅可以用于高層決策,而且可以用于進(jìn)行普通的商務(wù)管理。通過用戶的具名登錄(或匿名登錄),以及相應(yīng)的權(quán)限控制,可以實現(xiàn)在遠(yuǎn)端對系統(tǒng)的瀏覽、查詢、控制和審閱。隨著Internet的擴(kuò)展,現(xiàn)有的公司和學(xué)校不再局限于物理的有形的真實的地域,網(wǎng)絡(luò)本身成為事實上發(fā)展的空間。基于Internet上的MIS系統(tǒng),彌補(bǔ)了傳統(tǒng)MIS系統(tǒng)的不足,充分體現(xiàn)了現(xiàn)代網(wǎng)絡(luò)時代的特點。隨著Internet技術(shù)的高速發(fā)展,因特網(wǎng)必將成為人類新社會的技術(shù)基石。基于Internet的MIS系統(tǒng)必將成為網(wǎng)絡(luò)時代的新一代管理信息系統(tǒng),前景極為樂觀
國外開發(fā)者博客中有一篇有趣的文章,將程序員按水平像軟件版本號那樣劃分為不同的版本。相對于在招聘時分為初級,中級,高級程序員,直接表明需要某種語言N版本的程序員或許更方便直接。根據(jù)作者的觀點,可將WEB開發(fā)者大致分為以下幾個版本:
Alpha:閱讀過一些專業(yè)書籍,大多數(shù)能用Dreamweaver或者FrontPage幫朋友制作一些Web頁面。但在他們熟練掌握HTML代碼以前,你大概不會雇傭他們成為職業(yè)的WEB制作人員。
Beta:已經(jīng)比較擅長整合站點頁面了,在HTML技巧方面也有一定造詣,但還是用Tables來制作頁面,不了解CSS,在面對動態(tài)頁面或數(shù)據(jù)庫連接時還是底氣不足。
Pre Version 1 (0.1):比Beta版的開發(fā)者水平要高。熟悉HTML,開始了解CSS是如何運作的,懂一點JavaScript,但還是基于業(yè)余水準(zhǔn),逐步開始關(guān)心動態(tài)站點搭建和數(shù)據(jù)庫連接的知識。這個版本的WEB開發(fā)人員還遠(yuǎn)不能成為雇主眼中的香餑餑。
1.0: 能夠基本把控整個站點開發(fā),針對每個問題盡可能的找到最直接的解決辦法。但對可測性,可擴(kuò)展性以及在不同(層)框架下如何選擇最合適的WEB設(shè)計工具尚無概念。這個版本的WEB開發(fā)者有良好的技術(shù)基礎(chǔ),需要有進(jìn)一步的幫助和指導(dǎo)。

2.0:懂面向?qū)ο蟮木幊陶Z言,理解分層開發(fā)的必要性,關(guān)注代碼分離,對問題尋找更完美的解決方法,偶然也會考慮設(shè)計模式的問題,但對此仍然概念不清。屬于優(yōu)秀的初級開發(fā)者,能完成較松散的代碼開發(fā)(相對大型嚴(yán)謹(jǐn)?shù)恼军c開發(fā)而言),在面對較復(fù)雜問題尋找解決辦法時需要周邊人的幫助。
3.0:開始較為深入的理解面向?qū)ο缶幊毯驮O(shè)計模式,了解他們的用途,當(dāng)看到好的設(shè)計模式時能看透其本質(zhì),逐步關(guān)注分層的架構(gòu)解決辦法和可測試性。理解不同的開發(fā)語言并能說出他們的異同(例如各自的優(yōu)勢)。屬于優(yōu)秀的中級別開發(fā)者,雇主也確信他們最終能找到問題的解決辦法,這個版本的人可以給1.0和2.0的開發(fā)者以指導(dǎo)。但他們對架構(gòu)的理解仍然不夠清晰,值得一提的是,只要給予一些指導(dǎo),他們能很快理解并熟記做出的決定,以及選定方案的優(yōu)勢所在。
4.0:
理解模式,重視用戶的反饋。著手研究方法論,架構(gòu)設(shè)計和軟件開發(fā)的最佳入口。頭腦中已經(jīng)形成了超越開發(fā)語言,技術(shù)架構(gòu)的整體方案,可根據(jù)需求解構(gòu)程序。能從理論的角度,不同模式如何融合成最佳形態(tài),將多種X-驅(qū)動的模式應(yīng)用到不同的方案中。是精通多語言的高手,理解不同系統(tǒng)和方法論的細(xì)微差別,屬于高級程序員。這個級別的人能夠輕易的輔導(dǎo)2.0和3.0的程序員,將他們推向更高的級別。
5.0:從系統(tǒng)的角度考慮問題。對各種系統(tǒng)結(jié)構(gòu)有深入研究,能對整個代碼架構(gòu)中的問題進(jìn)行改進(jìn)。在團(tuán)隊粘合性以及代碼安全性方面有杰出貢獻(xiàn)。對1.0到4.0版本的開發(fā)人員出現(xiàn)的問題能及時察覺,讓整個團(tuán)隊保持積極性且保持興奮的狀態(tài)創(chuàng)建軟件解決辦法。舉例來說,他們總是對新的技術(shù)和信息保持饑渴狀態(tài),試圖用最簡便的方案解決開發(fā)任務(wù)。在整個IT團(tuán)隊中獲得信任,屬于高級程序員和架構(gòu)師。
那么,您屬于哪個版本的程序員呢?
感謝Wendal,匿名人士的投遞
Eclipse官方網(wǎng)站已經(jīng)正式宣布 Eclipse 3.4發(fā)布,代號為ganymede (Ganymede (英語發(fā)音"GAN uh meed")為最大的木星已知衛(wèi)星,也是第七顆發(fā)現(xiàn)的木星衛(wèi)星,在伽利略發(fā)現(xiàn)的衛(wèi)星中離木星第三近,在希臘神話中 Ganymede是一個特洛伊美人的男孩(一個美少男),被宙斯帶去給眾神斟酒)。
?
關(guān)注Eclipse項目的開發(fā)者朋友們可以下載3.4版本嘗試一下,在JavaEye上還專門介紹一個很酷的Eclipse3.4帶的實時結(jié)對編程插件
目前3.4版本是Eclipse項目發(fā)布的10周年慶典版;至今Eclipse項目共有23個子項目。此次發(fā)布的Ganymede 版本引入不少亮點,其中包括新的p2平臺(provisioning platform),點擊查看p2的介紹、新增的Equinox(OSGi實現(xiàn))安全方面的特性、全新的Ecore建模工具、支持SOA等。
Java作為一門優(yōu)秀的面向?qū)ο蟮某绦蛟O(shè)計語言,正在被越來越多的人使用。本文試圖列出作者在實際開發(fā)中碰到的一些Java語言的容易被人忽視的細(xì)節(jié),希望能給正在學(xué)習(xí)Java語言的人有所幫助。
?
1,拓寬數(shù)值類型會造成精度丟失嗎?
??? Java語言的8種基本數(shù)據(jù)類型中7種都可以看作是數(shù)值類型,我們知道對于數(shù)值類型的轉(zhuǎn)換有一個規(guī)律:從窄范圍轉(zhuǎn)化成寬范圍能夠自動類型轉(zhuǎn)換,反之則必須強(qiáng)制轉(zhuǎn)換。請看下圖:
byte-->short-->int-->long-->float-->double
char-->int
我們把順箭頭方向的轉(zhuǎn)化叫做拓寬類型,逆箭頭方向的轉(zhuǎn)化叫做窄化類型。一般我們認(rèn)為因為順箭頭方向的轉(zhuǎn)化不會有數(shù)據(jù)和精度的丟失,所以Java語言允許自動轉(zhuǎn)化,而逆箭頭方向的轉(zhuǎn)化可能會造成數(shù)據(jù)和精度的丟失,所以Java語言要求程序員在程序中明確這種轉(zhuǎn)化,也就是強(qiáng)制轉(zhuǎn)換。那么拓寬類型就一定不會造成數(shù)據(jù)和精度丟失嗎?請看下面代碼:
int i=2000000000;
int num=0;
for(float f=i;f<i+50;f++){
??? num++;
}
System.out.println(num);
請考察以上代碼輸出多少?
如果你回答50 ,那么請運行一下,結(jié)果會讓你大吃一驚!沒錯,輸出結(jié)果是0,難道這個循環(huán)根本就沒有執(zhí)行哪怕一次?確實如此,如果你還不死心,我?guī)憧匆粋€更詫異的現(xiàn)象,運行以下代碼,看輸出什么?
int i=2000000000;
float f1=i;
float f2=i+50;
System.out.println(f1==f2);
??? 哈哈,你快要不相信你的眼睛了,結(jié)果竟然是true;難道f1和f2是相等的嗎?是的,就是這樣,這也就能解釋為什么上一段代碼輸出的結(jié)果是0,而不是50了。那為什么會這樣呢?關(guān)鍵原因在于你將int值自動提升為float時發(fā)生了數(shù)據(jù)精度的丟失,i的初始值是2000000000,這個值非常接近Integer.MAX_VALUE,因此需要用31位來精確表示,而float只能提供24位數(shù)據(jù)的精度(另外8位是存儲位權(quán),見IEEE745浮點數(shù)存儲規(guī)則)。所以在這種自動轉(zhuǎn)化的過程中,系統(tǒng)會將31位數(shù)據(jù)的前24位保留下來,而舍棄掉最右邊的7位,所以不管是2000000000還是2000000050,舍棄掉最右邊7位后得到的值是一樣的。這就是為什么f1==f2的原因了。
??? 類似的這種數(shù)值拓寬類型的過程中會造成精度丟失的還有兩種情況,那就是long轉(zhuǎn)化成float和long轉(zhuǎn)化成double,所以在使用的時候一定要小心。
?
2,i=i+1和i+=1完全等價嗎?
??? 可能有很多程序員認(rèn)為i+=1只是i=i+1的簡寫方式,其實不然,它們一個使用簡單賦值運算,一個使用復(fù)合賦值運算,而簡單賦值運算和復(fù)合賦值運算的最大差別就在于:復(fù)合賦值運算符會自動地將運算結(jié)果轉(zhuǎn)型為其左操作數(shù)的類型。看看以下的兩種寫法,你就知道它們的差別在哪兒了:
? (1) byte i=5;
????? i+=1;
? (2) byte i=5;
????? i=i+1;
??? 第一種寫法編譯沒問題,而第二種寫法卻編譯通不過。原因就在于,當(dāng)使用復(fù)合賦值運算符進(jìn)行操作時,即使右邊算出的結(jié)果是int類型,系統(tǒng)也會將其值轉(zhuǎn)化為左邊的byte類型,而使用簡單賦值運算時沒有這樣的優(yōu)待,系統(tǒng)會認(rèn)為將i+1的值賦給i是將int類型賦給byte,所以要求強(qiáng)制轉(zhuǎn)換。理解了這一點后,我們再來看一個例子:
? byte b=120;
? b+=20;
? System.out.println("b="+b);
? 說到這里你應(yīng)該明白了,上例中輸出b的值不是140,而是-116。因為120+20的值已經(jīng)超出了一個byte表示的范圍,而當(dāng)我們使用復(fù)合賦值運算時系統(tǒng)會自動作類型的轉(zhuǎn)化,將140強(qiáng)轉(zhuǎn)成byte,所以得到是-116。由此可見,在使用復(fù)合賦值運算符時還得小心,因為這種類型轉(zhuǎn)換是在不知不覺中進(jìn)行的,所以得到的結(jié)果就有可能和你的預(yù)想不一樣。
?
3,位移運算越界怎么處理
??? 考察下面的代碼輸出結(jié)果是多少?
??? int a=5;
??? System.out.println(a<<33);
??? 按照常理推測,把a(bǔ)左移33位應(yīng)該將a的所有有效位都移出去了,那剩下的都是零啊,所以輸出結(jié)果應(yīng)該是0才對啊,可是執(zhí)行后發(fā)現(xiàn)輸出結(jié)果是10,為什么呢?因為Java語言對位移運算作了優(yōu)化處理,Java語言對a<<b轉(zhuǎn)化為a<<(b%32)來處理,所以當(dāng)要移位的位數(shù)b超過32時,實際上移位的位數(shù)是b%32的值,那么上面的代碼中a<<33相當(dāng)于a<<1,所以輸出結(jié)果是10。
?
4,判斷奇數(shù)
? 以下的方法判斷某個整數(shù)是否是奇數(shù),考察是否正確:
?? public boolean isOdd(int n){
?????? return (n%2==1);
?? }
?? 很多人認(rèn)為上面的代碼沒問題,但實際上這段代碼隱藏著一個非常大的BUG,當(dāng)n的值是正整數(shù)時,以上的代碼能夠得到正確結(jié)果,但當(dāng)n的值是負(fù)整數(shù)時,以上方法不能做出正確判斷。例如,當(dāng)n=-3時,以上方法返回false。因為根據(jù)Java語言規(guī)范的定義,Java語言里的求余運算符(%)得到的結(jié)果與運算符左邊的值符號相同,所以,-3%2的結(jié)果是-1,而不是1。那么上面的方法正確的寫法應(yīng)該是:
?? public boolean isOdd(int n){
?????? return (n%2!=0);
?? }
?
5,可以讓i!=i嗎?
在本題中,要求你聲明一個i值,使得以下程序輸出"No i!=i":
//在此聲明i,并賦值。
if(i==i){
????? System.out.println("Yes i==i");
? }else{
????? System.out.println("No i!=i");
? }
?
??? 當(dāng)你看到這個命題的時候一定會以為我瘋了,或者Java語言瘋了。這看起來是絕對不可能的,一個數(shù)怎么可能不等于它自己呢?或許就真的是Java語言瘋了,不信請將i做出以下聲明,再運行上面的代碼。
? double i=0.0/0.0;
??? 上面的代碼輸出"No i!=i",為什么會這樣呢?關(guān)鍵在0.0/0.0這個值,在IEEE 754浮點算術(shù)規(guī)則里保留了一個特殊的值用來表示一個不是數(shù)字的數(shù)量。這個值就是NaN("Not a Number"的縮寫),對于所有沒有良好定義的浮點計算都將得到這個值,比如:0.0/0.0;其實我們還可以直接使用Double.NaN來得到這個值。在IEEE 754規(guī)范里面規(guī)定NaN不等于任何值,包括它自己。所以就有了i!=i的代碼。
?
6,2.0-1.1==0.9嗎?
?考察下面的代碼:
?double a=2.0,b=1.1,c=0.9;
?if(a-b==c){
?? System.out.println("YES!");
?}else{
?? System.out.println("NO!");
?}
以上代碼輸出的結(jié)果是多少呢?你認(rèn)為是“YES!”嗎?那么,很遺憾的告訴你,不對,Java語言再一次欺騙了你,以上代碼會輸出“NO!”。為什么會這樣呢?其實這是由實型數(shù)據(jù)的存儲方式?jīng)Q定的。我們知道實型數(shù)據(jù)在內(nèi)存空間中是近似存儲的,所以2.0-1.1的結(jié)果不是0.9,而是0.88888888889。所以在做實型數(shù)據(jù)是否相等的判斷時要非常的謹(jǐn)慎。一般來說,我們不建議在代碼中直接判斷兩個實型數(shù)據(jù)是否相等,如果一定要比較是否相等的話我們也采用以下方式來判斷:
? if(Math.abs(a-b)<1e-5){
???? //相等
? }else{
??? //不相等
? }
上面的代碼判斷a與b之差的絕對值是否小于一個足夠小的數(shù)字,如果是,則認(rèn)為a與b相等,否則,不相等。
隨著企業(yè)intranet和國際internet的迅速發(fā)展,越來越多的工作流程,商務(wù)交易,教育、培訓(xùn)、會議和講座,以及個人消費娛樂都被轉(zhuǎn)移到所謂的萬維網(wǎng)(world wide web,以下簡稱web)上來了。與此相對應(yīng)的是交互操作的復(fù)雜性越來越高。
隨著browser rver模式的日漸流行,很多操作都是在瀏覽器環(huán)境下的網(wǎng)頁上完成的,并不是只有失效的鏈接和意外的出錯才會使操作者感到煩惱,即便是一次完整的成功操作過程,也可能因為操作的繁復(fù)性過高或者使用上的不方便而給操作者帶來不愉快的體驗。
本文試圖闡述web交互頁面設(shè)計的一些指導(dǎo)性原則,這些原則有利于避免發(fā)生不愉快的操作體驗。這些原則是用戶友好性的,是在完成同一種操作要求下,使用戶最感到輕松、簡單、舒適的web交互界面設(shè)計原則。我們假定我們討論的web頁面都是功能正常的,符合美學(xué)觀點的。需要說明我們討論的原則可能會和設(shè)計上的美學(xué)觀點以及既有的功能設(shè)計有所沖突。如果發(fā)生這種情況,基于“實用的就是美的”觀點,我們會建議您酌情放棄原先的美學(xué)觀點與功能設(shè)計。
一、輸入控件的自動聚焦和可用鍵盤切換輸入焦點
??? 使用javascript實現(xiàn)頁面加載完成后立即自動聚焦(focus)到第一個輸入控件。可用tab鍵(ie缺省實現(xiàn))或方向鍵切換聚焦到下一個輸入控件。
??? 輸入控件指web頁面表單(<form> )中顯式的,需要用戶進(jìn)行修改、編輯操作的表單元素。對于這些控件,如果沒有自動聚焦操作,不可避免的出現(xiàn)一次用戶鼠標(biāo)定位操作(如果用戶此前處于鍵盤輸入操作狀態(tài)或鼠標(biāo)定位后需要進(jìn)行鍵盤輸入操作,實際上是鍵盤鼠標(biāo)切換操作)。如果鼠標(biāo)定位后需要進(jìn)行鍵盤輸入操作,如果不能鍵盤切換輸入焦點,那么不可避免的在切換輸入焦點時需要反復(fù)的鍵盤鼠標(biāo)切換操作,這是很繁瑣的。
??? 如果實現(xiàn)了頁面加載完成即自動聚焦到第一個輸入控件,并且可以鍵盤切換輸入焦點標(biāo)定位操作,那么對于用戶來說整個頁面的輸入操作可能都不需要鼠標(biāo)操作,或次數(shù)較少,這是一種便利。畢竟頻繁的鍵盤鼠標(biāo)切換操作是比較累人的。
??? 對于有輸入欄的對話框或網(wǎng)頁,在不干預(yù)的情況下就應(yīng)將當(dāng)前控制焦點定位在待輸入的輸入欄上;如果輸入欄在一般情況下不需要更改其中的內(nèi)容,則應(yīng)直接將焦點定在“確定”按鈕上;在幾個輸入欄之間應(yīng)支持tab,shift+tab切換操作,“確定”和“取消”應(yīng)該是切換操作的終點,與具體所在位置無關(guān)。
二、可用enter(或ctrl+enter)鍵提交,確保和點擊提交按鈕的效果是相同的
不要在提交按鈕上加入onclick=”…”這樣的javascript代碼。
??? 用enter鍵提交頁面是原則1的自然延伸,而且這也是瀏覽器所缺省支持的。只所以單獨列出來是因為實際上有些設(shè)計者設(shè)計的頁面不能達(dá)到這種效果,結(jié)果導(dǎo)致使用enter鍵提交和點擊“確定”按鈕提交帶來的效果不一樣。大部分情況下是設(shè)計者在“確定”按鈕上加入了onclik=”…”這樣的代碼,通過點擊“確定”按鈕后,會執(zhí)行一段javascript代碼,比如對某些hidden類型的input元素設(shè)值。而使用enter鍵提交時就不會執(zhí)行這段代碼。
??? 正確的做法是把這段代碼移到表單標(biāo)簽<form>中,以onsubmit=”…”屬性引入。
??? 對于<textarea>表單元素,它會消耗enter鍵,因此會使得enter鍵提交失效。可以引入javascript代碼捕捉ctrl+enter復(fù)合鍵,一旦捕捉到即執(zhí)行表單的submit()方法。對于需要頻繁提交的場合,比如bbs上,這種代碼是很有必要的。
三、鼠標(biāo)動作提示和回應(yīng)
??? 對用戶的鼠標(biāo)定位操作,當(dāng)移動到可響應(yīng)的位置上時,應(yīng)給予視覺或聽覺的提示。
??? 動作回應(yīng)的最簡單形式就是鼠標(biāo)icon變成手狀。瀏覽器只對具有href屬性的html標(biāo)簽會自動進(jìn)行這種變換icon的行為。對于沒有href屬性(或沒有設(shè)置href屬性)的標(biāo)簽,可以通過javascript設(shè)置style屬性的cursor為hand。
??? 目標(biāo)區(qū)域發(fā)生變化是更為主動的響應(yīng)形式。當(dāng)鼠標(biāo)指針移到目標(biāo)區(qū)域,此時指針圖形改變或文字顏色發(fā)生改變均能較大的減輕用戶搜索定位目標(biāo)區(qū)域的注意力負(fù)擔(dān)。在按鈕上增添直觀的圖形,盡可能的增大按鈕面積;按鈕間保持適當(dāng)?shù)木嚯x,太近增加了用戶區(qū)別它們之間界限以防誤操作的負(fù)擔(dān),太遠(yuǎn)增加了用戶搜索定位按鈕的負(fù)擔(dān)。
四、盡可能早的在客戶端完成輸入數(shù)據(jù)合法性驗證
??? 輸入數(shù)據(jù)的合法性檢驗應(yīng)該在客戶端使用javascript進(jìn)行驗證。除非驗證只能在服務(wù)器端完成,否則驗證工作應(yīng)在最早能完成的情況下進(jìn)行。
??? 在客戶端完成數(shù)據(jù)合法性驗證,可以避免一次服務(wù)器請求和回復(fù)通訊,這種通訊是需要用戶等待的,如果用戶等待很長時間后從服務(wù)器返回的結(jié)果提示出現(xiàn)的錯誤是在輸入時即可發(fā)現(xiàn)的,那么這種設(shè)計就是不友好的。諸如密碼長度限制,用戶名允許字符限制等等,顯然應(yīng)該在客戶端提交前就應(yīng)該進(jìn)行驗證。
五、根據(jù)應(yīng)用場景決定在表單頁面和提交后返回頁面間是否使用中間過渡頁面
??? 根據(jù)應(yīng)用場景,決定是否顯示接收表單頁面(表單頁面和提交后返回頁面間的中間過渡頁面),以及使用何種方式顯示接收表單頁面。
表單頁面和接收表單頁面是大部分web交互操作賴以實現(xiàn)的配合模式。關(guān)于表單頁面和接收表單頁面的相互關(guān)系的設(shè)計,要做如下幾個方面的考慮。
1.對于需要頻繁操作的場合,從操作便利和快捷性出發(fā),盡可能的減少服務(wù)器和客戶端交互次數(shù),應(yīng)該避免使用中間過渡頁面。提交完畢直接返回原來的表單頁面或默認(rèn)頁面。在這種情況下要考慮到數(shù)據(jù)安全和可恢復(fù)性。
如果因為用戶輸入的數(shù)據(jù)不合格,需要重新輸入,那么,去除中間頁面,把錯誤信息直接顯示在原表單頁面上的設(shè)計方式,將是最簡潔的處理方式。用戶只需要根據(jù)錯誤提示進(jìn)行更正即可。當(dāng)然這樣做稍微增加了編程負(fù)擔(dān)。在表單接收頁面上需要包含原表單頁面的內(nèi)容,而且輸入數(shù)據(jù)項都必須用服務(wù)器端代碼或客戶端javascript設(shè)置成用戶輸入的值。為了開發(fā)快捷,可以這樣做:表單頁面和接收表單頁面用同一個服務(wù)器端腳本頁面實現(xiàn)。這個頁面按如下流程完成原來兩個頁面的工作:
頁面腳本初始化
┃
檢查“提交”變量是否設(shè)置
┠已設(shè)置,做數(shù)據(jù)驗證
┃ ┠驗證通過->業(yè)務(wù)邏輯處理->使用包含頁面方式或重定向方式返回到特定頁面
┃ ┗驗證不通過->保存用戶輸入的數(shù)據(jù)->退出表單提交處理到表單頁面流程中
┗未設(shè)置,做表單頁面流程,如有來自提交流程中產(chǎn)生的用戶輸入數(shù)據(jù),則顯示出來
其中,使用包含頁面方式返回到特定頁面可以避免一次客戶端重定向過程,比客戶端重定向過程還要快捷和穩(wěn)定一些。但是有些情況下因為代碼變量沖突或其他原因,使用包含頁面方式可能并不方便,這時候可以使用服務(wù)器端重定向技術(shù),在asp里是server.transfer方法,在java servlet里是requestdispatcher.forward()方法。不要使用response.redirect或者h(yuǎn)ttpservletresponse.sendredirect()這種客戶端http重定向方法。不使用中間過渡頁面也就意味著用戶不能后退瀏覽原先已經(jīng)填好的表單頁面,因為使用的是同一個url。所以在驗證不通過情況下保存用戶輸入的數(shù)據(jù)就是必不可少的。
不使用中間過渡頁面帶來的另一個問題就是使用包含頁面方式或服務(wù)器端重定向方式返回會使得url和頁面內(nèi)容不能一一對應(yīng)。對于用戶可能會直接用這個url(會收藏這個url)訪問返回頁面的情況,他會發(fā)現(xiàn)實際上到達(dá)的是表單頁面,不是他想要的那個返回結(jié)果頁面。所以,去除中間過渡頁面,確實會帶來url和內(nèi)容含混不清的情況,因而不適合需要url和頁面內(nèi)容一一對應(yīng)的場合。
2.從技術(shù)角度考慮,使用中間過渡頁面能保證url和頁面內(nèi)容一一對應(yīng),簡化頁面開發(fā)工作。
為了保證頁面內(nèi)容總是和固定的url聯(lián)系起來,必須使用客戶端重定向:
提交?? 業(yè)務(wù)邏輯處理 (中間過渡頁面)
表單頁面――――->接收表單頁面―――――――――>顯示處理結(jié)果―――>客戶端重定向到特定頁面
客戶端重定向分幾種情況:
1.使用http header重定向,location: http://www.netall.com.cn,這種定向是最快的,在窗口一片空白的情況下就迅速訪問(get)另一個頁面。這種方式實際上不能顯示處理結(jié)果,只能說是向第一種快速重定向方式的一種折衷處理;
2.html標(biāo)簽刷新,<meta http-equiv=”refresh” content=”5;url=http://www.netall.com.cn”>,這種定向比較友好,在這個頁面加載完畢后訪問另一個頁面。很多設(shè)計者把這個作為一個技巧使用,在載入一個大頁面前放置一個緩沖頁面以避免用戶乏味的等待;
3.javascript重定向。由于是用代碼控制重定向,可以做的更靈活。比如根據(jù)用戶習(xí)慣,控制操作完畢后的轉(zhuǎn)向流程。
4.被動式的重定向。在頁面上放置按鈕或鏈接,由用戶手動決定返回到特定頁面。這種情況適合于處理結(jié)果的顯示頁面包含相當(dāng)多的信息,需要用戶仔細(xì)瀏覽,而決定下一步的操作。
?? 在使用中間過渡頁面的情況下,不能再使用頁面過期失效了。否則一旦出現(xiàn)錯誤,需要用戶重新輸入表單數(shù)據(jù),用戶就不能用后退按鈕恢復(fù)此前填寫的表單數(shù)據(jù)了。除非設(shè)計者有意禁止這種恢復(fù)。
六、防止表單重復(fù)提交處理
??? 對提交按鈕點擊后做變灰處理避免在網(wǎng)絡(luò)響應(yīng)較慢情況下用戶重復(fù)提交同一個表單。使用頁面過期失效避免用戶后退瀏覽重復(fù)提交表單。
??? 有些復(fù)雜的應(yīng)用會導(dǎo)致需要較長時間的等待才會返回處理結(jié)果。而在較慢的網(wǎng)絡(luò)環(huán)境中,這種情況更是頻繁發(fā)生。焦急等待的用戶往往會重復(fù)點擊提交按鈕。這種情況是設(shè)計者所不希望看到的。
??? 使用javascript在點擊提交按鈕后使按鈕失效變灰是一個最直接的辦法(根據(jù)原則2這段代碼應(yīng)該放在<form>標(biāo)簽里onsubmit=”…”做)。此外,在表單頁面上,用服務(wù)器端腳本設(shè)置http header的expires為立即過期可以保證用戶沒辦法使用后退瀏覽恢復(fù)表單頁面。注意這樣做的代價可能是用戶辛辛苦苦填寫很長的內(nèi)容,結(jié)果一旦操作失誤就沒法恢復(fù)。所以應(yīng)該避免在包含<textarea>表單元素的頁面上使用頁面過期失效。
??? 應(yīng)該說,更嚴(yán)格的方法是,服務(wù)器端腳本就應(yīng)該具備抵抗重復(fù)提交的能力。例如,為這個表單分配一個唯一id或一個使用一次即失效的驗證碼。此外,這個表單處理還應(yīng)具有事務(wù)性質(zhì),如果表單不被接受,所做的改變還是能恢復(fù)的。在金融應(yīng)用場合,重復(fù)提交同一筆交易是肯定不被允許的。能在重復(fù)提交中獲利的一方總是會想辦法繞過瀏覽器的限制,所以不能依賴于客戶端的技術(shù)。
七、頁面鏈接是打開新窗口、使用原窗口還是彈出窗口的原則
??? 一般而言,首頁上鏈接可以使用target=”_blank”屬性打開新窗口,而其他頁面上的鏈接都應(yīng)使用原窗口或彈出窗口。如果鏈接頁面內(nèi)容相對原頁面來說不重要,是附屬性質(zhì)的,可以使用彈出窗口方式。
??? 一般情況下應(yīng)該使用原窗口,把是否保留原窗口內(nèi)容的權(quán)利留給用戶。除非設(shè)計者相信原頁面是如此重要,在用戶發(fā)出點擊指令后還有使用上的價值,以至于不能被隨便更新或覆蓋。一般來說,只有首頁才會處于這樣一個地位,用戶在首頁上打開一個鏈接后,一般還會在這個首頁上去打開另一個鏈接。比如首頁包含極多鏈接的門戶網(wǎng)站,或者搜索引擎的搜索結(jié)果頁面。google.com以前的搜索結(jié)果頁面上的鏈接是使用原窗口的,后來他們意識到用戶會反復(fù)使用這個頁面,而改成打開新窗口了。一般的網(wǎng)站如果首頁鏈接不多,就不必使用新窗口,這是用戶友好的設(shè)計原則。
??? 上述情形的一個極端情況就是新頁面內(nèi)容比起原頁面內(nèi)容的重要性差很多,以至于都未必需要打開一個新頁面。這時候使用彈出窗口比較合適。用javascript彈出窗口有好幾種:一個是window.open()函數(shù)。這里有個技巧。應(yīng)該使用window.open()先打開一個空白窗口,再使用location.replace()用目標(biāo)頁面替換。這樣做可以避免在打開新頁面的過程中導(dǎo)致原頁面失去響應(yīng)。window.open()將打開一個新的瀏覽器窗口進(jìn)程,因此資源消耗比較大。另一個是由微軟dynamichtml規(guī)范中擴(kuò)充的方法createpopup()。createpopup()可以創(chuàng)建無邊框的彈出窗口,消耗系統(tǒng)資源較小。還有一個就是用頁面中隱藏的層<div>來模擬一個彈出頁面。后兩種可以使用javascript代碼填充彈出窗口內(nèi)容。如果需要下載網(wǎng)頁作為其內(nèi)容的話,需要微軟dynamichtml規(guī)范中的<download>標(biāo)簽。
八、盡可能少的排列可選項,盡可能少的安排操作步驟
??? 根據(jù)用戶操作習(xí)慣安排盡可能少的操作菜單選項,同時要保證盡可能少的操作步驟。 在不降低功能多樣性的前提下減少菜單項和操作步驟是用戶友好的設(shè)計。要做到這一點很不容易。要從用戶出發(fā)考慮他們最頻繁的操作是什么。正常情況下一個用戶需要的操作總可以歸類為5個以下的種類,如果出現(xiàn)更多的種類,那一定是沒有針對用戶興趣去區(qū)分主次。一個用戶同時有5個以上的強(qiáng)烈興趣中心是難以想像的,走馬觀花似的隨意點擊瀏覽的用戶,是不大可能在某個種類上進(jìn)行深入的交互操作的。在這5個種類中,每個種類都可能有若干個可操作的二級種類。如果這些二級操作項是不可見的,那么意味著要做兩次選擇才能進(jìn)入可操作頁面。這就違背了“盡可能少的安排操作步驟”這一原則。如果使用javascript制作二級菜單,避免請求服務(wù)器,會好一些。如果二級菜單項總共不超過20個左右,不妨將二級菜單直接顯示出來,比如放在左列一字向下排開,這樣只需要一次選擇到可操作項,更加明了方便。
九、操作邏輯無漏洞,保證數(shù)據(jù)是操作安全的
??? 多個頁面間的操作和同個頁面上的多個操作間的邏輯關(guān)系在設(shè)計上是安全和嚴(yán)謹(jǐn)?shù)摹1WC不會出現(xiàn)不被允許的用戶操作組合,至少不會因為用戶的不適當(dāng)?shù)牟僮鲗?dǎo)致出錯。
??? 這最典型的表現(xiàn)則是在頁面上廣泛采用的所謂聯(lián)動下拉框設(shè)計。一個下拉框中允許的選項受另一個下拉框中的選擇而變。另外一個例子是根據(jù)選擇使表單元素有效或者失效。如果在多個頁面間也要維持某種合法性邏輯,那么就需要服務(wù)器端腳本的參與。這樣會使表單設(shè)計跟操作有關(guān),應(yīng)該說這不是一個好的設(shè)計。可以通過變更操作步驟順序、組合方式來盡可能避免這種情況出現(xiàn)。
??? 操作邏輯的設(shè)計既要保證用戶任意的輸入不會導(dǎo)致錯誤,也要保證是用戶輸入的數(shù)據(jù)能購被安全處理。在session控制下的表單中輸入大幅文字可能會導(dǎo)致超時出錯,這時候往往還伴隨重定向過程,導(dǎo)致用戶的長篇輸入蕩然無存。用javascript提醒用戶已超時,請保存輸入后重新提交,是一個好辦法。某些表單元素如<input type=”text”>接受esc鍵清除數(shù)據(jù),并且無法撤銷,這也是很危險的。在中文輸入法中常常使用esc鍵清楚輸入的碼位,一旦不小心多按一下esc就會使得輸入數(shù)據(jù)消失。因此有必要用javascript禁用<input>和<textarea>的esc鍵處理過程。(Edit From:Internet,By Aaron)
記得還是去年,剛到據(jù)說是高手云集的威威公司上班的時候,一個新到的同事給我講他花了半天的時間寫,并做了很長時間的實踐,寫了個關(guān)于攻擊.jsp頁面的程序。下面我把具體的實現(xiàn)過程和大家分享一下。測試平臺是Tomcat,當(dāng)然,版本有點低,他的目的只是想證實一下他的某些想法。首先,他在Tomcat的WEB目錄下建立了一個Hello.jsp文件,內(nèi)容是:
<%out.print(hello);%>
通過IE的正常請求地址為:http://localhost:8080/examples/jsp/hello.jsp,顯示結(jié)果為:hello。然后開始具體的攻擊測試。測試時,發(fā)出的請求地址為:http://localhost:8080/examples/jsp/////////hello.jsp?,瀏覽器上顯示編譯錯誤,錯誤的原因是500?java.lang.NullPointerException。這個應(yīng)該是比較常見的錯誤了。現(xiàn)在,恢復(fù)正常的請求http://localhost:8080/examples/jsp/hello.jsp,問題就出現(xiàn)了,即出錯,而且所報的錯誤和剛才造成它錯誤的請求是一樣的:“500?java.lang.NullPointerException”。難道是緩存在瀏覽器里了嗎?換臺機(jī)器訪問http://192.168.10.188/examples/jsp/hello.jsp。問題依然如故,哎!可憐的Hello.jsp呀!
雖然這個問題有些弱智,不過,他的目的也達(dá)到了,即找出“.jsp”流程中存在的一些問題。所以,JSP程序同ASP一樣,還是存在著很多安全上的問題的。因此,對于一心研究論壇或者其他安全信息的朋友來說,要想發(fā)現(xiàn)JSP的BUG,了解一些JSP的工作原理是十分重要的。
需要指出的是,雖然是一門網(wǎng)絡(luò)編程語言,JSP和PHP、ASP的工作機(jī)制還存在很大的區(qū)別,首次調(diào)用JSP文件時,JSP頁面在執(zhí)行時是編譯式,而不是解釋式的。首次調(diào)用JSP文件其實是執(zhí)行一個編譯為Servlet的過程。當(dāng)瀏覽器向服務(wù)器請求這一個JSP文件的時候,服務(wù)器將檢查自上次編譯后JSP文件是否有改變,如果沒有改變,就直接執(zhí)行Servlet,而不用再重新編譯,這樣,工作效率得到了明顯提高。這也是目前JSP論壇開始逐漸風(fēng)靡的一個重要原因。
小提示:Servlet是用Java編寫的Server端程序,它與協(xié)議和平臺無關(guān);Servlet運行于Java-enabled?WEB?Server中;Java?Servlet可以動態(tài)地擴(kuò)展Server的能力,并采用請求-響應(yīng)模式提供WEB服務(wù);最早支持Servlet技術(shù)的是JavaSoft的Java?WEB?Server;Servlet的主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動態(tài)WEB內(nèi)容。
說到這里,我們自然就會關(guān)心一些JSP的安全問題。一般來說,常見的JSP安全問題有源代碼暴露(包括程序源代碼以明文的方式返回給訪問者,如添加特殊后綴引起jsp源代碼暴露;插入特殊字符串引起Jsp源代碼暴露;路徑權(quán)限引起的文件Jsp源代碼暴露;文件不存在引起的絕對路徑暴露問題等)、遠(yuǎn)程程序執(zhí)行類、數(shù)據(jù)庫如SQL?Server、Oracle?、DB2等的漏洞,操作系統(tǒng)漏洞等。不過,為了突出Jsp的安全問題,本文將結(jié)合目前的一些比較流行的Jsp論壇分類闡述和提出解決的建議。為了講解方便,本文還采用一些公開了原代碼的論壇實例代碼,至于安裝軟件版本、操作系統(tǒng)等,可以查看安裝提示。
論壇用戶管理缺陷
為了加強(qiáng)實戰(zhàn)效果,我們可以到http://down.chinaz.com/S/5819.asp這個地址下載一個典型的論壇代碼,根據(jù)提示,數(shù)據(jù)源名稱為yyForum,用戶名為xyworker,密碼:999。到baidu、Google等網(wǎng)站搜索一下,我們可以看到,安裝這個代碼的論壇不少。仔細(xì)分析后,可以發(fā)現(xiàn),用戶管理的頁面是user_manager.jsp文件。首先,我們看看這個系統(tǒng)是如何加強(qiáng)它的代碼安全性的。其中,在代碼的開始部分有一個if限制條件,代碼的第三行到第十行具體如下:
<%
if?((session.getValue(UserName)==null)||(session.getValue(UserClass)==null)||(!session.getValue(UserClass).equals(系統(tǒng)管理員)))
%>
其中,Session.getValue表示檢索出Session的值;sendRedirect()執(zhí)行后,地址欄鏈接會改變,相當(dāng)于客戶端又重新發(fā)了一個get請求,要服務(wù)器傳輸另一個文件過來。
下面,我們再來看看修改用戶信息的文件modifyuser_manager.jsp。典型代碼如下:
<%@page?contentType=text/html;?charset=gb2312?language=java?import=java.sql.*,java.util.*??%>
<jsp:useBean?id=yy?scope=page?class=yy.jdbc/>
<%!String?User_Name,User_Password,sql,?User_Sign;%>
<%
User_Name=request.getParameter(name);
//out.println(User_Name);
User_Password=request.getParameter(password);
User_Password=yy.ex_chinese(User_Password);
……
User_Sign=request.getParameter(sign);
User_Sign=yy.ex_chinese(User_Sign);
Connection?con=yy.getConn();
Statement??stmt=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
sql=update?用戶表?set?用戶密碼='+User_Password+',用戶性別='+User_Sex+',用戶郵箱='+User_Email+',居住地址='+User_Address+',手機(jī)號碼='+User_Mobile+',Oicq='+User_Oicq+',出生日期='+User_Birthay+',用戶等級='+User_Class+',簽名='+User_Sign+'?where?用戶名='+User_Name+';
//out.println(sql);
stmt.executeUpdate(sql);
out.println(<font?size=2?color=blue>正在處理你的用戶信息,請稍后...</font><meta?http-equiv='refresh'?content='2;url=user_manager.jsp'>);
%>
<jsp:include?page=inc/online.jsp?flush=true/>
看看這個文件,我們就好像看到了一個簡單的教學(xué)文件。現(xiàn)在,假設(shè)管理員提交如下地址,即http://www.51dz.net/bbs/modifyuser_manager.jsp?modifyid=51,需要查看、修改ID為51的用戶的資料(管理員默認(rèn)的用戶ID為51)。問題就出來了。同樣的,我們可以通過搜索引擎得到如下地址
很明顯,這個用戶管理文件缺乏認(rèn)證,即使是普通的用戶,甚至包括我們這些搭不上邊的“游客”,也可以直接提交上述請求,從而將其資料一覽無余,更讓人動心的是,密碼也是明文存儲的。
http://www.51dz.net/bbs/modifyuser_manager.jsp同樣是大開山門,直到惡意用戶把數(shù)據(jù)更新的操作執(zhí)行完畢,重定向到user_manager.jsp的時候,管理員才會看見那個顯示錯誤的頁面,但這個時候為時已晚,更談不上“亡羊補(bǔ)牢”了。類似的錯誤存在于很多JSP的站點上,面對這樣的論壇,我們能夠放心的說“安全”嗎?解決之道有很多,不過,最基本的要求是為每個需要加身份認(rèn)證的地方加上身份認(rèn)證,如果借用別人的代碼,一定要對涉及到用戶管理、密碼認(rèn)證等重要文件修改一下,照搬雖然省事,但代碼毫無安全性可言。
再就是SQL注入的問題。比如,這個典型的問題:“昨天公司的數(shù)據(jù)庫被人SQL注入,9萬條記錄都被update了,同事寫了個JSP程序來把他改回來,可是這JSP沒有一點信息返回,看不到進(jìn)度,在運行些什么都不知道。”不過,這和JSP程序沒有什么必然的聯(lián)系,根據(jù)國情,國內(nèi)的網(wǎng)站用ASP+Access或SQLServer的占70%以上,PHP+MySQL占20%,其它的不足10%。因此,ASP的SQL注入比較常見也不足為怪。不過,SQL注入漏洞可謂是“千里之堤,潰于蟻穴”,這種漏洞在網(wǎng)上極為普遍,即使是JSP程序也不能幸免。歸根結(jié)底,通常是由于程序員對注入不了解,或者程序過濾不嚴(yán)格,或者某個參數(shù)忘記檢查導(dǎo)致。看看這個教材式的JSP程序就可以窺見一般:
Statement?stmt?=?conn.createStatement();?
String?checkUser?=?select?*?from?login?where?username?=?'?+?userName?+?'?and?userpassword?=?'?+?userPassword?+?';?
ResultSet?rs?=?stmt.executeQuery(checkUser);?
if(rs.next())?
response.sendRedirect(SuccessLogin.jsp);?
else?
response.sendRedirect(FailureLogin.jsp);
針對這種情況,如果數(shù)據(jù)庫里存在一個名叫“Tom”的用戶,那么在不知道密碼的情況下至少有下面幾種方法可以登錄:?
用戶名:Tom????????????密碼:'?or?'a'='a
用戶名:Tom????????????密碼:'?or?1=1/*
用戶名:Tom'?or?1=1/*?????密碼:(任意)
|
|
|
1.1重定向(如果對方不支持cookie,回寫sessionID進(jìn)行session跟蹤) ?response.sendRedirect(response.encodeRedirectURL(request.getContextPath()+"/next")); ****************************************************************** 1.2轉(zhuǎn)發(fā) ?RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(url); ?dispatcher.forward(request,response); ****************************************************************** 1.3字符 ??request.setCharacterEncoding("utf-8"); ??response.setContentType("text/html;charset=utf-8"); ****************************************************************** String servletPath = request.getServletPath(); ??servletPath = servletPath.substring(servletPath.lastIndexOf("/") + 1); ??String operation = servletPath.substring(0, servletPath.indexOf(".do"));
1.設(shè)置連接超時時間(分鐘) ?<session-config> ??<session-timeout>50</session-timeout> ?</session-config> ****************************************************************** 4.相對路徑匹配 ?1>絕對匹配?/xx/yy ?2>后綴匹配?*.xx ?3>后面匹配?/xx/* ****************************************************************** 5.監(jiān)聽器 5.1ServletRequestListener ???getServletContext() ???getServletRequest() ?requestDestroyed(ServletRequestEvent) ?requestInitialized(ServletRequestEvent) 5.2HttpSessionListener ???getSession() ?sessionCreated(HttpSessionEvent) ?sessionDestroyed(HttpSessionEvent) 5.3ServletContextListener ???getServletContext() ?contextInitialized(ServletContextEvent) ?contextDestroyed(ServletContextEvent) ? 5.4ServletRequestAttributeListener ???getName() ???getValue() ?attributeAdded(ServletRequestAttributeEvent) ?attributeRemoved(ServletRequestAttributeEvent) ?attributeReplaced(ServletRequestAttributeEvent) 5.5HttpSessionAttributeListener ???getName() ???getValue() ???getSession() ?attributeAdded(HttpSessionBindingEvent) ?attributeRemoved(HttpSessionBindingEvent) ?attributeReplaced(HttpSessionBindingEvent) 5.6ServletContextAttributeListener ???getName() ???getValue() ?attributeAdded(ServletContextAttributeEvent) ?attributeRemoved(ServletContextAttributeEvent) ?attributeReplaced(ServletContextAttributeEvent)
|
|
?
?
Linux中常用的關(guān)機(jī)和重新啟動命令有shutdown、halt、reboot以及init,它們都可以達(dá)到關(guān)機(jī)和重新啟動的目的,但是每個命令的內(nèi)部工作過程是不同的,下面將逐一進(jìn)行介紹。
1. shutdown
shutdown命令用于安全關(guān)閉Linux系統(tǒng)。有些用戶會使用直接斷掉電源的方式來關(guān)閉Linux,這是十分危險的。因為Linux與Windows不同,其后臺運行著許多進(jìn)程,所以強(qiáng)制關(guān)機(jī)可能會導(dǎo)致進(jìn)程的數(shù)據(jù)丟失,使系統(tǒng)處于不穩(wěn)定的狀態(tài),甚至?xí)p壞硬件設(shè)備。
執(zhí) 行shutdown命令時,系統(tǒng)會通知所有登錄的用戶系統(tǒng)將要關(guān)閉,并且login指令會被凍結(jié),即新的用戶不能再登錄系統(tǒng)。使用shutdown命令可 以直接關(guān)閉系統(tǒng),也可以延遲指定的時間再關(guān)閉系統(tǒng),還可以重新啟動。延遲指定的時間再關(guān)閉系統(tǒng),可以讓用戶有時間儲存當(dāng)前正在處理的文件和關(guān)閉已經(jīng)打開的 程序。
shutdown命令的部分參數(shù)如下:
[-t] 指定在多長時間之后關(guān)閉系統(tǒng)
[-r] 重啟系統(tǒng)
[-k] 并不真正關(guān)機(jī),只是給每個登錄用戶發(fā)送警告信號
[-h] 關(guān)閉系統(tǒng)(halt)
shutdown命令的工作實質(zhì)是給init程序發(fā)送信號(signal),要求其切換系統(tǒng)的運行級別(Runlevel)。系統(tǒng)的運行級別包括:
0:關(guān)閉系統(tǒng)
1:單用戶模式,如果沒有為shutdown命令指定-h或-r參數(shù)而直接執(zhí)行,則默認(rèn)將切換到此運行級別
2:多用戶模式(不支持NFS)
3:多用戶模式(支持NFS),一般常用此種運行級別
5:多用戶模式(GUI模式)
6:重新啟動系統(tǒng)
2. halt
halt是最簡單的關(guān)機(jī)命令,其實際上是調(diào)用shutdown -h命令。halt執(zhí)行時,殺死應(yīng)用進(jìn)程,文件系統(tǒng)寫操作完成后就會停止內(nèi)核。
halt命令的部分參數(shù)如下:
[-f] 沒有調(diào)用shutdown而強(qiáng)制關(guān)機(jī)或重啟
[-i] 關(guān)機(jī)或重新啟動之前,關(guān)掉所有的網(wǎng)絡(luò)接口
[-p] 關(guān)機(jī)時調(diào)用poweroff,此選項為缺省選項
3.reboot
reboot的工作過程與halt類似,其作用是重新啟動,而halt是關(guān)機(jī)。其參數(shù)也與halt類似。
4.init
init是所有進(jìn)程的祖先,其進(jìn)程號始終為1。init用于切換系統(tǒng)的運行級別,切換的工作是立即完成的。init 0命令用于立即將系統(tǒng)運行級別切換為0,即關(guān)機(jī);init 6命令用于將系統(tǒng)運行級別切換為6,即重新啟動。
|