#
引言
在J2EE的整個發展歷程中,現在正是一個非常時刻。從很多方面來說,J2EE都是一個偉大的成功:它成功地在從前沒有標準的地方建立了標準;大大提升了企業級軟件的開放程度,并且得到了整個行業和開發者的廣泛認可。然而,J2EE在一些方面已經開始捉襟見肘。J2EE應用開發的成本通常很高。J2EE應用項目至少和從前的非J2EE項目一樣容易失敗——如果不是更容易失敗的話。這樣的失敗率高得讓人難以接受。在這樣的失敗率之下,軟件開發幾乎變成了碰運氣。而在J2EE遭遇失敗的場景中,EJB通常都扮演著重要的角色。因此,J2EE社群不斷地向著更簡單的解決方案、更少使用EJB的方向發展
[1]。然而,每個應用程序都需要一些基礎設施,拒絕使用EJB并不意味著拒絕EJB所采用的基礎設施解決方案。那么,如何利用現有的框架來提供這些基礎設施服務呢,伴隨著這個問題的提出,一個輕量級的J2EE解決方案出現了,這就是Spring Framework。
Spring是為簡化企業級系統開發而誕生的,Spring框架為J2EE應用常見的問題提供了簡單、有效的解決方案,使用Spring,你可以用簡單的POJO(Plain Old Java Object)來實現那些以前只有EJB才能實現的功能。這樣不只是能簡化服務器端開發,任何Java系統開發都能從Spring的簡單、可測試和松耦合特征中受益。可以簡單的說,Spring是一個輕量級的反向控制(IoC)和面向切面編程(AOP)容器框架
[3]。Spring IoC,借助于依賴注入設計模式,使得開發者不用理會對象自身的生命周期及其關系,而且能夠改善開發者對J2EE模式的使用;Spring AOP,借助于Spring實現的攔截器,開發者能夠實現以聲明的方式使用企業級服務,比如安全性服務、事務服務等。Spring IoC和 Spring ; AOP組合,一起形成了Spring,這樣一個有機整體,使得構建輕量級的J2EE架構成為可能,而且事實證明,非常有效。
沒有Spring IoC的Spring AOP是不完善的,沒有Spring AOP的Spring IoC是不健壯的。本文是以Spring架構的成功的實際商務系統項目為背景,闡述了反向控制原理和面向切面的編程技術在Spring框架中的應用,同時抽取適量代碼示意具體應用,并和傳統開發模式進行對比,展示了Spring framework的簡單,高效,可維護等優點。
1、Spring IoC 1.1 反向控制原理 反向控制是Spring框架的核心。但是,反向控制是什么意思?到底控制的什么方面被反向了呢?2004年美國專家Martin Fowler發表了一篇論文《Inversion of Control Containers and the Dependency Injection pattern》闡述了這個問題,他總結說是獲得依賴對象的方式反向了,根據這個啟示,他還為反向控制提出了一個更貼切的名字:Dependency Injection(DI 依賴注入)。
通常,應用代碼需要告知容器或框架,讓它們找到自身所需要的類,然后再由應用代碼創建待使用的對象實例。因此,應用代碼在使用實例之前,需要創建對象實例。然而,IoC模式中,創建對象實例的任務交給IoC容器或框架(實現了IoC設計模式的框架也被稱為IoC容器),使得應用代碼只需要直接使用實例,這就是IoC。相對IoC 而言,“依賴注入”的確更加準確的描述了這種設計理念。所謂依賴注入,即組件之間的依賴關系由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關系注入到組件之中。
1.2 IoC在Spring中的實現
任何重要的系統都需要至少兩個相互合作的類來完成業務邏輯。通常,每個對象都要自己負責得到它的合作(依賴)對象。你會發現,這樣會導致代碼耦合度高而且難于測試。使用IoC,對象的依賴都是在對象創建時由負責協調系統中各個對象的外部實體提供的,這樣使軟件組件松散連接成為可能。下面示意了Spring IoC 應用,步驟如下:
(1)定義Action接口,并為其定義一個execute方法,以完成目標邏輯。多年前,GoF在《Design Pattern:Elements of Reusable Object-Oriented Software》一書中提出“Programming to an Interface,not an implementation”的原則,這里首先將業務對象抽象成接口,正是為了實施這個原則。
(2)類UpperAction實現Action接口,在此類中,定義一個String型的域message,并提供相應的setter和getter方法,實現的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")); } |
上面的測試代碼中,我們根據"bean.xml"創建了一個ApplicationContext實例,并從此實例中獲取我們所需的Action實現,運行測試代碼,我們看到控制臺輸出:
仔細觀察一下上面的代碼,可以看到:
(1)我們的組件并不需要實現框架指定的接口,因此可以輕松的將組件從Spring中脫離,甚至不需要任何修改,這在基于EJB框架實現的應用中是難以想象的。
(2)組件間的依賴關系減少,極大改善了代碼的可重用性。Spring的依賴注入機制,可以在運行期為組件配置所需資源,而無需在編寫組件代碼時就加以指定,從而在相當程度上降低了組件之間的耦合。
Spring給我們帶來了如此這般的好處,那么,反過來,讓我們試想一下,如果不使用Spring框架,回到我們傳統的編碼模式,情況會是怎樣呢?
首先,我們必須編寫一個配置文件讀取類,以實現Message屬性的可配置化。
其次,得有一個Factory模式的實現,并結合配置文件的讀寫完成Action的動態加載。于是,我們實現了一個ActionFactory來實現這個功能:
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 |
測試代碼也作相應修改。現在不論實現的好壞,總之通過上面新增的多行代碼,終于實現了類似的功能。如果現在有了一個新的需求,這樣這個ActionFactory每次都新建一個類的實例,顯然這對系統性能不利,考慮到我們的兩個Action都是線程安全的,修改一下ActionFactory,保持系統中只有一個Action實例供其它線程調用。另外Action對象創建后,需要做一些初始化工作。修改一下ActionFactory,使其在創建Action實例之后,隨即就調用Action.init方法執行初始化。Action的處理這樣就差不多了。下面我們來看看另外一個Factory
……
往往這些系統開發中最常見的需求,會導致我們的代碼迅速膨脹,而Spring IoC的出現,則大大緩解了這樣的窘境。通過以上實例,可以看出,Spring IoC為我們提供了如下幾方面的優勢:
(1)應用組件不需要在運行時尋找其協作者,因此更易于開發和編寫應用;
(2)由于借助于IoC容器管理組件的依賴關系,使得應用的單元測試和集成測試更利于展開;
(3)通常,在借助于IoC容器關系業務對象的前提下,很少需要使用具體IoC容器提供的API,這使得集成現有的遺留應用成為可能。
因此,通過使用IoC能夠降低組件之間的耦合度,最終,能夠提高類的重用性,利于測試,而且更利于整個產品或系統集成和配置。
2、Spring AOP
2.1 面向切面編程基礎
通常,系統由很多組件組成,每個組件負責一部分功能,然而,這些組件也經常帶有一些除了核心功能之外的附帶功能 。系統服務如日志、事務管理和安全經常融入到一些其他功能模塊中。這些系統服務通常叫做交叉業務,這是因為它們總是分布在系統的很多組件中。通過將這些業務分布在多個組件中,給我們的代碼引入了雙重復雜性。
(1) 實現系統級業務的代碼在多個組件中復制。這意味著如果你要改變這些業務邏輯,你就必須到各個模塊去修改。就算把這些業務抽象成一個獨立模塊,其它模塊只是調用它的一個方法,但是這個方法調用也還是分布在很多地方。
(2) 組件會因為那些與自己核心業務無關的代碼變得雜亂。一個向地址錄中添加條目的方法應該只關心如何添加地址,而不是關心它是不是安全或支持事務的。
此時,我們該怎么辦呢?這正是AOP用得著的地方。AOP幫助我們將這些服務模塊化,并把它們聲明式地應用在需要它們的地方,使得這些組件更加專注于自身業務,完全不知道其它涉及到的系統服務。
這里的概念切面,就是我們要實現的交叉功能,是應用系統模塊化的一個方面或領域。切面的最常見例子就是日志記錄。日志記錄在系統中到處需要用到,利用繼承來重用日志模塊是不合適的,這樣,就可以創建一個日志記錄切面,并且使用AOP在系統中應用。下圖展示了切面應用方式

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

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