#

          反向控制和面向切面編程在Spring的應(yīng)用

          引言

            在J2EE的整個(gè)發(fā)展歷程中,現(xiàn)在正是一個(gè)非常時(shí)刻。從很多方面來(lái)說(shuō),J2EE都是一個(gè)偉大的成功:它成功地在從前沒(méi)有標(biāo)準(zhǔn)的地方建立了標(biāo)準(zhǔn);大大提升了企業(yè)級(jí)軟件的開(kāi)放程度,并且得到了整個(gè)行業(yè)和開(kāi)發(fā)者的廣泛認(rèn)可。然而,J2EE在一些方面已經(jīng)開(kāi)始捉襟見(jiàn)肘。J2EE應(yīng)用開(kāi)發(fā)的成本通常很高。J2EE應(yīng)用項(xiàng)目至少和從前的非J2EE項(xiàng)目一樣容易失敗——如果不是更容易失敗的話。這樣的失敗率高得讓人難以接受。在這樣的失敗率之下,軟件開(kāi)發(fā)幾乎變成了碰運(yùn)氣。而在J2EE遭遇失敗的場(chǎng)景中,EJB通常都扮演著重要的角色。因此,J2EE社群不斷地向著更簡(jiǎn)單的解決方案、更少使用EJB的方向發(fā)展[1]。然而,每個(gè)應(yīng)用程序都需要一些基礎(chǔ)設(shè)施,拒絕使用EJB并不意味著拒絕EJB所采用的基礎(chǔ)設(shè)施解決方案。那么,如何利用現(xiàn)有的框架來(lái)提供這些基礎(chǔ)設(shè)施服務(wù)呢,伴隨著這個(gè)問(wèn)題的提出,一個(gè)輕量級(jí)的J2EE解決方案出現(xiàn)了,這就是Spring Framework。

            Spring是為簡(jiǎn)化企業(yè)級(jí)系統(tǒng)開(kāi)發(fā)而誕生的,Spring框架為J2EE應(yīng)用常見(jiàn)的問(wèn)題提供了簡(jiǎn)單、有效的解決方案,使用Spring,你可以用簡(jiǎn)單的POJO(Plain Old Java Object)來(lái)實(shí)現(xiàn)那些以前只有EJB才能實(shí)現(xiàn)的功能。這樣不只是能簡(jiǎn)化服務(wù)器端開(kāi)發(fā),任何Java系統(tǒng)開(kāi)發(fā)都能從Spring的簡(jiǎn)單、可測(cè)試和松耦合特征中受益。可以簡(jiǎn)單的說(shuō),Spring是一個(gè)輕量級(jí)的反向控制(IoC)和面向切面編程(AOP)容器框架[3]。Spring IoC,借助于依賴(lài)注入設(shè)計(jì)模式,使得開(kāi)發(fā)者不用理會(huì)對(duì)象自身的生命周期及其關(guān)系,而且能夠改善開(kāi)發(fā)者對(duì)J2EE模式的使用;Spring AOP,借助于Spring實(shí)現(xiàn)的攔截器,開(kāi)發(fā)者能夠?qū)崿F(xiàn)以聲明的方式使用企業(yè)級(jí)服務(wù),比如安全性服務(wù)、事務(wù)服務(wù)等。Spring IoC和 Spring ; AOP組合,一起形成了Spring,這樣一個(gè)有機(jī)整體,使得構(gòu)建輕量級(jí)的J2EE架構(gòu)成為可能,而且事實(shí)證明,非常有效。沒(méi)有Spring IoC的Spring AOP是不完善的,沒(méi)有Spring AOP的Spring IoC是不健壯的。本文是以Spring架構(gòu)的成功的實(shí)際商務(wù)系統(tǒng)項(xiàng)目為背景,闡述了反向控制原理和面向切面的編程技術(shù)在Spring框架中的應(yīng)用,同時(shí)抽取適量代碼示意具體應(yīng)用,并和傳統(tǒng)開(kāi)發(fā)模式進(jìn)行對(duì)比,展示了Spring framework的簡(jiǎn)單,高效,可維護(hù)等優(yōu)點(diǎn)。

            1、Spring IoC 1.1 反向控制原理

            反向控制是Spring框架的核心。但是,反向控制是什么意思?到底控制的什么方面被反向了呢?2004年美國(guó)專(zhuān)家Martin Fowler發(fā)表了一篇論文《Inversion of Control Containers and the Dependency Injection pattern》闡述了這個(gè)問(wèn)題,他總結(jié)說(shuō)是獲得依賴(lài)對(duì)象的方式反向了,根據(jù)這個(gè)啟示,他還為反向控制提出了一個(gè)更貼切的名字:Dependency Injection(DI 依賴(lài)注入)。

            通常,應(yīng)用代碼需要告知容器或框架,讓它們找到自身所需要的類(lèi),然后再由應(yīng)用代碼創(chuàng)建待使用的對(duì)象實(shí)例。因此,應(yīng)用代碼在使用實(shí)例之前,需要?jiǎng)?chuàng)建對(duì)象實(shí)例。然而,IoC模式中,創(chuàng)建對(duì)象實(shí)例的任務(wù)交給IoC容器或框架(實(shí)現(xiàn)了IoC設(shè)計(jì)模式的框架也被稱(chēng)為IoC容器),使得應(yīng)用代碼只需要直接使用實(shí)例,這就是IoC。相對(duì)IoC 而言,“依賴(lài)注入”的確更加準(zhǔn)確的描述了這種設(shè)計(jì)理念。所謂依賴(lài)注入,即組件之間的依賴(lài)關(guān)系由容器在運(yùn)行期決定,形象的來(lái)說(shuō),即由容器動(dòng)態(tài)的將某種依賴(lài)關(guān)系注入到組件之中。

            1.2 IoC在Spring中的實(shí)現(xiàn)

            任何重要的系統(tǒng)都需要至少兩個(gè)相互合作的類(lèi)來(lái)完成業(yè)務(wù)邏輯。通常,每個(gè)對(duì)象都要自己負(fù)責(zé)得到它的合作(依賴(lài))對(duì)象。你會(huì)發(fā)現(xiàn),這樣會(huì)導(dǎo)致代碼耦合度高而且難于測(cè)試。使用IoC,對(duì)象的依賴(lài)都是在對(duì)象創(chuàng)建時(shí)由負(fù)責(zé)協(xié)調(diào)系統(tǒng)中各個(gè)對(duì)象的外部實(shí)體提供的,這樣使軟件組件松散連接成為可能。下面示意了Spring IoC 應(yīng)用,步驟如下:

            (1)定義Action接口,并為其定義一個(gè)execute方法,以完成目標(biāo)邏輯。多年前,GoF在《Design Pattern:Elements of Reusable Object-Oriented Software》一書(shū)中提出“Programming to an Interface,not an implementation”的原則,這里首先將業(yè)務(wù)對(duì)象抽象成接口,正是為了實(shí)施這個(gè)原則。

            (2)類(lèi)UpperAction實(shí)現(xiàn)Action接口,在此類(lèi)中,定義一個(gè)String型的域message,并提供相應(yīng)的setter和getter方法,實(shí)現(xiàn)的execute方法如下:

          public String execute (String str) {
           return (getMessage () + str).toUpperCase () ;
          }

            (3)編寫(xiě)Spring配置文件(bean.xml)

          <beans>
          <bean id="TheAction" class="net.chen.spring.qs.UpperAction">
          <property name="message">
          <value>HeLLo</value>
          </property>
          </bean>
          </beans>

            (4)測(cè)試代碼

          public void testQuickStart () {
           ApplicationContext ctx=new
           FileSystemXmlApplicationContext ("bean.xml");
           Action a= (Action) ctx.getBean ("TheAction");
           System.out.println (a. execute ("Rod Johnson"));
          }

            上面的測(cè)試代碼中,我們根據(jù)"bean.xml"創(chuàng)建了一個(gè)ApplicationContext實(shí)例,并從此實(shí)例中獲取我們所需的Action實(shí)現(xiàn),運(yùn)行測(cè)試代碼,我們看到控制臺(tái)輸出:

          ……
          HELLO ROD JOHNSON

            仔細(xì)觀察一下上面的代碼,可以看到:

            (1)我們的組件并不需要實(shí)現(xiàn)框架指定的接口,因此可以輕松的將組件從Spring中脫離,甚至不需要任何修改,這在基于EJB框架實(shí)現(xiàn)的應(yīng)用中是難以想象的。

            (2)組件間的依賴(lài)關(guān)系減少,極大改善了代碼的可重用性。Spring的依賴(lài)注入機(jī)制,可以在運(yùn)行期為組件配置所需資源,而無(wú)需在編寫(xiě)組件代碼時(shí)就加以指定,從而在相當(dāng)程度上降低了組件之間的耦合。

            Spring給我們帶來(lái)了如此這般的好處,那么,反過(guò)來(lái),讓我們?cè)囅胍幌拢绻皇褂肧pring框架,回到我們傳統(tǒng)的編碼模式,情況會(huì)是怎樣呢?

            首先,我們必須編寫(xiě)一個(gè)配置文件讀取類(lèi),以實(shí)現(xiàn)Message屬性的可配置化。

            其次,得有一個(gè)Factory模式的實(shí)現(xiàn),并結(jié)合配置文件的讀寫(xiě)完成Action的動(dòng)態(tài)加載。于是,我們實(shí)現(xiàn)了一個(gè)ActionFactory來(lái)實(shí)現(xiàn)這個(gè)功能:

          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

            測(cè)試代碼也作相應(yīng)修改。現(xiàn)在不論實(shí)現(xiàn)的好壞,總之通過(guò)上面新增的多行代碼,終于實(shí)現(xiàn)了類(lèi)似的功能。如果現(xiàn)在有了一個(gè)新的需求,這樣這個(gè)ActionFactory每次都新建一個(gè)類(lèi)的實(shí)例,顯然這對(duì)系統(tǒng)性能不利,考慮到我們的兩個(gè)Action都是線程安全的,修改一下ActionFactory,保持系統(tǒng)中只有一個(gè)Action實(shí)例供其它線程調(diào)用。另外Action對(duì)象創(chuàng)建后,需要做一些初始化工作。修改一下ActionFactory,使其在創(chuàng)建Action實(shí)例之后,隨即就調(diào)用Action.init方法執(zhí)行初始化。Action的處理這樣就差不多了。下面我們來(lái)看看另外一個(gè)Factory

            ……

            往往這些系統(tǒng)開(kāi)發(fā)中最常見(jiàn)的需求,會(huì)導(dǎo)致我們的代碼迅速膨脹,而Spring IoC的出現(xiàn),則大大緩解了這樣的窘境。通過(guò)以上實(shí)例,可以看出,Spring IoC為我們提供了如下幾方面的優(yōu)勢(shì):

            (1)應(yīng)用組件不需要在運(yùn)行時(shí)尋找其協(xié)作者,因此更易于開(kāi)發(fā)和編寫(xiě)應(yīng)用;

            (2)由于借助于IoC容器管理組件的依賴(lài)關(guān)系,使得應(yīng)用的單元測(cè)試和集成測(cè)試更利于展開(kāi);

            (3)通常,在借助于IoC容器關(guān)系業(yè)務(wù)對(duì)象的前提下,很少需要使用具體IoC容器提供的API,這使得集成現(xiàn)有的遺留應(yīng)用成為可能。

            因此,通過(guò)使用IoC能夠降低組件之間的耦合度,最終,能夠提高類(lèi)的重用性,利于測(cè)試,而且更利于整個(gè)產(chǎn)品或系統(tǒng)集成和配置。
          2、Spring AOP

            2.1 面向切面編程基礎(chǔ)

            通常,系統(tǒng)由很多組件組成,每個(gè)組件負(fù)責(zé)一部分功能,然而,這些組件也經(jīng)常帶有一些除了核心功能之外的附帶功能 。系統(tǒng)服務(wù)如日志、事務(wù)管理和安全經(jīng)常融入到一些其他功能模塊中。這些系統(tǒng)服務(wù)通常叫做交叉業(yè)務(wù),這是因?yàn)樗鼈兛偸欠植荚谙到y(tǒng)的很多組件中。通過(guò)將這些業(yè)務(wù)分布在多個(gè)組件中,給我們的代碼引入了雙重復(fù)雜性。

            (1) 實(shí)現(xiàn)系統(tǒng)級(jí)業(yè)務(wù)的代碼在多個(gè)組件中復(fù)制。這意味著如果你要改變這些業(yè)務(wù)邏輯,你就必須到各個(gè)模塊去修改。就算把這些業(yè)務(wù)抽象成一個(gè)獨(dú)立模塊,其它模塊只是調(diào)用它的一個(gè)方法,但是這個(gè)方法調(diào)用也還是分布在很多地方。

            (2) 組件會(huì)因?yàn)槟切┡c自己核心業(yè)務(wù)無(wú)關(guān)的代碼變得雜亂。一個(gè)向地址錄中添加條目的方法應(yīng)該只關(guān)心如何添加地址,而不是關(guān)心它是不是安全或支持事務(wù)的。

            此時(shí),我們?cè)撛趺崔k呢?這正是AOP用得著的地方。AOP幫助我們將這些服務(wù)模塊化,并把它們聲明式地應(yīng)用在需要它們的地方,使得這些組件更加專(zhuān)注于自身業(yè)務(wù),完全不知道其它涉及到的系統(tǒng)服務(wù)。

            這里的概念切面,就是我們要實(shí)現(xiàn)的交叉功能,是應(yīng)用系統(tǒng)模塊化的一個(gè)方面或領(lǐng)域。切面的最常見(jiàn)例子就是日志記錄。日志記錄在系統(tǒng)中到處需要用到,利用繼承來(lái)重用日志模塊是不合適的,這樣,就可以創(chuàng)建一個(gè)日志記錄切面,并且使用AOP在系統(tǒng)中應(yīng)用。下圖展示了切面應(yīng)用方式


          圖表 1 應(yīng)用切面

            其中,通知Advice是切面的實(shí)際實(shí)現(xiàn)。連接點(diǎn)Joinpoint是應(yīng)用程序執(zhí)行過(guò)程中插入切面的地點(diǎn),這個(gè)地點(diǎn)可以是方法調(diào)用,異常拋出,甚至可以是要修改的字段,切面代碼在這些地方插入到你的應(yīng)用流程中,添加新的行為。切入點(diǎn)Pointcut定義了Advice應(yīng)該應(yīng)用在那些連接點(diǎn),通常通過(guò)指定類(lèi)名和方法名,或者匹配類(lèi)名和方法名式樣的正則表達(dá)式來(lái)指定切入點(diǎn)。

            2.2 AOP在Spring中的實(shí)現(xiàn)

            基于AOP,業(yè)界存在各種各樣的AOP實(shí)現(xiàn),比如,JBoss AOP、Spring AOP、AspectJ、Aspect Werkz等。各自實(shí)現(xiàn)的功能也不一樣。AOP實(shí)現(xiàn)的強(qiáng)弱在很大程度上取決于連接點(diǎn)模型。目前,Spring只支持方法級(jí)的連接點(diǎn)。這和一些其他AOP框架不一樣,如AspectJ和JBoss,它們還提供了屬性接入點(diǎn),這樣可以防止你創(chuàng)建特別細(xì)致的通知,如對(duì)更新對(duì)象屬性值進(jìn)行攔截。然而,由于Spring關(guān)注于提供一個(gè)實(shí)現(xiàn)J2EE服務(wù)的框架,所以方法攔截可以滿(mǎn)足大部分要求,而且Spring的觀點(diǎn)是屬性攔截破壞了封裝,讓Advice觸發(fā)在屬性值改變而不是方法調(diào)用上無(wú)疑是破壞了這個(gè)概念。

            Spring的AOP框架的關(guān)鍵點(diǎn)如下:

            (1)Spring實(shí)現(xiàn)了AOP聯(lián)盟接口。在Spring AOP中,存在如下幾種通知(Advice)類(lèi)型

            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)方法拋出異常時(shí)調(diào)用,涉及接口org.springframework.aop.MethodBeforeAdvice;

            Around通知:攔截對(duì)目標(biāo)對(duì)象方法調(diào)用,涉及接口為org.aopalliance.intercept.MethodInterceptor。

            (2)用java編寫(xiě)Spring通知,并在Spring的配置文件中,定義在什么地方應(yīng)用通知的切入點(diǎn)。

            (3)Spring的運(yùn)行時(shí)通知對(duì)象。代理Bean只有在第一次被應(yīng)用系統(tǒng)需要的時(shí)候才被創(chuàng)建。如果你使用的是ApplicationContext,代理對(duì)象在BeanFactory載入所有Bean的時(shí)候被創(chuàng)建。Spring有兩種代理創(chuàng)建方式。如果目標(biāo)對(duì)象實(shí)現(xiàn)了一個(gè)或多個(gè)接口暴露的方法,Spring將使用JDK的java.lang.reflect.Proxy類(lèi)創(chuàng)建代理。這個(gè)類(lèi)讓Spring動(dòng)態(tài)產(chǎn)生一個(gè)新的類(lèi),它實(shí)現(xiàn)所需的接口,織入了通知,并且代理對(duì)目標(biāo)對(duì)象的所有請(qǐng)求。如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)任何接口,Spring使用CGLIB庫(kù)生成目標(biāo)對(duì)象的子類(lèi)。在創(chuàng)建這個(gè)子類(lèi)的時(shí)候,Spring將通知織入,并且將對(duì)目標(biāo)對(duì)象的調(diào)用委托給這個(gè)子類(lèi)。此時(shí),需要將Spring發(fā)行包lib/cglib目錄下的JAR文件發(fā)布到應(yīng)用系統(tǒng)中。

            2.3 Spring AOP的優(yōu)勢(shì)

            借助于Spring AOP,Spring IoC能夠很方便的使用到非常健壯、靈活的企業(yè)級(jí)服務(wù),是因?yàn)镾pring AOP能夠提供如下幾方面的優(yōu)勢(shì):

            (1)允許開(kāi)發(fā)者使用聲明式企業(yè)服務(wù),比如事務(wù)服務(wù)、安全性服務(wù);EJB開(kāi)發(fā)者都知道,EJB組件能夠使用J2EE容器提供的聲明式服務(wù),但是這些服務(wù)要借助于EJB容器,而Spring AOP卻不需要EJB容器,借助于Spring的事務(wù)抽象框架就可以這些服務(wù)。

            (2)開(kāi)發(fā)者可以開(kāi)發(fā)滿(mǎn)足業(yè)務(wù)需求的自定義切面;

            (3)開(kāi)發(fā)Spring AOP Advice很方便。因?yàn)檫@些AOP Advice僅是POJO類(lèi),借助于Spring提供的ProxyFactoryBean,能夠快速的搭建Spring AOP Advice。

            3、結(jié)語(yǔ)

            本文詳細(xì)闡述了Spring背后的IoC原理和AOP技術(shù),以實(shí)際成功項(xiàng)目為背景,抽取簡(jiǎn)短片斷,展示了Spring架構(gòu)J2EE應(yīng)用系統(tǒng)的原理。Spring IoC借助于依賴(lài)注入機(jī)制,減輕了組件之間的依賴(lài)關(guān)系,同時(shí)也大大提高了組件的可移植性,組件得到了更多的重用機(jī)會(huì)。借助于Spring AOP,使得開(kāi)發(fā)者能聲明式、基于元數(shù)據(jù)訪問(wèn)企業(yè)級(jí)服務(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)對(duì)抗,EJB不再是必需品。Spring已經(jīng)沖入了J2EE的核心,將引領(lǐng)整個(gè)J2EE開(kāi)發(fā)、架構(gòu)的方向。

          posted @ 2008-08-25 18:08 金家寶 閱讀(235) | 評(píng)論 (0)編輯 收藏

          GoF設(shè)計(jì)模式

          著名的EJB領(lǐng)域頂尖的專(zhuān)家Richard Monson-Haefel在其個(gè)人網(wǎng)站:www.EJBNow.com中極力推薦的GoF的《設(shè)計(jì)模式》,原文如下:

          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.

          翻譯: 很多程序員在讀完這本書(shū),宣布自己相當(dāng)于經(jīng)歷了一次"主顯節(jié)"(紀(jì)念那穌降生和受洗的雙重節(jié)日),如果你從來(lái)沒(méi)有讀過(guò)這本書(shū),你會(huì)在你的程序教育生涯里存在一個(gè)嚴(yán)重裂溝,所以你應(yīng)該立即挽救彌補(bǔ)!

          可以這么說(shuō):GoF設(shè)計(jì)模式是程序員真正掌握面向?qū)ο蠛诵乃枷氲谋匦拚n。雖然你可能已經(jīng)通過(guò)了SUN的很多令人炫目的技術(shù)認(rèn)證,但是如果你沒(méi)有學(xué)習(xí)掌握GoF設(shè)計(jì)模式,只能說(shuō)明你還是一個(gè)技工。

          在瀏覽《Thingking in Java》(第一版)時(shí),你是不是覺(jué)得好象這還是一本Java基礎(chǔ)語(yǔ)言書(shū)籍?但又不純粹是,因?yàn)檫@本書(shū)的作者將面向?qū)ο蟮乃枷肭擅畹娜诤显贘ava的具體技術(shù)上,潛移默化的讓你感覺(jué)到了一種新的語(yǔ)言和新的思想方式的誕生。

          但是讀完這本書(shū),你對(duì)書(shū)中這些蘊(yùn)含的思想也許需要一種更明晰更系統(tǒng)更透徹的了解和掌握,那么你就需要研讀GoF的《設(shè)計(jì)模式》了。

          《Thingking in Java》(第一版中文)是這樣描述設(shè)計(jì)模式的:他在由Gamma, Helm和Johnson Vlissides簡(jiǎn)稱(chēng)Gang of Four(四人幫),縮寫(xiě)GoF編著的《Design Patterns》一書(shū)中被定義成一個(gè)“里程碑”。事實(shí)上,那本書(shū)現(xiàn)在已成為幾乎所有OOP(面向?qū)ο蟪绦蛟O(shè)計(jì))程序員都必備的參考書(shū)。(在國(guó)外是如此)。

          GoF的《設(shè)計(jì)模式》是所有面向?qū)ο笳Z(yǔ)言(C++ Java C#)的基礎(chǔ),只不過(guò)不同的語(yǔ)言將之實(shí)現(xiàn)得更方便地使用。

          GOF的設(shè)計(jì)模式是一座"橋"
          就Java語(yǔ)言體系來(lái)說(shuō),GOF的設(shè)計(jì)模式是Java基礎(chǔ)知識(shí)和J2EE框架知識(shí)之間一座隱性的"橋"。

          會(huì)Java的人越來(lái)越多,但是一直徘徊在語(yǔ)言層次的程序員不在少數(shù),真正掌握J(rèn)ava中接口或抽象類(lèi)的應(yīng)用不是很多,大家經(jīng)常以那些技術(shù)只適合大型項(xiàng)目為由,避開(kāi)或忽略它們,實(shí)際中,Java的接口或抽象類(lèi)是真正體現(xiàn)Java思想的核心所在,這些你都將在GoF的設(shè)計(jì)模式里領(lǐng)略到它們變幻無(wú)窮的魔力。

          GoF的設(shè)計(jì)模式表面上好象也是一種具體的"技術(shù)",而且新的設(shè)計(jì)模式不斷在出現(xiàn),設(shè)計(jì)模式自有其自己的發(fā)展軌道,而這些好象和J2EE .Net等技術(shù)也無(wú)關(guān)!

          實(shí)際上,GoF的設(shè)計(jì)模式并不是一種具體"技術(shù)",它講述的是思想,它不僅僅展示了接口或抽象類(lèi)在實(shí)際案例中的靈活應(yīng)用和智慧,讓你能夠真正掌握接口或抽象類(lèi)的應(yīng)用,從而在原來(lái)的Java語(yǔ)言基礎(chǔ)上躍進(jìn)一步,更重要的是,GoF的設(shè)計(jì)模式反復(fù)向你強(qiáng)調(diào)一個(gè)宗旨:要讓你的程序盡可能的可重用。

          這其實(shí)在向一個(gè)極限挑戰(zhàn):軟件需求變幻無(wú)窮,計(jì)劃沒(méi)有變化快,但是我們還是要尋找出不變的東西,并將它和變化的東西分離開(kāi)來(lái),這需要非常的智慧和經(jīng)驗(yàn)。

          而GoF的設(shè)計(jì)模式是在這方面開(kāi)始探索的一塊里程碑。

          J2EE等屬于一種框架軟件,什么是框架軟件?它不同于我們以前接觸的Java API等,那些屬于Toolkist(工具箱),它不再被動(dòng)的被使用,被調(diào)用,而是深刻的介入到一個(gè)領(lǐng)域中去,J2EE等框架軟件設(shè)計(jì)的目的是將一個(gè)領(lǐng)域中不變的東西先定義好,比如整體結(jié)構(gòu)和一些主要職責(zé)(如數(shù)據(jù)庫(kù)操作 事務(wù)跟蹤 安全等),剩余的就是變化的東西,針對(duì)這個(gè)領(lǐng)域中具體應(yīng)用產(chǎn)生的具體不同的變化需求,而這些變化東西就是J2EE程序員所要做的。

          由此可見(jiàn),設(shè)計(jì)模式和J2EE在思想和動(dòng)機(jī)上是一脈相承,只不過(guò)

          1.設(shè)計(jì)模式更抽象,J2EE是具體的產(chǎn)品代碼,我們可以接觸到,而設(shè)計(jì)模式在對(duì)每個(gè)應(yīng)用時(shí)才會(huì)產(chǎn)生具體代碼。

          2.設(shè)計(jì)模式是比J2EE等框架軟件更小的體系結(jié)構(gòu),J2EE中許多具體程序都是應(yīng)用設(shè)計(jì)模式來(lái)完成的,當(dāng)你深入到J2EE的內(nèi)部代碼研究時(shí),這點(diǎn)尤其明顯,因此,如果你不具備設(shè)計(jì)模式的基礎(chǔ)知識(shí)(GoF的設(shè)計(jì)模式),你很難快速的理解J2EE。不能理解J2EE,如何能靈活應(yīng)用?

          3.J2EE只是適合企業(yè)計(jì)算應(yīng)用的框架軟件,但是GoF的設(shè)計(jì)模式幾乎可以用于任何應(yīng)用!因此GoF的設(shè)計(jì)模式應(yīng)該是J2EE的重要理論基礎(chǔ)之一。

          所以說(shuō),GoF的設(shè)計(jì)模式是Java基礎(chǔ)知識(shí)和J2EE框架知識(shí)之間一座隱性的"橋"。為什么說(shuō)隱性的?

          GOF的設(shè)計(jì)模式是一座隱性的"橋"
          因?yàn)楹芏嗳藳](méi)有注意到這點(diǎn),學(xué)完Java基礎(chǔ)語(yǔ)言就直接去學(xué)J2EE,有的甚至鴨子趕架,直接使用起Weblogic等具體J2EE軟件,一段時(shí)間下來(lái),發(fā)現(xiàn)不過(guò)如此,挺簡(jiǎn)單好用,但是你真正理解J2EE了嗎?你在具體案例中的應(yīng)用是否也是在延伸J2EE的思想?

          如果你不能很好的延伸J2EE的思想,那你豈非是大炮轟蚊子,認(rèn)識(shí)到J2EE不是適合所有場(chǎng)合的人至少是明智的,但我們更需要將J2EE用對(duì)地方,那么只有理解J2EE此類(lèi)框架軟件的精髓,那么你才能真正靈活應(yīng)用Java解決你的問(wèn)題,甚至構(gòu)架出你自己企業(yè)的框架來(lái)。(我們不能總是使用別人設(shè)定好的框架,為什么不能有我們自己的框架?)

          因此,首先你必須掌握GoF的設(shè)計(jì)模式。雖然它是隱性,但不是可以越過(guò)的。

          ?

          關(guān)于本站“設(shè)計(jì)模式”

          Java提供了豐富的API,同時(shí)又有強(qiáng)大的數(shù)據(jù)庫(kù)系統(tǒng)作底層支持,那么我們的編程似乎變成了類(lèi)似積木的簡(jiǎn)單"拼湊"和調(diào)用,甚至有人提倡"藍(lán)領(lǐng)程序員",這些都是對(duì)現(xiàn)代編程技術(shù)的不了解所至.

          在真正可復(fù)用的面向?qū)ο缶幊讨?GoF的《設(shè)計(jì)模式》為我們提供了一套可復(fù)用的面向?qū)ο蠹夹g(shù),再配合Refactoring(重構(gòu)方法),所以很少存在簡(jiǎn)單重復(fù)的工作,加上Java代碼的精煉性和面向?qū)ο蠹儩嵭?設(shè)計(jì)模式是java的靈魂),編程工作將變成一個(gè)讓你時(shí)刻體驗(yàn)創(chuàng)造快感的激動(dòng)人心的過(guò)程.

          為能和大家能共同探討"設(shè)計(jì)模式",我將自己在學(xué)習(xí)中的心得寫(xiě)下來(lái),只是想幫助更多人更容易理解GoF的《設(shè)計(jì)模式》。由于原著都是以C++為例, 以Java為例的設(shè)計(jì)模式基本又都以圖形應(yīng)用為例,而我們更關(guān)心Java在中間件等服務(wù)器方面的應(yīng)用,因此,本站所有實(shí)例都是非圖形應(yīng)用,并且順帶剖析Jive論壇系統(tǒng).同時(shí)為降低理解難度,盡量避免使用UML圖.

          如果你有一定的面向?qū)ο缶幊探?jīng)驗(yàn),你會(huì)發(fā)現(xiàn)其中某些設(shè)計(jì)模式你已經(jīng)無(wú)意識(shí)的使用過(guò)了;如果你是一個(gè)新手,那么從開(kāi)始就培養(yǎng)自己良好的編程習(xí)慣(讓你的的程序使用通用的模式,便于他人理解;讓你自己減少重復(fù)性的編程工作),這無(wú)疑是成為一個(gè)優(yōu)秀程序員的必備條件.

          整個(gè)設(shè)計(jì)模式貫穿一個(gè)原理:面對(duì)接口編程,而不是面對(duì)實(shí)現(xiàn).目標(biāo)原則是:降低耦合,增強(qiáng)靈活性.

          posted @ 2008-07-07 11:05 金家寶 閱讀(280) | 評(píng)論 (0)編輯 收藏

          什么是MIS

          所謂MIS(管理信息系統(tǒng)--Management Information System)系統(tǒng),是一個(gè)由人、計(jì)算機(jī)及其他外圍設(shè)備等組成的能進(jìn)行信息的收集、傳遞、存貯、加工、維護(hù)和使用的系統(tǒng)。它是一門(mén)新興的科學(xué),其主要任務(wù)是最大限度的利用現(xiàn)代計(jì)算機(jī)及網(wǎng)絡(luò)通訊技術(shù)加強(qiáng)企業(yè)的信息管理,通過(guò)對(duì)企業(yè)擁有的人力、物力、財(cái)力、設(shè)備、技術(shù)等資源的調(diào)查了解,建立正確的數(shù)據(jù),加工處理并編制成各種信息資料及時(shí)提供給管理人員,以便進(jìn)行正確的決策,不斷提高企業(yè)的管理水平和經(jīng)濟(jì)效益。目前,企業(yè)的計(jì)算機(jī)網(wǎng)絡(luò)已成為企業(yè)進(jìn)行技術(shù)改造及提高企業(yè)管理水平的重要手段。隨著我國(guó)與世界信息高速公路的接軌,企業(yè)通過(guò)計(jì)算機(jī)網(wǎng)絡(luò)獲得信息必將為企業(yè)帶來(lái)巨大的經(jīng)濟(jì)效益和社會(huì)效益,企業(yè)的辦公及管理都將朝著高效、快速、無(wú)紙化的方向發(fā)展。MIS系統(tǒng)通常用于系統(tǒng)決策,例如,可以利用MIS系統(tǒng)找出目前迫切需要解決的問(wèn)題,并將信息及時(shí)反饋給上層管理人員,使他們了解當(dāng)前工作發(fā)展的進(jìn)展或不足。換句話說(shuō),MIS系統(tǒng)的最終目的是使管理人員及時(shí)了解公司現(xiàn)狀,把握將來(lái)的發(fā)展路徑。

            一個(gè)完整的MIS應(yīng)包括:輔助決策系統(tǒng)(DSS)、工業(yè)控制系統(tǒng)(IPC)、辦公自動(dòng)化系統(tǒng)(OA)以及數(shù)據(jù)庫(kù)、模型庫(kù)、方法庫(kù)、知識(shí)庫(kù)和與上級(jí)機(jī)關(guān)及外界交換信息的接口。其中,特別是辦公自動(dòng)化系統(tǒng)(OA)、與上級(jí)機(jī)關(guān)及外界交換信息等都離不開(kāi)Intranet的應(yīng)用。可以這樣說(shuō),現(xiàn)代企業(yè)MIS不能沒(méi)有 Intranet,但I(xiàn)ntranet的建立又必須依賴(lài)于MIS的體系結(jié)構(gòu)和軟硬件環(huán)境。

            傳統(tǒng)的MIS系統(tǒng)的核心是CS (Client/Server——客戶(hù)端/服務(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)依賴(lài)于專(zhuān)門(mén)的操作環(huán)境,這意味著操作者的活動(dòng)空間受到極大限制;而B(niǎo)S架構(gòu)則不需要專(zhuān)門(mén)的操作環(huán)境,在任何地方,只要能上網(wǎng),就能夠操作MIS系統(tǒng),這其中的優(yōu)劣差別是不言而喻的。

            基于Internet上的 MIS系統(tǒng)是對(duì)傳統(tǒng)MIS系統(tǒng)概念上的擴(kuò)展,它不僅可以用于高層決策,而且可以用于進(jìn)行普通的商務(wù)管理。通過(guò)用戶(hù)的具名登錄(或匿名登錄),以及相應(yīng)的權(quán)限控制,可以實(shí)現(xiàn)在遠(yuǎn)端對(duì)系統(tǒng)的瀏覽、查詢(xún)、控制和審閱。隨著Internet的擴(kuò)展,現(xiàn)有的公司和學(xué)校不再局限于物理的有形的真實(shí)的地域,網(wǎng)絡(luò)本身成為事實(shí)上發(fā)展的空間。基于Internet上的MIS系統(tǒng),彌補(bǔ)了傳統(tǒng)MIS系統(tǒng)的不足,充分體現(xiàn)了現(xiàn)代網(wǎng)絡(luò)時(shí)代的特點(diǎn)。隨著Internet技術(shù)的高速發(fā)展,因特網(wǎng)必將成為人類(lèi)新社會(huì)的技術(shù)基石。基于Internet的MIS系統(tǒng)必將成為網(wǎng)絡(luò)時(shí)代的新一代管理信息系統(tǒng),前景極為樂(lè)觀

          posted @ 2008-07-05 19:20 金家寶 閱讀(222) | 評(píng)論 (0)編輯 收藏

          開(kāi)發(fā)者版本:你屬于哪個(gè)版本的程序員?

          國(guó)外開(kāi)發(fā)者博客中有一篇有趣的文章,將程序員按水平像軟件版本號(hào)那樣劃分為不同的版本。相對(duì)于在招聘時(shí)分為初級(jí),中級(jí),高級(jí)程序員,直接表明需要某種語(yǔ)言N版本的程序員或許更方便直接。根據(jù)作者的觀點(diǎn),可將WEB開(kāi)發(fā)者大致分為以下幾個(gè)版本:

          Alpha:閱讀過(guò)一些專(zhuān)業(yè)書(shū)籍,大多數(shù)能用Dreamweaver或者FrontPage幫朋友制作一些Web頁(yè)面。但在他們熟練掌握HTML代碼以前,你大概不會(huì)雇傭他們成為職業(yè)的WEB制作人員。

          Beta:已經(jīng)比較擅長(zhǎng)整合站點(diǎn)頁(yè)面了,在HTML技巧方面也有一定造詣,但還是用Tables來(lái)制作頁(yè)面,不了解CSS,在面對(duì)動(dòng)態(tài)頁(yè)面或數(shù)據(jù)庫(kù)連接時(shí)還是底氣不足。

          Pre Version 1 (0.1):比Beta版的開(kāi)發(fā)者水平要高。熟悉HTML,開(kāi)始了解CSS是如何運(yùn)作的,懂一點(diǎn)JavaScript,但還是基于業(yè)余水準(zhǔn),逐步開(kāi)始關(guān)心動(dòng)態(tài)站點(diǎn)搭建和數(shù)據(jù)庫(kù)連接的知識(shí)。這個(gè)版本的WEB開(kāi)發(fā)人員還遠(yuǎn)不能成為雇主眼中的香餑餑。

          1.0: 能夠基本把控整個(gè)站點(diǎn)開(kāi)發(fā),針對(duì)每個(gè)問(wèn)題盡可能的找到最直接的解決辦法。但對(duì)可測(cè)性,可擴(kuò)展性以及在不同(層)框架下如何選擇最合適的WEB設(shè)計(jì)工具尚無(wú)概念。這個(gè)版本的WEB開(kāi)發(fā)者有良好的技術(shù)基礎(chǔ),需要有進(jìn)一步的幫助和指導(dǎo)。



          2.0:懂面向?qū)ο蟮木幊陶Z(yǔ)言,理解分層開(kāi)發(fā)的必要性,關(guān)注代碼分離,對(duì)問(wèn)題尋找更完美的解決方法,偶然也會(huì)考慮設(shè)計(jì)模式的問(wèn)題,但對(duì)此仍然概念不清。屬于優(yōu)秀的初級(jí)開(kāi)發(fā)者,能完成較松散的代碼開(kāi)發(fā)(相對(duì)大型嚴(yán)謹(jǐn)?shù)恼军c(diǎn)開(kāi)發(fā)而言),在面對(duì)較復(fù)雜問(wèn)題尋找解決辦法時(shí)需要周邊人的幫助。

          3.0:開(kāi)始較為深入的理解面向?qū)ο缶幊毯驮O(shè)計(jì)模式,了解他們的用途,當(dāng)看到好的設(shè)計(jì)模式時(shí)能看透其本質(zhì),逐步關(guān)注分層的架構(gòu)解決辦法和可測(cè)試性。理解不同的開(kāi)發(fā)語(yǔ)言并能說(shuō)出他們的異同(例如各自的優(yōu)勢(shì))。屬于優(yōu)秀的中級(jí)別開(kāi)發(fā)者,雇主也確信他們最終能找到問(wèn)題的解決辦法,這個(gè)版本的人可以給1.0和2.0的開(kāi)發(fā)者以指導(dǎo)。但他們對(duì)架構(gòu)的理解仍然不夠清晰,值得一提的是,只要給予一些指導(dǎo),他們能很快理解并熟記做出的決定,以及選定方案的優(yōu)勢(shì)所在。

          4.0:
          理解模式,重視用戶(hù)的反饋。著手研究方法論,架構(gòu)設(shè)計(jì)和軟件開(kāi)發(fā)的最佳入口。頭腦中已經(jīng)形成了超越開(kāi)發(fā)語(yǔ)言,技術(shù)架構(gòu)的整體方案,可根據(jù)需求解構(gòu)程序。能從理論的角度,不同模式如何融合成最佳形態(tài),將多種X-驅(qū)動(dòng)的模式應(yīng)用到不同的方案中。是精通多語(yǔ)言的高手,理解不同系統(tǒng)和方法論的細(xì)微差別,屬于高級(jí)程序員。這個(gè)級(jí)別的人能夠輕易的輔導(dǎo)2.0和3.0的程序員,將他們推向更高的級(jí)別。

          5.0:從系統(tǒng)的角度考慮問(wèn)題。對(duì)各種系統(tǒng)結(jié)構(gòu)有深入研究,能對(duì)整個(gè)代碼架構(gòu)中的問(wèn)題進(jìn)行改進(jìn)。在團(tuán)隊(duì)粘合性以及代碼安全性方面有杰出貢獻(xiàn)。對(duì)1.0到4.0版本的開(kāi)發(fā)人員出現(xiàn)的問(wèn)題能及時(shí)察覺(jué),讓整個(gè)團(tuán)隊(duì)保持積極性且保持興奮的狀態(tài)創(chuàng)建軟件解決辦法。舉例來(lái)說(shuō),他們總是對(duì)新的技術(shù)和信息保持饑渴狀態(tài),試圖用最簡(jiǎn)便的方案解決開(kāi)發(fā)任務(wù)。在整個(gè)IT團(tuán)隊(duì)中獲得信任,屬于高級(jí)程序員和架構(gòu)師。

          那么,您屬于哪個(gè)版本的程序員呢?

          posted @ 2008-06-27 10:47 金家寶 閱讀(248) | 評(píng)論 (0)編輯 收藏

          Eclipse官方網(wǎng)站已經(jīng)正式宣布 Eclipse 3.4發(fā)布

          感謝Wendal,匿名人士的投遞
          Eclipse官方網(wǎng)站已經(jīng)正式宣布 Eclipse 3.4發(fā)布,代號(hào)為ganymede (Ganymede (英語(yǔ)發(fā)音"GAN uh meed")為最大的木星已知衛(wèi)星,也是第七顆發(fā)現(xiàn)的木星衛(wèi)星,在伽利略發(fā)現(xiàn)的衛(wèi)星中離木星第三近,在希臘神話中 Ganymede是一個(gè)特洛伊美人的男孩(一個(gè)美少男),被宙斯帶去給眾神斟酒)。

          ?

          關(guān)注Eclipse項(xiàng)目的開(kāi)發(fā)者朋友們可以下載3.4版本嘗試一下,在JavaEye上還專(zhuān)門(mén)介紹一個(gè)很酷的Eclipse3.4帶的實(shí)時(shí)結(jié)對(duì)編程插件

          目前3.4版本是Eclipse項(xiàng)目發(fā)布的10周年慶典版;至今Eclipse項(xiàng)目共有23個(gè)子項(xiàng)目。此次發(fā)布的Ganymede 版本引入不少亮點(diǎn),其中包括新的p2平臺(tái)(provisioning platform),點(diǎn)擊查看p2的介紹、新增的Equinox(OSGi實(shí)現(xiàn))安全方面的特性、全新的Ecore建模工具、支持SOA等。

          posted @ 2008-06-26 09:01 金家寶 閱讀(817) | 評(píng)論 (1)編輯 收藏

          [轉(zhuǎn)]Java語(yǔ)言的細(xì)節(jié)

          Java作為一門(mén)優(yōu)秀的面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言,正在被越來(lái)越多的人使用。本文試圖列出作者在實(shí)際開(kāi)發(fā)中碰到的一些Java語(yǔ)言的容易被人忽視的細(xì)節(jié),希望能給正在學(xué)習(xí)Java語(yǔ)言的人有所幫助。
          ?
          1,拓寬數(shù)值類(lèi)型會(huì)造成精度丟失嗎?
          ??? Java語(yǔ)言的8種基本數(shù)據(jù)類(lèi)型中7種都可以看作是數(shù)值類(lèi)型,我們知道對(duì)于數(shù)值類(lèi)型的轉(zhuǎn)換有一個(gè)規(guī)律:從窄范圍轉(zhuǎn)化成寬范圍能夠自動(dòng)類(lèi)型轉(zhuǎn)換,反之則必須強(qiáng)制轉(zhuǎn)換。請(qǐng)看下圖:
          byte-->short-->int-->long-->float-->double
          char-->int
          我們把順箭頭方向的轉(zhuǎn)化叫做拓寬類(lèi)型,逆箭頭方向的轉(zhuǎn)化叫做窄化類(lèi)型。一般我們認(rèn)為因?yàn)轫樇^方向的轉(zhuǎn)化不會(huì)有數(shù)據(jù)和精度的丟失,所以Java語(yǔ)言允許自動(dòng)轉(zhuǎn)化,而逆箭頭方向的轉(zhuǎn)化可能會(huì)造成數(shù)據(jù)和精度的丟失,所以Java語(yǔ)言要求程序員在程序中明確這種轉(zhuǎn)化,也就是強(qiáng)制轉(zhuǎn)換。那么拓寬類(lèi)型就一定不會(huì)造成數(shù)據(jù)和精度丟失嗎?請(qǐng)看下面代碼:
          int i=2000000000;
          int num=0;
          for(float f=i;f<i+50;f++){
          ??? num++;
          }
          System.out.println(num);
          請(qǐng)考察以上代碼輸出多少?
          如果你回答50 ,那么請(qǐng)運(yùn)行一下,結(jié)果會(huì)讓你大吃一驚!沒(méi)錯(cuò),輸出結(jié)果是0,難道這個(gè)循環(huán)根本就沒(méi)有執(zhí)行哪怕一次?確實(shí)如此,如果你還不死心,我?guī)憧匆粋€(gè)更詫異的現(xiàn)象,運(yùn)行以下代碼,看輸出什么?
          int i=2000000000;
          float f1=i;
          float f2=i+50;
          System.out.println(f1==f2);
          ??? 哈哈,你快要不相信你的眼睛了,結(jié)果竟然是true;難道f1和f2是相等的嗎?是的,就是這樣,這也就能解釋為什么上一段代碼輸出的結(jié)果是0,而不是50了。那為什么會(huì)這樣呢?關(guān)鍵原因在于你將int值自動(dòng)提升為float時(shí)發(fā)生了數(shù)據(jù)精度的丟失,i的初始值是2000000000,這個(gè)值非常接近Integer.MAX_VALUE,因此需要用31位來(lái)精確表示,而float只能提供24位數(shù)據(jù)的精度(另外8位是存儲(chǔ)位權(quán),見(jiàn)IEEE745浮點(diǎn)數(shù)存儲(chǔ)規(guī)則)。所以在這種自動(dòng)轉(zhuǎn)化的過(guò)程中,系統(tǒng)會(huì)將31位數(shù)據(jù)的前24位保留下來(lái),而舍棄掉最右邊的7位,所以不管是2000000000還是2000000050,舍棄掉最右邊7位后得到的值是一樣的。這就是為什么f1==f2的原因了。
          ??? 類(lèi)似的這種數(shù)值拓寬類(lèi)型的過(guò)程中會(huì)造成精度丟失的還有兩種情況,那就是long轉(zhuǎn)化成float和long轉(zhuǎn)化成double,所以在使用的時(shí)候一定要小心。
          ?
          2,i=i+1和i+=1完全等價(jià)嗎?
          ??? 可能有很多程序員認(rèn)為i+=1只是i=i+1的簡(jiǎn)寫(xiě)方式,其實(shí)不然,它們一個(gè)使用簡(jiǎn)單賦值運(yùn)算,一個(gè)使用復(fù)合賦值運(yùn)算,而簡(jiǎn)單賦值運(yùn)算和復(fù)合賦值運(yùn)算的最大差別就在于:復(fù)合賦值運(yùn)算符會(huì)自動(dòng)地將運(yùn)算結(jié)果轉(zhuǎn)型為其左操作數(shù)的類(lèi)型。看看以下的兩種寫(xiě)法,你就知道它們的差別在哪兒了:
          ? (1) byte i=5;
          ????? i+=1;
          ? (2) byte i=5;
          ????? i=i+1;
          ??? 第一種寫(xiě)法編譯沒(méi)問(wèn)題,而第二種寫(xiě)法卻編譯通不過(guò)。原因就在于,當(dāng)使用復(fù)合賦值運(yùn)算符進(jìn)行操作時(shí),即使右邊算出的結(jié)果是int類(lèi)型,系統(tǒng)也會(huì)將其值轉(zhuǎn)化為左邊的byte類(lèi)型,而使用簡(jiǎn)單賦值運(yùn)算時(shí)沒(méi)有這樣的優(yōu)待,系統(tǒng)會(huì)認(rèn)為將i+1的值賦給i是將int類(lèi)型賦給byte,所以要求強(qiáng)制轉(zhuǎn)換。理解了這一點(diǎn)后,我們?cè)賮?lái)看一個(gè)例子:
          ? byte b=120;
          ? b+=20;
          ? System.out.println("b="+b);
          ? 說(shuō)到這里你應(yīng)該明白了,上例中輸出b的值不是140,而是-116。因?yàn)?20+20的值已經(jīng)超出了一個(gè)byte表示的范圍,而當(dāng)我們使用復(fù)合賦值運(yùn)算時(shí)系統(tǒng)會(huì)自動(dòng)作類(lèi)型的轉(zhuǎn)化,將140強(qiáng)轉(zhuǎn)成byte,所以得到是-116。由此可見(jiàn),在使用復(fù)合賦值運(yùn)算符時(shí)還得小心,因?yàn)檫@種類(lèi)型轉(zhuǎn)換是在不知不覺(jué)中進(jìn)行的,所以得到的結(jié)果就有可能和你的預(yù)想不一樣。
          ?
          3,位移運(yùn)算越界怎么處理
          ??? 考察下面的代碼輸出結(jié)果是多少?
          ??? int a=5;
          ??? System.out.println(a<<33);
          ??? 按照常理推測(cè),把a(bǔ)左移33位應(yīng)該將a的所有有效位都移出去了,那剩下的都是零啊,所以輸出結(jié)果應(yīng)該是0才對(duì)啊,可是執(zhí)行后發(fā)現(xiàn)輸出結(jié)果是10,為什么呢?因?yàn)镴ava語(yǔ)言對(duì)位移運(yùn)算作了優(yōu)化處理,Java語(yǔ)言對(duì)a<<b轉(zhuǎn)化為a<<(b%32)來(lái)處理,所以當(dāng)要移位的位數(shù)b超過(guò)32時(shí),實(shí)際上移位的位數(shù)是b%32的值,那么上面的代碼中a<<33相當(dāng)于a<<1,所以輸出結(jié)果是10。
          ?
          4,判斷奇數(shù)
          ? 以下的方法判斷某個(gè)整數(shù)是否是奇數(shù),考察是否正確:
          ?? public boolean isOdd(int n){
          ?????? return (n%2==1);
          ?? }
          ?? 很多人認(rèn)為上面的代碼沒(méi)問(wèn)題,但實(shí)際上這段代碼隱藏著一個(gè)非常大的BUG,當(dāng)n的值是正整數(shù)時(shí),以上的代碼能夠得到正確結(jié)果,但當(dāng)n的值是負(fù)整數(shù)時(shí),以上方法不能做出正確判斷。例如,當(dāng)n=-3時(shí),以上方法返回false。因?yàn)楦鶕?jù)Java語(yǔ)言規(guī)范的定義,Java語(yǔ)言里的求余運(yùn)算符(%)得到的結(jié)果與運(yùn)算符左邊的值符號(hào)相同,所以,-3%2的結(jié)果是-1,而不是1。那么上面的方法正確的寫(xiě)法應(yīng)該是:
          ?? public boolean isOdd(int n){
          ?????? return (n%2!=0);
          ?? }
          ?
          5,可以讓i!=i嗎?
          在本題中,要求你聲明一個(gè)i值,使得以下程序輸出"No i!=i":
          //在此聲明i,并賦值。
          if(i==i){
          ????? System.out.println("Yes i==i");
          ? }else{
          ????? System.out.println("No i!=i");
          ? }
          ?
          ??? 當(dāng)你看到這個(gè)命題的時(shí)候一定會(huì)以為我瘋了,或者Java語(yǔ)言瘋了。這看起來(lái)是絕對(duì)不可能的,一個(gè)數(shù)怎么可能不等于它自己呢?或許就真的是Java語(yǔ)言瘋了,不信請(qǐng)將i做出以下聲明,再運(yùn)行上面的代碼。
          ? double i=0.0/0.0;
          ??? 上面的代碼輸出"No i!=i",為什么會(huì)這樣呢?關(guān)鍵在0.0/0.0這個(gè)值,在IEEE 754浮點(diǎn)算術(shù)規(guī)則里保留了一個(gè)特殊的值用來(lái)表示一個(gè)不是數(shù)字的數(shù)量。這個(gè)值就是NaN("Not a Number"的縮寫(xiě)),對(duì)于所有沒(méi)有良好定義的浮點(diǎn)計(jì)算都將得到這個(gè)值,比如:0.0/0.0;其實(shí)我們還可以直接使用Double.NaN來(lái)得到這個(gè)值。在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!”嗎?那么,很遺憾的告訴你,不對(duì),Java語(yǔ)言再一次欺騙了你,以上代碼會(huì)輸出“NO!”。為什么會(huì)這樣呢?其實(shí)這是由實(shí)型數(shù)據(jù)的存儲(chǔ)方式?jīng)Q定的。我們知道實(shí)型數(shù)據(jù)在內(nèi)存空間中是近似存儲(chǔ)的,所以2.0-1.1的結(jié)果不是0.9,而是0.88888888889。所以在做實(shí)型數(shù)據(jù)是否相等的判斷時(shí)要非常的謹(jǐn)慎。一般來(lái)說(shuō),我們不建議在代碼中直接判斷兩個(gè)實(shí)型數(shù)據(jù)是否相等,如果一定要比較是否相等的話我們也采用以下方式來(lái)判斷:
          ? if(Math.abs(a-b)<1e-5){
          ???? //相等
          ? }else{
          ??? //不相等
          ? }
          上面的代碼判斷a與b之差的絕對(duì)值是否小于一個(gè)足夠小的數(shù)字,如果是,則認(rèn)為a與b相等,否則,不相等。

          posted @ 2008-06-13 14:41 金家寶 閱讀(492) | 評(píng)論 (2)編輯 收藏

          WEB交互界面易用性設(shè)計(jì)和驗(yàn)收的指導(dǎo)性原則

          隨著企業(yè)intranet和國(guó)際internet的迅速發(fā)展,越來(lái)越多的工作流程,商務(wù)交易,教育、培訓(xùn)、會(huì)議和講座,以及個(gè)人消費(fèi)娛樂(lè)都被轉(zhuǎn)移到所謂的萬(wàn)維網(wǎng)(world wide web,以下簡(jiǎn)稱(chēng)web)上來(lái)了。與此相對(duì)應(yīng)的是交互操作的復(fù)雜性越來(lái)越高。

          隨著browser rver模式的日漸流行,很多操作都是在瀏覽器環(huán)境下的網(wǎng)頁(yè)上完成的,并不是只有失效的鏈接和意外的出錯(cuò)才會(huì)使操作者感到煩惱,即便是一次完整的成功操作過(guò)程,也可能因?yàn)椴僮鞯姆睆?fù)性過(guò)高或者使用上的不方便而給操作者帶來(lái)不愉快的體驗(yàn)。

          本文試圖闡述web交互頁(yè)面設(shè)計(jì)的一些指導(dǎo)性原則,這些原則有利于避免發(fā)生不愉快的操作體驗(yàn)。這些原則是用戶(hù)友好性的,是在完成同一種操作要求下,使用戶(hù)最感到輕松、簡(jiǎn)單、舒適的web交互界面設(shè)計(jì)原則。我們假定我們討論的web頁(yè)面都是功能正常的,符合美學(xué)觀點(diǎn)的。需要說(shuō)明我們討論的原則可能會(huì)和設(shè)計(jì)上的美學(xué)觀點(diǎn)以及既有的功能設(shè)計(jì)有所沖突。如果發(fā)生這種情況,基于“實(shí)用的就是美的”觀點(diǎn),我們會(huì)建議您酌情放棄原先的美學(xué)觀點(diǎn)與功能設(shè)計(jì)。

          一、輸入控件的自動(dòng)聚焦和可用鍵盤(pán)切換輸入焦點(diǎn)

          ??? 使用javascript實(shí)現(xiàn)頁(yè)面加載完成后立即自動(dòng)聚焦(focus)到第一個(gè)輸入控件。可用tab鍵(ie缺省實(shí)現(xiàn))或方向鍵切換聚焦到下一個(gè)輸入控件。

          ??? 輸入控件指web頁(yè)面表單(<form> )中顯式的,需要用戶(hù)進(jìn)行修改、編輯操作的表單元素。對(duì)于這些控件,如果沒(méi)有自動(dòng)聚焦操作,不可避免的出現(xiàn)一次用戶(hù)鼠標(biāo)定位操作(如果用戶(hù)此前處于鍵盤(pán)輸入操作狀態(tài)或鼠標(biāo)定位后需要進(jìn)行鍵盤(pán)輸入操作,實(shí)際上是鍵盤(pán)鼠標(biāo)切換操作)。如果鼠標(biāo)定位后需要進(jìn)行鍵盤(pán)輸入操作,如果不能鍵盤(pán)切換輸入焦點(diǎn),那么不可避免的在切換輸入焦點(diǎn)時(shí)需要反復(fù)的鍵盤(pán)鼠標(biāo)切換操作,這是很繁瑣的。

          ??? 如果實(shí)現(xiàn)了頁(yè)面加載完成即自動(dòng)聚焦到第一個(gè)輸入控件,并且可以鍵盤(pán)切換輸入焦點(diǎn)標(biāo)定位操作,那么對(duì)于用戶(hù)來(lái)說(shuō)整個(gè)頁(yè)面的輸入操作可能都不需要鼠標(biāo)操作,或次數(shù)較少,這是一種便利。畢竟頻繁的鍵盤(pán)鼠標(biāo)切換操作是比較累人的。

          ??? 對(duì)于有輸入欄的對(duì)話框或網(wǎng)頁(yè),在不干預(yù)的情況下就應(yīng)將當(dāng)前控制焦點(diǎn)定位在待輸入的輸入欄上;如果輸入欄在一般情況下不需要更改其中的內(nèi)容,則應(yīng)直接將焦點(diǎn)定在“確定”按鈕上;在幾個(gè)輸入欄之間應(yīng)支持tab,shift+tab切換操作,“確定”和“取消”應(yīng)該是切換操作的終點(diǎn),與具體所在位置無(wú)關(guān)。

          二、可用enter(或ctrl+enter)鍵提交,確保和點(diǎn)擊提交按鈕的效果是相同的

          不要在提交按鈕上加入onclick=”…”這樣的javascript代碼。

          ??? 用enter鍵提交頁(yè)面是原則1的自然延伸,而且這也是瀏覽器所缺省支持的。只所以單獨(dú)列出來(lái)是因?yàn)閷?shí)際上有些設(shè)計(jì)者設(shè)計(jì)的頁(yè)面不能達(dá)到這種效果,結(jié)果導(dǎo)致使用enter鍵提交和點(diǎn)擊“確定”按鈕提交帶來(lái)的效果不一樣。大部分情況下是設(shè)計(jì)者在“確定”按鈕上加入了onclik=”…”這樣的代碼,通過(guò)點(diǎn)擊“確定”按鈕后,會(huì)執(zhí)行一段javascript代碼,比如對(duì)某些hidden類(lèi)型的input元素設(shè)值。而使用enter鍵提交時(shí)就不會(huì)執(zhí)行這段代碼。

          ??? 正確的做法是把這段代碼移到表單標(biāo)簽<form>中,以onsubmit=”…”屬性引入。

          ??? 對(duì)于<textarea>表單元素,它會(huì)消耗enter鍵,因此會(huì)使得enter鍵提交失效。可以引入javascript代碼捕捉ctrl+enter復(fù)合鍵,一旦捕捉到即執(zhí)行表單的submit()方法。對(duì)于需要頻繁提交的場(chǎng)合,比如bbs上,這種代碼是很有必要的。

          三、鼠標(biāo)動(dòng)作提示和回應(yīng)

          ??? 對(duì)用戶(hù)的鼠標(biāo)定位操作,當(dāng)移動(dòng)到可響應(yīng)的位置上時(shí),應(yīng)給予視覺(jué)或聽(tīng)覺(jué)的提示。

          ??? 動(dòng)作回應(yīng)的最簡(jiǎn)單形式就是鼠標(biāo)icon變成手狀。瀏覽器只對(duì)具有href屬性的html標(biāo)簽會(huì)自動(dòng)進(jìn)行這種變換icon的行為。對(duì)于沒(méi)有href屬性(或沒(méi)有設(shè)置href屬性)的標(biāo)簽,可以通過(guò)javascript設(shè)置style屬性的cursor為hand。

          ??? 目標(biāo)區(qū)域發(fā)生變化是更為主動(dòng)的響應(yīng)形式。當(dāng)鼠標(biāo)指針移到目標(biāo)區(qū)域,此時(shí)指針圖形改變或文字顏色發(fā)生改變均能較大的減輕用戶(hù)搜索定位目標(biāo)區(qū)域的注意力負(fù)擔(dān)。在按鈕上增添直觀的圖形,盡可能的增大按鈕面積;按鈕間保持適當(dāng)?shù)木嚯x,太近增加了用戶(hù)區(qū)別它們之間界限以防誤操作的負(fù)擔(dān),太遠(yuǎn)增加了用戶(hù)搜索定位按鈕的負(fù)擔(dān)。

          四、盡可能早的在客戶(hù)端完成輸入數(shù)據(jù)合法性驗(yàn)證

          ??? 輸入數(shù)據(jù)的合法性檢驗(yàn)應(yīng)該在客戶(hù)端使用javascript進(jìn)行驗(yàn)證。除非驗(yàn)證只能在服務(wù)器端完成,否則驗(yàn)證工作應(yīng)在最早能完成的情況下進(jìn)行。

          ??? 在客戶(hù)端完成數(shù)據(jù)合法性驗(yàn)證,可以避免一次服務(wù)器請(qǐng)求和回復(fù)通訊,這種通訊是需要用戶(hù)等待的,如果用戶(hù)等待很長(zhǎng)時(shí)間后從服務(wù)器返回的結(jié)果提示出現(xiàn)的錯(cuò)誤是在輸入時(shí)即可發(fā)現(xiàn)的,那么這種設(shè)計(jì)就是不友好的。諸如密碼長(zhǎng)度限制,用戶(hù)名允許字符限制等等,顯然應(yīng)該在客戶(hù)端提交前就應(yīng)該進(jìn)行驗(yàn)證。

          五、根據(jù)應(yīng)用場(chǎng)景決定在表單頁(yè)面和提交后返回頁(yè)面間是否使用中間過(guò)渡頁(yè)面

          ??? 根據(jù)應(yīng)用場(chǎng)景,決定是否顯示接收表單頁(yè)面(表單頁(yè)面和提交后返回頁(yè)面間的中間過(guò)渡頁(yè)面),以及使用何種方式顯示接收表單頁(yè)面。

          表單頁(yè)面和接收表單頁(yè)面是大部分web交互操作賴(lài)以實(shí)現(xiàn)的配合模式。關(guān)于表單頁(yè)面和接收表單頁(yè)面的相互關(guān)系的設(shè)計(jì),要做如下幾個(gè)方面的考慮。

          1.對(duì)于需要頻繁操作的場(chǎng)合,從操作便利和快捷性出發(fā),盡可能的減少服務(wù)器和客戶(hù)端交互次數(shù),應(yīng)該避免使用中間過(guò)渡頁(yè)面。提交完畢直接返回原來(lái)的表單頁(yè)面或默認(rèn)頁(yè)面。在這種情況下要考慮到數(shù)據(jù)安全和可恢復(fù)性。

          如果因?yàn)橛脩?hù)輸入的數(shù)據(jù)不合格,需要重新輸入,那么,去除中間頁(yè)面,把錯(cuò)誤信息直接顯示在原表單頁(yè)面上的設(shè)計(jì)方式,將是最簡(jiǎn)潔的處理方式。用戶(hù)只需要根據(jù)錯(cuò)誤提示進(jìn)行更正即可。當(dāng)然這樣做稍微增加了編程負(fù)擔(dān)。在表單接收頁(yè)面上需要包含原表單頁(yè)面的內(nèi)容,而且輸入數(shù)據(jù)項(xiàng)都必須用服務(wù)器端代碼或客戶(hù)端javascript設(shè)置成用戶(hù)輸入的值。為了開(kāi)發(fā)快捷,可以這樣做:表單頁(yè)面和接收表單頁(yè)面用同一個(gè)服務(wù)器端腳本頁(yè)面實(shí)現(xiàn)。這個(gè)頁(yè)面按如下流程完成原來(lái)兩個(gè)頁(yè)面的工作:

          頁(yè)面腳本初始化

          檢查“提交”變量是否設(shè)置

          ┠已設(shè)置,做數(shù)據(jù)驗(yàn)證

          ┃ ┠驗(yàn)證通過(guò)->業(yè)務(wù)邏輯處理->使用包含頁(yè)面方式或重定向方式返回到特定頁(yè)面

          ┃ ┗驗(yàn)證不通過(guò)->保存用戶(hù)輸入的數(shù)據(jù)->退出表單提交處理到表單頁(yè)面流程中

          ┗未設(shè)置,做表單頁(yè)面流程,如有來(lái)自提交流程中產(chǎn)生的用戶(hù)輸入數(shù)據(jù),則顯示出來(lái)

          其中,使用包含頁(yè)面方式返回到特定頁(yè)面可以避免一次客戶(hù)端重定向過(guò)程,比客戶(hù)端重定向過(guò)程還要快捷和穩(wěn)定一些。但是有些情況下因?yàn)榇a變量沖突或其他原因,使用包含頁(yè)面方式可能并不方便,這時(shí)候可以使用服務(wù)器端重定向技術(shù),在asp里是server.transfer方法,在java servlet里是requestdispatcher.forward()方法。不要使用response.redirect或者h(yuǎn)ttpservletresponse.sendredirect()這種客戶(hù)端http重定向方法。不使用中間過(guò)渡頁(yè)面也就意味著用戶(hù)不能后退瀏覽原先已經(jīng)填好的表單頁(yè)面,因?yàn)槭褂玫氖峭粋€(gè)url。所以在驗(yàn)證不通過(guò)情況下保存用戶(hù)輸入的數(shù)據(jù)就是必不可少的。

          不使用中間過(guò)渡頁(yè)面帶來(lái)的另一個(gè)問(wèn)題就是使用包含頁(yè)面方式或服務(wù)器端重定向方式返回會(huì)使得url和頁(yè)面內(nèi)容不能一一對(duì)應(yīng)。對(duì)于用戶(hù)可能會(huì)直接用這個(gè)url(會(huì)收藏這個(gè)url)訪問(wèn)返回頁(yè)面的情況,他會(huì)發(fā)現(xiàn)實(shí)際上到達(dá)的是表單頁(yè)面,不是他想要的那個(gè)返回結(jié)果頁(yè)面。所以,去除中間過(guò)渡頁(yè)面,確實(shí)會(huì)帶來(lái)url和內(nèi)容含混不清的情況,因而不適合需要url和頁(yè)面內(nèi)容一一對(duì)應(yīng)的場(chǎng)合。

          2.從技術(shù)角度考慮,使用中間過(guò)渡頁(yè)面能保證url和頁(yè)面內(nèi)容一一對(duì)應(yīng),簡(jiǎn)化頁(yè)面開(kāi)發(fā)工作。

          為了保證頁(yè)面內(nèi)容總是和固定的url聯(lián)系起來(lái),必須使用客戶(hù)端重定向:

          提交?? 業(yè)務(wù)邏輯處理 (中間過(guò)渡頁(yè)面)

          表單頁(yè)面――――->接收表單頁(yè)面―――――――――>顯示處理結(jié)果―――>客戶(hù)端重定向到特定頁(yè)面

          客戶(hù)端重定向分幾種情況:

          1.使用http header重定向,location: http://www.netall.com.cn,這種定向是最快的,在窗口一片空白的情況下就迅速訪問(wèn)(get)另一個(gè)頁(yè)面。這種方式實(shí)際上不能顯示處理結(jié)果,只能說(shuō)是向第一種快速重定向方式的一種折衷處理;

          2.html標(biāo)簽刷新,<meta http-equiv=”refresh” content=”5;url=http://www.netall.com.cn”>,這種定向比較友好,在這個(gè)頁(yè)面加載完畢后訪問(wèn)另一個(gè)頁(yè)面。很多設(shè)計(jì)者把這個(gè)作為一個(gè)技巧使用,在載入一個(gè)大頁(yè)面前放置一個(gè)緩沖頁(yè)面以避免用戶(hù)乏味的等待;

          3.javascript重定向。由于是用代碼控制重定向,可以做的更靈活。比如根據(jù)用戶(hù)習(xí)慣,控制操作完畢后的轉(zhuǎn)向流程。

          4.被動(dòng)式的重定向。在頁(yè)面上放置按鈕或鏈接,由用戶(hù)手動(dòng)決定返回到特定頁(yè)面。這種情況適合于處理結(jié)果的顯示頁(yè)面包含相當(dāng)多的信息,需要用戶(hù)仔細(xì)瀏覽,而決定下一步的操作。

          ?? 在使用中間過(guò)渡頁(yè)面的情況下,不能再使用頁(yè)面過(guò)期失效了。否則一旦出現(xiàn)錯(cuò)誤,需要用戶(hù)重新輸入表單數(shù)據(jù),用戶(hù)就不能用后退按鈕恢復(fù)此前填寫(xiě)的表單數(shù)據(jù)了。除非設(shè)計(jì)者有意禁止這種恢復(fù)。

          六、防止表單重復(fù)提交處理

          ??? 對(duì)提交按鈕點(diǎn)擊后做變灰處理避免在網(wǎng)絡(luò)響應(yīng)較慢情況下用戶(hù)重復(fù)提交同一個(gè)表單。使用頁(yè)面過(guò)期失效避免用戶(hù)后退瀏覽重復(fù)提交表單。

          ??? 有些復(fù)雜的應(yīng)用會(huì)導(dǎo)致需要較長(zhǎng)時(shí)間的等待才會(huì)返回處理結(jié)果。而在較慢的網(wǎng)絡(luò)環(huán)境中,這種情況更是頻繁發(fā)生。焦急等待的用戶(hù)往往會(huì)重復(fù)點(diǎn)擊提交按鈕。這種情況是設(shè)計(jì)者所不希望看到的。

          ??? 使用javascript在點(diǎn)擊提交按鈕后使按鈕失效變灰是一個(gè)最直接的辦法(根據(jù)原則2這段代碼應(yīng)該放在<form>標(biāo)簽里onsubmit=”…”做)。此外,在表單頁(yè)面上,用服務(wù)器端腳本設(shè)置http header的expires為立即過(guò)期可以保證用戶(hù)沒(méi)辦法使用后退瀏覽恢復(fù)表單頁(yè)面。注意這樣做的代價(jià)可能是用戶(hù)辛辛苦苦填寫(xiě)很長(zhǎng)的內(nèi)容,結(jié)果一旦操作失誤就沒(méi)法恢復(fù)。所以應(yīng)該避免在包含<textarea>表單元素的頁(yè)面上使用頁(yè)面過(guò)期失效。

          ??? 應(yīng)該說(shuō),更嚴(yán)格的方法是,服務(wù)器端腳本就應(yīng)該具備抵抗重復(fù)提交的能力。例如,為這個(gè)表單分配一個(gè)唯一id或一個(gè)使用一次即失效的驗(yàn)證碼。此外,這個(gè)表單處理還應(yīng)具有事務(wù)性質(zhì),如果表單不被接受,所做的改變還是能恢復(fù)的。在金融應(yīng)用場(chǎng)合,重復(fù)提交同一筆交易是肯定不被允許的。能在重復(fù)提交中獲利的一方總是會(huì)想辦法繞過(guò)瀏覽器的限制,所以不能依賴(lài)于客戶(hù)端的技術(shù)。

          七、頁(yè)面鏈接是打開(kāi)新窗口、使用原窗口還是彈出窗口的原則

          ??? 一般而言,首頁(yè)上鏈接可以使用target=”_blank”屬性打開(kāi)新窗口,而其他頁(yè)面上的鏈接都應(yīng)使用原窗口或彈出窗口。如果鏈接頁(yè)面內(nèi)容相對(duì)原頁(yè)面來(lái)說(shuō)不重要,是附屬性質(zhì)的,可以使用彈出窗口方式。

          ??? 一般情況下應(yīng)該使用原窗口,把是否保留原窗口內(nèi)容的權(quán)利留給用戶(hù)。除非設(shè)計(jì)者相信原頁(yè)面是如此重要,在用戶(hù)發(fā)出點(diǎn)擊指令后還有使用上的價(jià)值,以至于不能被隨便更新或覆蓋。一般來(lái)說(shuō),只有首頁(yè)才會(huì)處于這樣一個(gè)地位,用戶(hù)在首頁(yè)上打開(kāi)一個(gè)鏈接后,一般還會(huì)在這個(gè)首頁(yè)上去打開(kāi)另一個(gè)鏈接。比如首頁(yè)包含極多鏈接的門(mén)戶(hù)網(wǎng)站,或者搜索引擎的搜索結(jié)果頁(yè)面。google.com以前的搜索結(jié)果頁(yè)面上的鏈接是使用原窗口的,后來(lái)他們意識(shí)到用戶(hù)會(huì)反復(fù)使用這個(gè)頁(yè)面,而改成打開(kāi)新窗口了。一般的網(wǎng)站如果首頁(yè)鏈接不多,就不必使用新窗口,這是用戶(hù)友好的設(shè)計(jì)原則。

          ??? 上述情形的一個(gè)極端情況就是新頁(yè)面內(nèi)容比起原頁(yè)面內(nèi)容的重要性差很多,以至于都未必需要打開(kāi)一個(gè)新頁(yè)面。這時(shí)候使用彈出窗口比較合適。用javascript彈出窗口有好幾種:一個(gè)是window.open()函數(shù)。這里有個(gè)技巧。應(yīng)該使用window.open()先打開(kāi)一個(gè)空白窗口,再使用location.replace()用目標(biāo)頁(yè)面替換。這樣做可以避免在打開(kāi)新頁(yè)面的過(guò)程中導(dǎo)致原頁(yè)面失去響應(yīng)。window.open()將打開(kāi)一個(gè)新的瀏覽器窗口進(jìn)程,因此資源消耗比較大。另一個(gè)是由微軟dynamichtml規(guī)范中擴(kuò)充的方法createpopup()。createpopup()可以創(chuàng)建無(wú)邊框的彈出窗口,消耗系統(tǒng)資源較小。還有一個(gè)就是用頁(yè)面中隱藏的層<div>來(lái)模擬一個(gè)彈出頁(yè)面。后兩種可以使用javascript代碼填充彈出窗口內(nèi)容。如果需要下載網(wǎng)頁(yè)作為其內(nèi)容的話,需要微軟dynamichtml規(guī)范中的<download>標(biāo)簽。

          八、盡可能少的排列可選項(xiàng),盡可能少的安排操作步驟

          ??? 根據(jù)用戶(hù)操作習(xí)慣安排盡可能少的操作菜單選項(xiàng),同時(shí)要保證盡可能少的操作步驟。 在不降低功能多樣性的前提下減少菜單項(xiàng)和操作步驟是用戶(hù)友好的設(shè)計(jì)。要做到這一點(diǎn)很不容易。要從用戶(hù)出發(fā)考慮他們最頻繁的操作是什么。正常情況下一個(gè)用戶(hù)需要的操作總可以歸類(lèi)為5個(gè)以下的種類(lèi),如果出現(xiàn)更多的種類(lèi),那一定是沒(méi)有針對(duì)用戶(hù)興趣去區(qū)分主次。一個(gè)用戶(hù)同時(shí)有5個(gè)以上的強(qiáng)烈興趣中心是難以想像的,走馬觀花似的隨意點(diǎn)擊瀏覽的用戶(hù),是不大可能在某個(gè)種類(lèi)上進(jìn)行深入的交互操作的。在這5個(gè)種類(lèi)中,每個(gè)種類(lèi)都可能有若干個(gè)可操作的二級(jí)種類(lèi)。如果這些二級(jí)操作項(xiàng)是不可見(jiàn)的,那么意味著要做兩次選擇才能進(jìn)入可操作頁(yè)面。這就違背了“盡可能少的安排操作步驟”這一原則。如果使用javascript制作二級(jí)菜單,避免請(qǐng)求服務(wù)器,會(huì)好一些。如果二級(jí)菜單項(xiàng)總共不超過(guò)20個(gè)左右,不妨將二級(jí)菜單直接顯示出來(lái),比如放在左列一字向下排開(kāi),這樣只需要一次選擇到可操作項(xiàng),更加明了方便。

          九、操作邏輯無(wú)漏洞,保證數(shù)據(jù)是操作安全的

          ??? 多個(gè)頁(yè)面間的操作和同個(gè)頁(yè)面上的多個(gè)操作間的邏輯關(guān)系在設(shè)計(jì)上是安全和嚴(yán)謹(jǐn)?shù)摹1WC不會(huì)出現(xiàn)不被允許的用戶(hù)操作組合,至少不會(huì)因?yàn)橛脩?hù)的不適當(dāng)?shù)牟僮鲗?dǎo)致出錯(cuò)。

          ??? 這最典型的表現(xiàn)則是在頁(yè)面上廣泛采用的所謂聯(lián)動(dòng)下拉框設(shè)計(jì)。一個(gè)下拉框中允許的選項(xiàng)受另一個(gè)下拉框中的選擇而變。另外一個(gè)例子是根據(jù)選擇使表單元素有效或者失效。如果在多個(gè)頁(yè)面間也要維持某種合法性邏輯,那么就需要服務(wù)器端腳本的參與。這樣會(huì)使表單設(shè)計(jì)跟操作有關(guān),應(yīng)該說(shuō)這不是一個(gè)好的設(shè)計(jì)。可以通過(guò)變更操作步驟順序、組合方式來(lái)盡可能避免這種情況出現(xiàn)。

          ??? 操作邏輯的設(shè)計(jì)既要保證用戶(hù)任意的輸入不會(huì)導(dǎo)致錯(cuò)誤,也要保證是用戶(hù)輸入的數(shù)據(jù)能購(gòu)被安全處理。在session控制下的表單中輸入大幅文字可能會(huì)導(dǎo)致超時(shí)出錯(cuò),這時(shí)候往往還伴隨重定向過(guò)程,導(dǎo)致用戶(hù)的長(zhǎng)篇輸入蕩然無(wú)存。用javascript提醒用戶(hù)已超時(shí),請(qǐng)保存輸入后重新提交,是一個(gè)好辦法。某些表單元素如<input type=”text”>接受esc鍵清除數(shù)據(jù),并且無(wú)法撤銷(xiāo),這也是很危險(xiǎn)的。在中文輸入法中常常使用esc鍵清楚輸入的碼位,一旦不小心多按一下esc就會(huì)使得輸入數(shù)據(jù)消失。因此有必要用javascript禁用<input>和<textarea>的esc鍵處理過(guò)程。(Edit From:Internet,By Aaron)

          posted @ 2008-05-29 22:35 金家寶 閱讀(587) | 評(píng)論 (0)編輯 收藏

          JSP安全幾個(gè)小問(wèn)題

          記得還是去年,剛到據(jù)說(shuō)是高手云集的威威公司上班的時(shí)候,一個(gè)新到的同事給我講他花了半天的時(shí)間寫(xiě),并做了很長(zhǎng)時(shí)間的實(shí)踐,寫(xiě)了個(gè)關(guān)于攻擊.jsp頁(yè)面的程序。下面我把具體的實(shí)現(xiàn)過(guò)程和大家分享一下。測(cè)試平臺(tái)是Tomcat,當(dāng)然,版本有點(diǎn)低,他的目的只是想證實(shí)一下他的某些想法。首先,他在Tomcat的WEB目錄下建立了一個(gè)Hello.jsp文件,內(nèi)容是:

          <%out.print(hello);%>

          通過(guò)IE的正常請(qǐng)求地址為:http://localhost:8080/examples/jsp/hello.jsp,顯示結(jié)果為:hello。然后開(kāi)始具體的攻擊測(cè)試。測(cè)試時(shí),發(fā)出的請(qǐng)求地址為:http://localhost:8080/examples/jsp/////////hello.jsp?,瀏覽器上顯示編譯錯(cuò)誤,錯(cuò)誤的原因是500?java.lang.NullPointerException。這個(gè)應(yīng)該是比較常見(jiàn)的錯(cuò)誤了。現(xiàn)在,恢復(fù)正常的請(qǐng)求http://localhost:8080/examples/jsp/hello.jsp,問(wèn)題就出現(xiàn)了,即出錯(cuò),而且所報(bào)的錯(cuò)誤和剛才造成它錯(cuò)誤的請(qǐng)求是一樣的:“500?java.lang.NullPointerException”。難道是緩存在瀏覽器里了嗎?換臺(tái)機(jī)器訪問(wèn)http://192.168.10.188/examples/jsp/hello.jsp。問(wèn)題依然如故,哎!可憐的Hello.jsp呀!

            雖然這個(gè)問(wèn)題有些弱智,不過(guò),他的目的也達(dá)到了,即找出“.jsp”流程中存在的一些問(wèn)題。所以,JSP程序同ASP一樣,還是存在著很多安全上的問(wèn)題的。因此,對(duì)于一心研究論壇或者其他安全信息的朋友來(lái)說(shuō),要想發(fā)現(xiàn)JSP的BUG,了解一些JSP的工作原理是十分重要的。

            需要指出的是,雖然是一門(mén)網(wǎng)絡(luò)編程語(yǔ)言,JSP和PHP、ASP的工作機(jī)制還存在很大的區(qū)別,首次調(diào)用JSP文件時(shí),JSP頁(yè)面在執(zhí)行時(shí)是編譯式,而不是解釋式的。首次調(diào)用JSP文件其實(shí)是執(zhí)行一個(gè)編譯為Servlet的過(guò)程。當(dāng)瀏覽器向服務(wù)器請(qǐng)求這一個(gè)JSP文件的時(shí)候,服務(wù)器將檢查自上次編譯后JSP文件是否有改變,如果沒(méi)有改變,就直接執(zhí)行Servlet,而不用再重新編譯,這樣,工作效率得到了明顯提高。這也是目前JSP論壇開(kāi)始逐漸風(fēng)靡的一個(gè)重要原因。

            小提示:Servlet是用Java編寫(xiě)的Server端程序,它與協(xié)議和平臺(tái)無(wú)關(guān);Servlet運(yùn)行于Java-enabled?WEB?Server中;Java?Servlet可以動(dòng)態(tài)地?cái)U(kuò)展Server的能力,并采用請(qǐng)求-響應(yīng)模式提供WEB服務(wù);最早支持Servlet技術(shù)的是JavaSoft的Java?WEB?Server;Servlet的主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動(dòng)態(tài)WEB內(nèi)容。

            說(shuō)到這里,我們自然就會(huì)關(guān)心一些JSP的安全問(wèn)題。一般來(lái)說(shuō),常見(jiàn)的JSP安全問(wèn)題有源代碼暴露(包括程序源代碼以明文的方式返回給訪問(wèn)者,如添加特殊后綴引起jsp源代碼暴露;插入特殊字符串引起Jsp源代碼暴露;路徑權(quán)限引起的文件Jsp源代碼暴露;文件不存在引起的絕對(duì)路徑暴露問(wèn)題等)、遠(yuǎn)程程序執(zhí)行類(lèi)、數(shù)據(jù)庫(kù)如SQL?Server、Oracle?、DB2等的漏洞,操作系統(tǒng)漏洞等。不過(guò),為了突出Jsp的安全問(wèn)題,本文將結(jié)合目前的一些比較流行的Jsp論壇分類(lèi)闡述和提出解決的建議。為了講解方便,本文還采用一些公開(kāi)了原代碼的論壇實(shí)例代碼,至于安裝軟件版本、操作系統(tǒng)等,可以查看安裝提示。

            論壇用戶(hù)管理缺陷

            為了加強(qiáng)實(shí)戰(zhàn)效果,我們可以到http://down.chinaz.com/S/5819.asp這個(gè)地址下載一個(gè)典型的論壇代碼,根據(jù)提示,數(shù)據(jù)源名稱(chēng)為yyForum,用戶(hù)名為xyworker,密碼:999。到baidu、Google等網(wǎng)站搜索一下,我們可以看到,安裝這個(gè)代碼的論壇不少。仔細(xì)分析后,可以發(fā)現(xiàn),用戶(hù)管理的頁(yè)面是user_manager.jsp文件。首先,我們看看這個(gè)系統(tǒng)是如何加強(qiáng)它的代碼安全性的。其中,在代碼的開(kāi)始部分有一個(gè)if限制條件,代碼的第三行到第十行具體如下:

          <%
          if?((session.getValue(UserName)==null)||(session.getValue(UserClass)==null)||(!session.getValue(UserClass).equals(系統(tǒng)管理員)))

          %>

            其中,Session.getValue表示檢索出Session的值;sendRedirect()執(zhí)行后,地址欄鏈接會(huì)改變,相當(dāng)于客戶(hù)端又重新發(fā)了一個(gè)get請(qǐng)求,要服務(wù)器傳輸另一個(gè)文件過(guò)來(lái)。

            下面,我們?cè)賮?lái)看看修改用戶(hù)信息的文件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?用戶(hù)表?set?用戶(hù)密碼='+User_Password+',用戶(hù)性別='+User_Sex+',用戶(hù)郵箱='+User_Email+',居住地址='+User_Address+',手機(jī)號(hào)碼='+User_Mobile+',Oicq='+User_Oicq+',出生日期='+User_Birthay+',用戶(hù)等級(jí)='+User_Class+',簽名='+User_Sign+'?where?用戶(hù)名='+User_Name+';
          //out.println(sql);
          stmt.executeUpdate(sql);
          out.println(<font?size=2?color=blue>正在處理你的用戶(hù)信息,請(qǐng)稍后...</font><meta?http-equiv='refresh'?content='2;url=user_manager.jsp'>);
          %>
          <jsp:include?page=inc/online.jsp?flush=true/>

            看看這個(gè)文件,我們就好像看到了一個(gè)簡(jiǎn)單的教學(xué)文件。現(xiàn)在,假設(shè)管理員提交如下地址,即http://www.51dz.net/bbs/modifyuser_manager.jsp?modifyid=51,需要查看、修改ID為51的用戶(hù)的資料(管理員默認(rèn)的用戶(hù)ID為51)。問(wèn)題就出來(lái)了。同樣的,我們可以通過(guò)搜索引擎得到如下地址

          很明顯,這個(gè)用戶(hù)管理文件缺乏認(rèn)證,即使是普通的用戶(hù),甚至包括我們這些搭不上邊的“游客”,也可以直接提交上述請(qǐng)求,從而將其資料一覽無(wú)余,更讓人動(dòng)心的是,密碼也是明文存儲(chǔ)的。
            
            http://www.51dz.net/bbs/modifyuser_manager.jsp同樣是大開(kāi)山門(mén),直到惡意用戶(hù)把數(shù)據(jù)更新的操作執(zhí)行完畢,重定向到user_manager.jsp的時(shí)候,管理員才會(huì)看見(jiàn)那個(gè)顯示錯(cuò)誤的頁(yè)面,但這個(gè)時(shí)候?yàn)闀r(shí)已晚,更談不上“亡羊補(bǔ)牢”了。類(lèi)似的錯(cuò)誤存在于很多JSP的站點(diǎn)上,面對(duì)這樣的論壇,我們能夠放心的說(shuō)“安全”嗎?解決之道有很多,不過(guò),最基本的要求是為每個(gè)需要加身份認(rèn)證的地方加上身份認(rèn)證,如果借用別人的代碼,一定要對(duì)涉及到用戶(hù)管理、密碼認(rèn)證等重要文件修改一下,照搬雖然省事,但代碼毫無(wú)安全性可言。

            再就是SQL注入的問(wèn)題。比如,這個(gè)典型的問(wèn)題:“昨天公司的數(shù)據(jù)庫(kù)被人SQL注入,9萬(wàn)條記錄都被update了,同事寫(xiě)了個(gè)JSP程序來(lái)把他改回來(lái),可是這JSP沒(méi)有一點(diǎn)信息返回,看不到進(jìn)度,在運(yùn)行些什么都不知道。”不過(guò),這和JSP程序沒(méi)有什么必然的聯(lián)系,根據(jù)國(guó)情,國(guó)內(nèi)的網(wǎng)站用ASP+Access或SQLServer的占70%以上,PHP+MySQL占20%,其它的不足10%。因此,ASP的SQL注入比較常見(jiàn)也不足為怪。不過(guò),SQL注入漏洞可謂是“千里之堤,潰于蟻穴”,這種漏洞在網(wǎng)上極為普遍,即使是JSP程序也不能幸免。歸根結(jié)底,通常是由于程序員對(duì)注入不了解,或者程序過(guò)濾不嚴(yán)格,或者某個(gè)參數(shù)忘記檢查導(dǎo)致。看看這個(gè)教材式的JSP程序就可以窺見(jiàn)一般:

          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);

            針對(duì)這種情況,如果數(shù)據(jù)庫(kù)里存在一個(gè)名叫“Tom”的用戶(hù),那么在不知道密碼的情況下至少有下面幾種方法可以登錄:?
          用戶(hù)名:Tom????????????密碼:'?or?'a'='a
          用戶(hù)名:Tom????????????密碼:'?or?1=1/*
          用戶(hù)名:Tom'?or?1=1/*?????密碼:(任意)

          posted @ 2008-05-23 18:20 金家寶 閱讀(1250) | 評(píng)論 (0)編輯 收藏

          Servlet使用

          1.1重定向(如果對(duì)方不支持cookie,回寫(xiě)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è)置連接超時(shí)時(shí)間(分鐘)
          ?<session-config>
          ??<session-timeout>50</session-timeout>
          ?</session-config>
          ******************************************************************
          4.相對(duì)路徑匹配
          ?1>絕對(duì)匹配?/xx/yy
          ?2>后綴匹配?*.xx
          ?3>后面匹配?/xx/*
          ******************************************************************
          5.監(jiān)聽(tīng)器
          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)

          posted @ 2008-04-19 20:31 金家寶 閱讀(408) | 評(píng)論 (0)編輯 收藏

          linux關(guān)機(jī)和重啟命令

          ?
          ?
          Linux中常用的關(guān)機(jī)和重新啟動(dòng)命令有shutdown、halt、reboot以及init,它們都可以達(dá)到關(guān)機(jī)和重新啟動(dòng)的目的,但是每個(gè)命令的內(nèi)部工作過(guò)程是不同的,下面將逐一進(jìn)行介紹。

          1. shutdown

          shutdown命令用于安全關(guān)閉Linux系統(tǒng)。有些用戶(hù)會(huì)使用直接斷掉電源的方式來(lái)關(guān)閉Linux,這是十分危險(xiǎn)的。因?yàn)長(zhǎng)inux與Windows不同,其后臺(tái)運(yùn)行著許多進(jìn)程,所以強(qiáng)制關(guān)機(jī)可能會(huì)導(dǎo)致進(jìn)程的數(shù)據(jù)丟失,使系統(tǒng)處于不穩(wěn)定的狀態(tài),甚至?xí)p壞硬件設(shè)備。

          執(zhí) 行shutdown命令時(shí),系統(tǒng)會(huì)通知所有登錄的用戶(hù)系統(tǒng)將要關(guān)閉,并且login指令會(huì)被凍結(jié),即新的用戶(hù)不能再登錄系統(tǒng)。使用shutdown命令可 以直接關(guān)閉系統(tǒng),也可以延遲指定的時(shí)間再關(guān)閉系統(tǒng),還可以重新啟動(dòng)。延遲指定的時(shí)間再關(guān)閉系統(tǒng),可以讓用戶(hù)有時(shí)間儲(chǔ)存當(dāng)前正在處理的文件和關(guān)閉已經(jīng)打開(kāi)的 程序。

          shutdown命令的部分參數(shù)如下:

          [-t] 指定在多長(zhǎng)時(shí)間之后關(guān)閉系統(tǒng)

          [-r] 重啟系統(tǒng)

          [-k] 并不真正關(guān)機(jī),只是給每個(gè)登錄用戶(hù)發(fā)送警告信號(hào)

          [-h] 關(guān)閉系統(tǒng)(halt)

          shutdown命令的工作實(shí)質(zhì)是給init程序發(fā)送信號(hào)(signal),要求其切換系統(tǒng)的運(yùn)行級(jí)別(Runlevel)。系統(tǒng)的運(yùn)行級(jí)別包括:

          0:關(guān)閉系統(tǒng)

          1:?jiǎn)斡脩?hù)模式,如果沒(méi)有為shutdown命令指定-h或-r參數(shù)而直接執(zhí)行,則默認(rèn)將切換到此運(yùn)行級(jí)別

          2:多用戶(hù)模式(不支持NFS)

          3:多用戶(hù)模式(支持NFS),一般常用此種運(yùn)行級(jí)別

          5:多用戶(hù)模式(GUI模式)

          6:重新啟動(dòng)系統(tǒng)

          2. halt

          halt是最簡(jiǎn)單的關(guān)機(jī)命令,其實(shí)際上是調(diào)用shutdown -h命令。halt執(zhí)行時(shí),殺死應(yīng)用進(jìn)程,文件系統(tǒng)寫(xiě)操作完成后就會(huì)停止內(nèi)核。

          halt命令的部分參數(shù)如下:

          [-f] 沒(méi)有調(diào)用shutdown而強(qiáng)制關(guān)機(jī)或重啟

          [-i] 關(guān)機(jī)或重新啟動(dòng)之前,關(guān)掉所有的網(wǎng)絡(luò)接口

          [-p] 關(guān)機(jī)時(shí)調(diào)用poweroff,此選項(xiàng)為缺省選項(xiàng)

          3.reboot

          reboot的工作過(guò)程與halt類(lèi)似,其作用是重新啟動(dòng),而halt是關(guān)機(jī)。其參數(shù)也與halt類(lèi)似。

          4.init

          init是所有進(jìn)程的祖先,其進(jìn)程號(hào)始終為1。init用于切換系統(tǒng)的運(yùn)行級(jí)別,切換的工作是立即完成的。init 0命令用于立即將系統(tǒng)運(yùn)行級(jí)別切換為0,即關(guān)機(jī);init 6命令用于將系統(tǒng)運(yùn)行級(jí)別切換為6,即重新啟動(dòng)。

          posted @ 2008-04-19 16:33 金家寶 閱讀(300) | 評(píng)論 (0)編輯 收藏

          僅列出標(biāo)題
          共7頁(yè): 上一頁(yè) 1 2 3 4 5 6 7 下一頁(yè) 
          主站蜘蛛池模板: 洛浦县| 通州区| 阿鲁科尔沁旗| 白水县| 交城县| 柯坪县| 湾仔区| 南丹县| 璧山县| 门源| 海伦市| 万安县| 舒城县| 封开县| 沂源县| 绍兴县| 东乌珠穆沁旗| 开平市| 卫辉市| 启东市| 昭苏县| 潢川县| 桦南县| 富裕县| 阿勒泰市| 怀集县| 二连浩特市| 昌黎县| 札达县| 青浦区| 垫江县| 东乡| 太仆寺旗| 龙陵县| 巴楚县| 陇南市| 扬中市| 鄢陵县| 崇义县| 枝江市| 诏安县|