hk2000c技術(shù)專(zhuān)欄

          技術(shù)源于哲學(xué),哲學(xué)來(lái)源于生活 關(guān)心生活,關(guān)注健康,關(guān)心他人

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            111 隨筆 :: 1 文章 :: 28 評(píng)論 :: 0 Trackbacks

          2007年12月31日 #

          posted @ 2014-04-21 21:33 hk2000c 閱讀(188) | 評(píng)論 (0)編輯 收藏

          許多年以來(lái),對(duì)于軟件項(xiàng)目,企業(yè)軟件開(kāi)發(fā)的主流實(shí)踐一直都傾向于在單一的通用編程語(yǔ)言上進(jìn)行標(biāo)準(zhǔn)化,從而使得Java和C#成為今天編程語(yǔ)言的主流選擇。隨著越來(lái)越多的目光開(kāi)始投向DSL,也許我們的前腳已經(jīng)踏在了一道新的門(mén)檻之上,向前望去,我們會(huì)發(fā)現(xiàn)在軟件項(xiàng)目中采用多種語(yǔ)言已經(jīng)成為一個(gè)標(biāo)準(zhǔn),但80年代和90年代初出現(xiàn)的問(wèn)題不會(huì)重現(xiàn)。

          Martin Fowler提出,也許我們正在邁進(jìn)這樣的一個(gè)新時(shí)期

          [……]在這個(gè)時(shí)期內(nèi),我們將見(jiàn)證多種語(yǔ)言在同一個(gè)項(xiàng)目上的應(yīng)用,人們就像現(xiàn)在選擇框架一樣,根據(jù)功能來(lái)選擇相應(yīng)的語(yǔ)言。

          Fowler稱(chēng):“像Hibernate、Struts和ADO這樣的大型框架,給人們?cè)趯W(xué)習(xí)上帶來(lái)的挑戰(zhàn),絕不亞于學(xué)習(xí)一門(mén)語(yǔ)言,即便你在單一一門(mén)宿主語(yǔ)言上使用這些框架編程也是如此。”此外,在它們的宿主語(yǔ)言中表述需求的難度可能會(huì)相當(dāng)大,并可能引出笨拙難用的配置文件,“這些配置文件實(shí)際上就是使用XML寫(xiě)的外部領(lǐng)域特定語(yǔ)言”。

          在語(yǔ)言中嵌入DSL,而不是使用類(lèi)庫(kù),可能會(huì)是一個(gè)更為合適的解決方案。Martin給出了這樣的一個(gè)分析結(jié)論:“API就好比是在聲明一個(gè)詞匯表,而DSL則為其增加了相應(yīng)的語(yǔ)法,使得人們能夠?qū)懗鰲l理清晰的句子。”因此,使用DSL而不是框架會(huì)使代碼豐富表現(xiàn)力,為人們帶來(lái)“更良好的抽象處理方式”,并使“閱讀我們寫(xiě)出的代碼及對(duì)我們意圖的展示變得更加容易”。

          Piers Cawley稱(chēng),DSL的主要特性并非其可讀性,而是“它們對(duì)去相應(yīng)領(lǐng)域的高度專(zhuān)注”使得它們能夠更加明確地表義。Cawley為了闡述他的觀點(diǎn)舉了一個(gè)例子,說(shuō)明DSL不僅僅能讓我們“寫(xiě)出讀起來(lái)像領(lǐng)域?qū)<艺f(shuō)出來(lái)的話(huà)一樣的程序”,也可以很技術(shù)化,用來(lái)代表一個(gè)使用它們的語(yǔ)法進(jìn)行操控的框架。

          Neal Ford也相信,被他稱(chēng)為多語(yǔ)言編程(Polyglot Programming)的勢(shì)頭正在興起。在軟件開(kāi)發(fā)的這個(gè)新紀(jì)元中,日益明顯的主要特征就是嵌入更多的語(yǔ)言,使人們能夠“為所做的菜選擇一把恰到好處的刀,并且恰如其分地使用它”。他舉了一個(gè)例子,展示在Java編程語(yǔ)言中并行類(lèi)庫(kù)的使用難度,并將其與Haskell作比。Haskell是一門(mén)函數(shù)式語(yǔ)言,“消除了變量所帶來(lái)的副作用”,并使“編寫(xiě)線(xiàn)程安全的代碼”變得更容易。Ford強(qiáng)調(diào)說(shuō),Java和.NET平臺(tái)都存在Haskell語(yǔ)言的實(shí)現(xiàn)(Jaskell和Haskell.net)。

          不再使用單一語(yǔ)言進(jìn)行開(kāi)發(fā)所帶來(lái)的風(fēng)險(xiǎn)之一可能讓80年代末90年代初所出現(xiàn)的問(wèn)題又再次重現(xiàn),當(dāng)時(shí)語(yǔ)言就是完全獨(dú)立的平臺(tái),既不能互操作也不能放在一起良好地使用。Martin Fowler指出,現(xiàn)在的情況有這樣的一個(gè)重要區(qū)別:

          在80年代末期,人們很難讓各個(gè)語(yǔ)言之間緊密地互操作。這些年來(lái),人們花了很大精力創(chuàng)建出可以讓不同語(yǔ)言緊密共存的環(huán)境。腳本語(yǔ)言在傳統(tǒng)上與C語(yǔ)言有著很密切的關(guān)系。在JVM和CLR平臺(tái)上也有人為互操作花費(fèi)了大量精力。另外人們也在類(lèi)庫(kù)上投入了很多人力物力,為的是讓語(yǔ)言忽視類(lèi)庫(kù)的存在。

          最終,要學(xué)習(xí)并使用多種語(yǔ)言,對(duì)于業(yè)界乃至開(kāi)發(fā)人員都可能會(huì)變成一項(xiàng)重要資產(chǎn)。《Pragmatic Programmers》這本書(shū)里面就說(shuō)到,由于這樣做會(huì)對(duì)人們對(duì)編程的思考方式產(chǎn)生影響,因此這樣能幫助人們發(fā)現(xiàn)解決問(wèn)題的新途徑。

          您是怎樣認(rèn)為的呢?在下去的五年中,我們會(huì)開(kāi)始混合使用語(yǔ)言,并像用類(lèi)庫(kù)一樣頻繁地使用DSL嗎?

          posted @ 2011-11-10 05:56 hk2000c 閱讀(318) | 評(píng)論 (0)編輯 收藏

               摘要: java 裝載 groovy 方法  閱讀全文
          posted @ 2011-11-10 05:45 hk2000c 閱讀(745) | 評(píng)論 (0)編輯 收藏

               摘要: java 和groovy 集成

            閱讀全文
          posted @ 2011-11-10 05:37 hk2000c 閱讀(1064) | 評(píng)論 (0)編輯 收藏

          AspectJ

           

                AspectJ是一個(gè)面向切面的框架,它擴(kuò)展了Java語(yǔ)言。AspectJ定義了AOP語(yǔ)法所以它有一個(gè)專(zhuān)門(mén)的編譯器用來(lái)生成遵守Java字節(jié)編碼規(guī)范的Class文件


          一、AspectJ概述

          圖1 :FigureEditor例子的UML

                AspectJ(也就是AOP)的動(dòng)機(jī)是發(fā)現(xiàn)那些使用傳統(tǒng)的編程方法無(wú)法很好處理的問(wèn)題。考慮一個(gè)要在某些應(yīng)用中實(shí)施安全策略的問(wèn)題。安全性是貫穿于系統(tǒng)所有模塊間的問(wèn)題,每個(gè)模塊都需要應(yīng)用安全機(jī)制才能保證整個(gè)系統(tǒng)的安全性,很明顯這里的安全策略的實(shí)施問(wèn)題就是一個(gè)橫切關(guān)注點(diǎn),使用傳統(tǒng)的編程解決此問(wèn)題非常的困難而且容易產(chǎn)生差錯(cuò),這就正是AOP發(fā)揮作用的時(shí)候了。

                傳統(tǒng)的面向?qū)ο缶幊?/a>中,每個(gè)單元就是一個(gè)類(lèi),而類(lèi)似于安全性這方面的問(wèn)題,它們通常不能集中在一個(gè)類(lèi)中處理因?yàn)樗鼈儥M跨多個(gè)類(lèi),這就導(dǎo)致了代碼無(wú)法重用,可維護(hù)性差而且產(chǎn)生了大量代碼冗余,這是我們不愿意看到的。

                面向方面編程的出現(xiàn)正好給處于黑暗中的我們帶來(lái)了光明,它針對(duì)于這些橫切關(guān)注點(diǎn)進(jìn)行處理,就好象面向?qū)ο?/a>編程處理一般的關(guān)注點(diǎn)一樣。而作為AOP的具體實(shí)現(xiàn)之一的AspectJ,它向Java中加入了連接點(diǎn)(Join Point)這個(gè)新概念,其實(shí)它也只是現(xiàn)存的一個(gè)Java概念的名稱(chēng)而已。它向Java語(yǔ)言中加入少許新結(jié)構(gòu):切點(diǎn)(pointcut)、通知(Advice)、類(lèi)型間聲明(Inter-type declaration)和方面(Aspect)。切點(diǎn)和通知?jiǎng)討B(tài)地影響程序流程,類(lèi)型間聲明則是靜態(tài)的影響程序的類(lèi)等級(jí)結(jié)構(gòu),而方面則是對(duì)所有這些新結(jié)構(gòu)的封裝。

                一個(gè)連接點(diǎn)是程序流中指定的一點(diǎn)。切點(diǎn)收集特定的連接點(diǎn)集合和在這些點(diǎn)中的值。一個(gè)通知是當(dāng)一個(gè)連接點(diǎn)到達(dá)時(shí)執(zhí)行的代碼,這些都是AspectJ的動(dòng)態(tài)部分。其實(shí)連接點(diǎn)就好比是程序中的一條一條的語(yǔ)句,而切點(diǎn)就是特定一條語(yǔ)句處設(shè)置的一個(gè)斷點(diǎn),它收集了斷點(diǎn)處程序棧的信息,而通知就是在這個(gè)斷點(diǎn)前后想要加入的程序代碼。AspectJ中也有許多不同種類(lèi)的類(lèi)型間聲明,這就允許程序員修改程序的靜態(tài)結(jié)構(gòu)、名稱(chēng)、類(lèi)的成員以及類(lèi)之間的關(guān)系。AspectJ中的方面是橫切關(guān)注點(diǎn)的模塊單元。它們的行為與Java語(yǔ)言中的類(lèi)很象,但是方面還封裝了切點(diǎn)、通知以及類(lèi)型間聲明。

          動(dòng)態(tài)連接點(diǎn)模型

                任何面向方面編程的關(guān)鍵元素就是連接點(diǎn)模型。AspectJ提供了許多種類(lèi)的連接點(diǎn)集合,但是本篇只介紹它們中的一個(gè):方法調(diào)用連接點(diǎn)集(method call join points)。一個(gè)方法調(diào)用連接點(diǎn)捕捉對(duì)象的方法調(diào)用。每一個(gè)運(yùn)行時(shí)方法調(diào)用都是一個(gè)不同的連接點(diǎn),許多其他的連接點(diǎn)集合可能在方法調(diào)用連接點(diǎn)執(zhí)行時(shí)運(yùn),包括方法執(zhí)行時(shí)的所有連接點(diǎn)集合以及在方法中其他方法的調(diào)用。我們說(shuō)這些連接點(diǎn)集合在原來(lái)調(diào)用的連接點(diǎn)的動(dòng)態(tài)環(huán)境中執(zhí)行。

           

          切點(diǎn)

                 在AspectJ中,切點(diǎn)捕捉程序流中特定的連接點(diǎn)集合。例如,切點(diǎn)

                        call(void Point.setX(int))

          捕捉每一個(gè)簽名為void Point.setX(int)的方法調(diào)用的連接點(diǎn),也就是說(shuō),調(diào)用Point對(duì)象的有一個(gè)整型參數(shù)的void setX方法。切點(diǎn)能與其他切點(diǎn)通過(guò)或(||)、與(&&)以及非(!)操作符聯(lián)合。例如 call(void Point.setX(int)) || call(void Point.setY(int)) 捕捉setX或setY調(diào)用的連接點(diǎn)。切點(diǎn)還可以捕捉不同類(lèi)型的連接點(diǎn)集合,換句話(huà)說(shuō),它們能橫切類(lèi)型。例如

                 call(void FigureElement.setXY(int,int)) || call(void Point.setX(int))

                 || call(void Point.setY(int) || call(void Line.setP1(Point))

                 || call(void Line.setP2(Point));

          捕捉上述五個(gè)方法調(diào)用的任意一個(gè)的連接點(diǎn)集合。它在本文的例子中捕捉當(dāng)FigureElement移動(dòng)時(shí)的所有連接點(diǎn)集合。AspectJ使程序員可以命名一個(gè)切點(diǎn)集合,以便通知的使用。例如可以為上面的那些切點(diǎn)命名

          pointcut move():

          call(void FigureElement.setXY(int,int)) || call(void Point.setX(int))

          || call(void Point.setY(int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point));

          無(wú)論什么時(shí)候,程序員都可以使用move()代替捕捉這些復(fù)雜的切點(diǎn)。

                 前面所說(shuō)的切點(diǎn)都是基于顯示的方法簽名,它們稱(chēng)為基于名字(name-based)橫切。AspectJ還提供了另一種橫切,稱(chēng)為基于屬性(property-based)的橫切。它們可以使用通配符描述方法簽名,例如 call(void Figure.make*(..)) 捕捉Figure對(duì)象中以make開(kāi)頭的參數(shù)列表任意的方法調(diào)用的連接點(diǎn)。而 call(public & Figure.*(..)) 則捕捉Figure對(duì)象中的任何公共方法調(diào)用的連接點(diǎn)。但是通配符不是AspectJ支持的唯一屬性,AspectJ中還有許多其他的屬性可供程序員使用。例如cflow,它根據(jù)連接點(diǎn)集合是否在其他連接點(diǎn)集合的動(dòng)態(tài)環(huán)境中發(fā)生標(biāo)識(shí)連接點(diǎn)集合。例如 cflow(move()) 捕捉被move()捕捉到的連接點(diǎn)集合的動(dòng)態(tài)環(huán)境中發(fā)生的連接點(diǎn)。

           

          通知

                 雖然切點(diǎn)用來(lái)捕捉連接點(diǎn)集合,但是它們沒(méi)有做任何事。要真正實(shí)現(xiàn)橫切行為,我們需要使用通知機(jī)制。通知包含了切點(diǎn)和要在每個(gè)連連接點(diǎn)處執(zhí)行的代碼段。AspectJ有幾種通知。

          ·前通知(Before Advice)      當(dāng)?shù)竭_(dá)一個(gè)連接點(diǎn)但是在程序進(jìn)程運(yùn)行之前執(zhí)行。例如,前通知在方法實(shí)際調(diào)用之前運(yùn)行,剛剛在方法的參數(shù)被分析之后。

                 Before() : move(){ System.out.println(“物體將移動(dòng)了”);}

          ·后通知(After Advice) 當(dāng)特定連接點(diǎn)處的程序進(jìn)程執(zhí)行之后運(yùn)行。例如,一個(gè)方法調(diào)用的后通知在方法體運(yùn)行之后,剛好在控制返回調(diào)用者之前執(zhí)行。因?yàn)镴ava程序有兩種退出連接點(diǎn)的形式,正常的和拋出異常。相對(duì)的就有三種后通知:返回后通知(after returning)、拋出異常后通知(after throwing)和清楚的后通知(after),所謂清楚后通知就是指無(wú)論是正常還是異常都執(zhí)行的后通知,就像Java中的finally語(yǔ)句。

                 After() returning : move(){    System.out.println(“物體剛剛成功的移動(dòng)了”);}

          ·在周?chē)ㄖ?Around Advice)    在連接點(diǎn)到達(dá)后,顯示的控制程序進(jìn)程是否執(zhí)行(暫不討論)

           

          暴露切點(diǎn)環(huán)境

                 切點(diǎn)不僅僅捕捉連接點(diǎn),它還能暴露連接點(diǎn)處的部分執(zhí)行環(huán)境。切點(diǎn)中暴露的值可以在通知體中聲明以后使用。通知聲明有一個(gè)參數(shù)列表(和方法相同)用來(lái)描述它所使用的環(huán)境的名稱(chēng)。例如后通知

                 after(FigureElement fe,int x,int y) returning : somePointcuts {  someCodes  }

          使用了三個(gè)暴露的環(huán)境,一個(gè)名為fe的FigureElement對(duì)象,兩個(gè)整型變量x,y。通知體可以像使用方法的參數(shù)那樣使用這些變量,例如

                 after(FigureElement fe,int x,int y) returning : somePointcuts {

                        System.out.println(fe+”移動(dòng)到(”+x+”,”+y+”)”);

          }

          通知的切點(diǎn)發(fā)布了通知參數(shù)的值,三個(gè)原生切點(diǎn)this、target和args被用來(lái)發(fā)布這些值/所以上述例子的完整代碼為

                 after(FigureElement fe,int x,int y) returning : call(void FigureElement.setXY(int,int)

          && target(fe) && args(x,y) {

                        System.out.println(fe+”移動(dòng)到(”+x+”,”+y+”)”);

          }

          目標(biāo)對(duì)象是FigureElement所以fe是after的第一個(gè)參數(shù),調(diào)用的方法包含兩個(gè)整型參數(shù)所以x和y為after的第二和第三個(gè)參數(shù)。所以通知打印出方法setXY調(diào)用返回后對(duì)象移動(dòng)到的點(diǎn)x和y。當(dāng)然還可以使用命名切點(diǎn)完成同樣的工作,例如

                 pointcut setXY(FigureElement fe,int x,int y):call(void FigureElement.setXY(int,int)

                        && target(fe) && args(x,y);

                 after(FigureElement fe,int x,int y) returning : setXY(fe,x,y){

                 System.out.println(fe+”移動(dòng)到(”+x+”,”+y+”)”);

                 }

           

          類(lèi)型間聲明

                 AspectJ的類(lèi)型間聲明指的是那些跨越類(lèi)和它們的等級(jí)結(jié)構(gòu)的聲明。這些可能是橫跨多個(gè)類(lèi)的成員聲明或者是類(lèi)之間繼承關(guān)系的改變。不像通知是動(dòng)態(tài)地操作,類(lèi)型間聲明編譯時(shí)的靜態(tài)操作。考慮一下,Java語(yǔ)言中如何向一個(gè)一些的類(lèi)中加入新方法,這需要實(shí)現(xiàn)一個(gè)特定接口,所有類(lèi)都必須在各自?xún)?nèi)部實(shí)現(xiàn)接口聲明的方法,而使用AspectJ則可以將這些工作利用類(lèi)型間聲明放在一個(gè)方面中。這個(gè)方面聲明方法和字段,然后將它們與需要的類(lèi)聯(lián)系。

          假設(shè)我們想有一個(gè)Sreen對(duì)象觀察Point對(duì)象的變化,當(dāng)Point是一個(gè)存在的類(lèi)。我們可以通過(guò)書(shū)寫(xiě)一個(gè)方面,由這個(gè)方面聲明Point對(duì)象有一個(gè)實(shí)例字段observers,用來(lái)保存所有觀察Point對(duì)象的Screen對(duì)象的引用,從而實(shí)現(xiàn)這個(gè)功能。

                 Aspect PointObserving{

                        Private    Collection Point.observers=new ArrayList();

          ……

          }

          observers字段是私有字段,只有PointObserving能使用。因此,要在aspect中加入方法管理observers聚集。

                 Aspect PointObserving{

                        Private Collection Point.observers=new ArrayList();

                        Public static void addObserver(Point p,Screen s){

                               p.observers.add(s);

                        }

                        public static void removeObserver(Point p,Screen s){

                               p.observers.remove(s);

                        }

                        ……

          }

          然后我們可以定義一個(gè)切點(diǎn)stateChanges決定我們想要觀察什么并且提供一個(gè)after通知定義當(dāng)觀察到變化時(shí)我們想要做什么。

                 Aspect PointObserving{

                        Private Collection Point.observers=new ArrayList();

                        Public static void addObserver(Point p,Screen s){

                               p.observers.add(s);

                        }

                        public static void removeObserver(Point p,Screen s){

                               p.observers.remove(s);

                        }

                        pointcut stateChanges(Point p) : target(p) && call(void Point.set*(int));

                        after(Point p) : stateChanges(p){

                               Iterator it=p.observers.iterator();

                               While(it.hasNext()){

                                      UpdateObserver(p,(Screen)it.next()));

                               }

                        }

                        private static void updateObserver(Point p,Screen s){

                               s.display(p);

                        }

          }

          注意無(wú)論是Sreen還是Point的代碼都沒(méi)有被修改,所有的新功能的加入都在方面中實(shí)現(xiàn)了,很酷吧!

           

          方面

                 方面以橫切模塊單元的形式包裝了所有的切點(diǎn)、通知和類(lèi)型間聲明。這非常像Java語(yǔ)言的類(lèi)。實(shí)際上,方面也可以定義自己的方法,字段和初始化方法。像類(lèi)一樣一個(gè)方面也可以用abstrace關(guān)鍵字聲明為抽象方面,可以被子方面繼承。在AspectJ中方面的設(shè)計(jì)實(shí)際上使用了單例模式,缺省情況下,它不能使用new構(gòu)造,但是可以使用一個(gè)方法實(shí)例化例如方法aspectOf()可以獲得方面的實(shí)例。所以在方面的通知中可以使用非靜態(tài)的成員字段。

          例如

                 aspect Tracing {

                        OutputStream trace=System.out;

                        After() : move(){    trace.println(“物體成功移動(dòng)”); }


          二、AspectJ應(yīng)用范圍

                 如前所述,AspectJ可以用于應(yīng)用開(kāi)發(fā)的不同階段。下面討論不同階段的AspectJ的具體應(yīng)用情況。

          開(kāi)發(fā)型方面(Development Aspects)

                 開(kāi)發(fā)方面可以很容易的從真正的產(chǎn)品中刪除。而產(chǎn)品方面則被可用于開(kāi)發(fā)過(guò)程和生產(chǎn)過(guò)程,但是僅僅影響某幾個(gè)類(lèi)。

                 這一部分將通過(guò)幾個(gè)例子說(shuō)明方面在Java應(yīng)用的開(kāi)發(fā)階段是如何使用的。這些方面包括調(diào)試、測(cè)試和性能檢測(cè)等工作。方面定義的行為范圍包括簡(jiǎn)單的代碼跟蹤、測(cè)試應(yīng)用的內(nèi)在聯(lián)系等等。使用AspectJ不僅使得模塊化這些功能變?yōu)榭赡埽瑫r(shí)也使得根據(jù)需要打開(kāi)和關(guān)閉這些功能變成可能。

           

          代碼跟蹤(Tracing)
                 首先讓我們看看如何增加一個(gè)程序內(nèi)部工作的可視性。我們定義一個(gè)簡(jiǎn)單的方面用于代碼跟蹤并且在每個(gè)方法調(diào)用時(shí)輸出一些信息。在前一篇的圖形編輯例子中,這樣的方面可能僅僅簡(jiǎn)單的跟蹤什么時(shí)候畫(huà)一個(gè)點(diǎn)。

          aspect SimpleTracing {
              pointcut tracedCall():
                  call(void FigureElement.draw(GraphicsContext));
           
              before(): tracedCall() {
                  System.out.println("Entering: " + thisJoinPoint);
              }
          }
          代碼利用了thisJoinPoint變量。在所有的通知體內(nèi),這個(gè)變量將與描述當(dāng)前連接點(diǎn)的對(duì)象綁定。所以上述代碼在每次一個(gè)FigureElement對(duì)象接受到draw方法時(shí)輸出如下信息:

          Entering: call(void FigureElement.draw(GraphicsContext))

          通常我們?cè)谡{(diào)式程序時(shí),會(huì)在特定的地方放置幾條輸出語(yǔ)句,而當(dāng)調(diào)試結(jié)束時(shí)還需要找到這些代碼段將它們刪除,這樣做不但使我們的代碼很難看而且很費(fèi)時(shí)間。而使用AspectJ我們可以克服以上的兩個(gè)問(wèn)題,我們可以通過(guò)定義切點(diǎn)捕捉任何想要觀察的代碼段,利用通知可以在方面內(nèi)部書(shū)寫(xiě)輸出語(yǔ)句,而不需要修改源代碼,當(dāng)不在需要跟蹤語(yǔ)句的時(shí)候還可以很輕松的將方面從應(yīng)用中刪除并重新編譯代碼即可。

           

          前提條件和后續(xù)條件(Pre-and Post-Conditions)
                 許多的程序員使用按契約編程(Design by Contract)的形式。這種形式的編程需要顯式的前提條件測(cè)試以保證方法調(diào)用是否合適,還需要顯式的后續(xù)條件測(cè)試保證方法是否工作正常。AspectJ使得可以模塊化地實(shí)現(xiàn)這兩種條件測(cè)試。例如下面的代碼

          aspect PointBoundsChecking {

              pointcut setX(int x):

                  (call(void FigureElement.setXY(int, int)) && args(x, *))

                  || (call(void Point.setX(int)) && args(x));

           

              pointcut setY(int y):

                  (call(void FigureElement.setXY(int, int)) && args(*, y))

                  || (call(void Point.setY(int)) && args(y));

           

              before(int x): setX(x) {

                  if ( x < MIN_X || x > MAX_X )

                      throw new IllegalArgumentException("x is out of bounds.");

              }

           

              before(int y): setY(y) {

                  if ( y < MIN_Y || y > MAX_Y )

                      throw new IllegalArgumentException("y is out of bounds.");

              }

          }

          它實(shí)現(xiàn)了邊界檢測(cè)功能。當(dāng)FigureElement對(duì)象移動(dòng)時(shí),如果x或y的值超過(guò)了定義的邊界,程序?qū)?huì)拋出IllegalArgumentException異常。

           

          合同實(shí)施(Contract Enforcement)
                 基于屬性的橫切機(jī)制在定義更加復(fù)雜的合同實(shí)施上非常有用。一個(gè)十分強(qiáng)大的功能是它可以強(qiáng)制特定的方法調(diào)用只出現(xiàn)在對(duì)應(yīng)的程序中,而在其他程序中不出現(xiàn)。例如,下面的方面實(shí)施了一個(gè)限制,使得只有在知名的工廠方法中才能向注冊(cè)并添加FigureElement對(duì)象。實(shí)施這個(gè)限制的目的是為了確保沒(méi)有任何一個(gè)FigureElement對(duì)象被注冊(cè)多次。

          static aspect RegistrationProtection {

              pointcut register(): call(void Registry.register(FigureElement));

              pointcut canRegister(): withincode(static * FigureElement.make*(..));

           

              before(): register() && !canRegister() {

                  throw new IllegalAccessException("Illegal call " + thisJoinPoint);

              }

          }

          這個(gè)方面使用了withincode初始切點(diǎn),它表示在FigureElement對(duì)象的工廠方法(以make開(kāi)始的方法)體內(nèi)出現(xiàn)的所有連接點(diǎn)。在before通知中聲明一個(gè)異常,該通知用于捕捉任何不在工廠方法代碼內(nèi)部產(chǎn)生的register方法的調(diào)用。該通知在特定連接點(diǎn)處拋出一個(gè)運(yùn)行時(shí)異常,但是AspectJ能做地更好。使用declare error的形式,我們可以聲明一個(gè)編譯時(shí)的錯(cuò)誤。

          static aspect RegistrationProtection {

              pointcut register(): call(void Registry.register(FigureElement));

              pointcut canRegister(): withincode(static * FigureElement.make*(..));

           

              declare error: register() && !canRegister(): "Illegal call"

          }

          當(dāng)使用這個(gè)方面后,如果代碼中存在定義的這些非法調(diào)用我們將無(wú)法通過(guò)編譯。這種情況只出現(xiàn)在我們只需要靜態(tài)信息的時(shí)候,如果我們需要?jiǎng)討B(tài)信息,像上面提到的前提條件實(shí)施時(shí),就可以利用在通知中拋出帶參數(shù)的異常來(lái)實(shí)現(xiàn)。

           

          配置管理(Configuration Management)
                 AspectJ的配置管理可以使用類(lèi)似于make-file等技術(shù)進(jìn)行處理。程序員可以簡(jiǎn)單的包括他們想要的方面進(jìn)行編譯。不想要任何方面出現(xiàn)在產(chǎn)品階段的開(kāi)發(fā)者也可以通過(guò)配置他們的make-file使用傳統(tǒng)的Java編譯器編譯整個(gè)應(yīng)用。

           

          產(chǎn)品型方面(Production Aspects)

                 這一部分的方面例子將描述方面用于生產(chǎn)階段的應(yīng)用。產(chǎn)品方面將向應(yīng)用中加入功能而不僅僅為程序的內(nèi)部工作增加可視性。

          改變監(jiān)視(Change Monitoring)
                 在第一個(gè)例子,方面的角色是用于維護(hù)一位數(shù)據(jù)標(biāo)志,由它說(shuō)明對(duì)象從最后一次顯示刷新開(kāi)始是否移動(dòng)過(guò)。在方面中實(shí)現(xiàn)這樣的功能是十分直接的,testAndClear方法被顯示代碼調(diào)用以便找到一個(gè)圖形元素是否在最近移動(dòng)過(guò)。這個(gè)方法返回標(biāo)志的狀態(tài)并將它設(shè)置為假。切點(diǎn)move捕捉所有能夠是圖形移動(dòng)的方法調(diào)用。After通知截獲move切點(diǎn)并設(shè)置標(biāo)志位。

          aspect MoveTracking {

          private static boolean dirty = false;

           

              public static boolean testAndClear() {

                  boolean result = dirty;

                  dirty = false;

                  return result;

              }

           

              pointcut move():

                  call(void FigureElement.setXY(int, int)) ||

                  call(void Line.setP1(Point)) ||

                  call(void Line.setP2(Point)) ||

                  call(void Point.setX(int)) ||

                  call(void Point.setY(int));

           

              after() returning: move() {

                  dirty = true;

              }

          }
           

          這個(gè)簡(jiǎn)單例子同樣說(shuō)明了在產(chǎn)品代碼中使用AspectJ的一些好處。考慮使用普通的Java代碼實(shí)現(xiàn)這個(gè)功能:將有可能需要包含標(biāo)志位,testAndClear以及setFlag方法的輔助類(lèi)。這些方法需要每個(gè)移動(dòng)的圖形元素包含一個(gè)對(duì)setFlag方法的調(diào)用。這些方法的調(diào)用就是這個(gè)例子中的橫切關(guān)注點(diǎn)。

          ·顯示的捕捉了橫切關(guān)注點(diǎn)的結(jié)構(gòu)

          ·功能容易拔插

          ·實(shí)現(xiàn)更加穩(wěn)定

           

          傳遞上下文(Context Passing)
          橫切結(jié)構(gòu)的上下文傳遞在Java程序中是十分復(fù)雜的一部分。考慮實(shí)現(xiàn)一個(gè)功能,它允許客戶(hù)設(shè)置所創(chuàng)建的圖形對(duì)象的顏色。這個(gè)需求需要從客戶(hù)端傳入一個(gè)顏色或顏色工廠。而要在大量的方法中加入一個(gè)參數(shù),目的僅僅是為傳遞上下文信息這種不方便的情況是所有的程序員都十分熟悉的。

          使用AspectJ,這種上下文的傳遞可以使用模塊化的方式實(shí)現(xiàn)。下面代碼中的after通知僅當(dāng)一個(gè)圖形對(duì)象的工廠方法在客戶(hù)ColorControllingClient的某個(gè)方法控制流程中調(diào)用時(shí)才運(yùn)行。

          aspect ColorControl {

              pointcut CCClientCflow(ColorControllingClient client):

                  cflow(call(* * (..)) && target(client));

           

              pointcut make(): call(FigureElement Figure.make*(..));

           

              after (ColorControllingClient c) returning (FigureElement fe):

                      make() && CCClientCflow(c) {

                  fe.setColor(c.colorFor(fe));

              }

          }

          這個(gè)方面僅僅影響一小部分的方法,但是注意該功能的非AOP實(shí)現(xiàn)可能 需要編輯更多的方法。

           

          提供一致的行為(Providing Consistent Behavior)
          接下來(lái)的例子說(shuō)明了基于屬性的方面如何在很多操作中提供一致的處理功能。這個(gè)方面確保包c(diǎn)om.bigboxco的所有公共方法記錄由它們拋出的任何錯(cuò)誤。PublicMethodCall切點(diǎn)捕捉包中的公共方法調(diào)用, after通知在任何一個(gè)這種調(diào)用拋出錯(cuò)誤后運(yùn)行并且記錄下這個(gè)錯(cuò)誤。

          aspect PublicErrorLogging {

              Log log = new Log();

           

              pointcut publicMethodCall():

                  call(public * com.bigboxco.*.*(..));

           

              after() throwing (Error e): publicMethodCall() {

                  log.write(e);

              }

          }

          在一些情況中,這個(gè)方面可以記錄一個(gè)異常兩次。這在com.bigboxco包內(nèi)部的代碼自己調(diào)用本包中的公共方法時(shí)發(fā)生。為解決這個(gè)問(wèn)題,我們可以使用cflow初始切點(diǎn)將這些內(nèi)部調(diào)用排除:

          after() throwing (Error e) : publicMethodCall() && !cflow(publicMethodCall()) {

              log.write(e);

          }

           

          結(jié)論

                 AspectJ是對(duì)Java語(yǔ)言的簡(jiǎn)單而且實(shí)際的面向方面的擴(kuò)展。僅通過(guò)加入幾個(gè)新結(jié)構(gòu),AspectJ提供了對(duì)模塊化實(shí)現(xiàn)各種橫切關(guān)注點(diǎn)的有力支持。向以有的Java開(kāi)發(fā)項(xiàng)目中加入AspectJ是一個(gè)直接而且漸增的任務(wù)。一條路徑就是通過(guò)從使用開(kāi)發(fā)方面開(kāi)始再到產(chǎn)品方面當(dāng)擁有了AspectJ的經(jīng)驗(yàn)后就使用開(kāi)發(fā)可重用方面。當(dāng)然可以選取其他的開(kāi)發(fā)路徑。例如,一些開(kāi)發(fā)者將從使用產(chǎn)品方面馬上得到好處,另外的人員可能馬上編寫(xiě)可重用的方面。

                 AspectJ可以使用基于名字和基于屬性這兩種橫切點(diǎn)。使用基于名字橫切點(diǎn)的方面僅影響少數(shù)幾個(gè)類(lèi),雖然它們是小范圍的,但是比起普通的Java實(shí)現(xiàn)來(lái)說(shuō)它們能夠減少大量的復(fù)雜度。使用基于屬性橫切點(diǎn)的方面可以有小范圍或著大范圍。使用AspectJ導(dǎo)致了橫切關(guān)注點(diǎn)的干凈、模塊化的實(shí)現(xiàn)。當(dāng)編寫(xiě)AspectJ方面時(shí),橫切關(guān)注點(diǎn)的結(jié)構(gòu)變得十分明顯和易懂。方面也是高度模塊化的,使得開(kāi)發(fā)可拔插的橫切功能變成現(xiàn)實(shí)。

                 AspectJ提供了比這兩部分簡(jiǎn)短介紹更多的功能。本系列的下一章內(nèi)容,The AspectJ Language,將介紹 AspectJ語(yǔ)言的更多細(xì)節(jié)和特征。系列的第三章,Examples將通過(guò)一些完整的例子說(shuō)明如何使用AspectJ。建議大家在仔細(xì)閱讀了接下來(lái)的兩章后再?zèng)Q定是否在項(xiàng)目中加入AspectJ。


          三、AspectJ的高級(jí)特性

          (一)、The reflection API

          說(shuō)到高級(jí)特性,首先要說(shuō)的就是AspectJ提供的一套reflection API,主要包括JoinPoint、JoinPoint.StaticPart和Signature三個(gè)主要的接口。你可以從aspectj.jar中的javadoc來(lái)了解它們的詳細(xì)情況。那它們能提供什么功能呢?其實(shí)從字面上就能大致明白:通過(guò)這三個(gè)接口能訪(fǎng)問(wèn)到Join Points的信息。譬如,調(diào)用thisJoinPoint.getArgs()就可以得到方法的參數(shù)列表。

          (二)、Aspect precedence

          在AspectJ中,pointcut和advice都會(huì)包含在一個(gè)aspect中。在應(yīng)用系統(tǒng)中,對(duì)同一個(gè)join point會(huì)有多種advice(logging,caching等),這就會(huì)引出一個(gè)問(wèn)題:如果系統(tǒng)中有很多的aspect,而這些aspect很有可能會(huì)捕獲同樣的join points,那這些aspect的執(zhí)行順序是如何安排的呢?

          AspectJ早已為我們考慮到了這個(gè)問(wèn)題,它提供了一種設(shè)置aspect precedence的方法。對(duì)三種不同的advice來(lái)說(shuō):

          1、before advice是先執(zhí)行higher-precedence,后執(zhí)行l(wèi)ower-precedence;

          2、around advice是higher-precedence包含lower-precedence,當(dāng)higher-precedence around advice沒(méi)有調(diào)用proceed()方法時(shí),lower-precedence不會(huì)被執(zhí)行;

          3、after advice與before advice正好相反,先執(zhí)行執(zhí)行l(wèi)ower-precedence,然后執(zhí)行higher-precedence。

          那應(yīng)該如何來(lái)聲明aspect precedence?非常簡(jiǎn)單,只要在aspect中使用如下的語(yǔ)法即可:

          declare precedence : TypePattern1, TypePattern2, ..;

          從左往右,排在前面的是higher-precedence advice,后面的是lower-precedence。

          (三)、Aspect association

          在Java中,為了節(jié)省對(duì)象每次構(gòu)建的耗費(fèi),增加效率,很多人會(huì)考慮使用Singleton模式,讓jvm中只有一個(gè)實(shí)例存在。AspectJ當(dāng)然為我們考慮到這個(gè)問(wèn)題,Aspect association實(shí)際上就是aspect與advised join point object的一種關(guān)聯(lián)關(guān)系,這很類(lèi)似于OO中association,譬如1:1,1:m等。Aspect association能讓我們能更好地控制aspect的狀態(tài)信息。

          在AspectJ中可以把Aspect association大致分為三類(lèi):

          1、Per virtual machine (default)

          一個(gè)jvm中只有一個(gè)aspect instance,AspectJ默認(rèn)association。

          2、Per object

          每一個(gè)advised join point object都會(huì)產(chǎn)生一個(gè)aspect instance,不過(guò)同一個(gè)object instance只會(huì)產(chǎn)生一個(gè)aspect instance。

          3、Per control-flow association

          這種association稍微復(fù)雜一些,它主要針對(duì)程序調(diào)用的控制流,譬如:A方法調(diào)用B方法,B方法又調(diào)用C方法,這就是control-flow。

          在aspect中聲明這三種association非常簡(jiǎn)單,它的主要語(yǔ)法如下:

          aspect [( )] {
          ... aspect body
          }

          Per virtual machine是aspectj的默認(rèn)association,不需要你額外的聲明,正常使用即可。

          Per object主要有兩種方式:perthis()和pertarget()。perthis()主要用于execution object,pertarget()主要用于target object,兩者非常類(lèi)似。

          Per control-flow中也包含兩種方式:percflow()和percflowbelow()。這兩者也很類(lèi)似,只是兩者的control-flow不太一樣而已。

          維護(hù)aspect的狀態(tài)信息還有一種方法,就是使用introduce。可以在aspect中introduce member fields,通過(guò)fields來(lái)保存狀態(tài)信息。

           

          四、AspectJ實(shí)例

           

          使用方面的Tracing程序

                 寫(xiě)一個(gè)具有跟蹤能力的類(lèi)是很簡(jiǎn)單的事情:一組方法,一個(gè)控制其開(kāi)或關(guān)的布爾變量,一種可選的輸出流,可能還有一些格式化輸出能力。這些都是Trace類(lèi)需要的東西。當(dāng)然,如果程序需要的話(huà),Trace類(lèi)也可以實(shí)現(xiàn)的十分的復(fù)雜。開(kāi)發(fā)這樣的程序只是一方面,更重要的是如何在合適的時(shí)候調(diào)用它。在大型系統(tǒng)開(kāi)發(fā)過(guò)程中,跟蹤程序往往影響效率,而且在正式版本中去除這些功能十分麻煩,需要修改任何包含跟蹤代碼的源碼。出于這些原因,開(kāi)發(fā)人員常常使用腳本程序以便向源碼中添加或刪除跟蹤代碼。

                 AspectJ可以更加方便的實(shí)現(xiàn)跟蹤功能并克服這些缺點(diǎn)。Tracing可以看作是面向整個(gè)系統(tǒng)的關(guān)注點(diǎn),因此,Tracing方面可以完全獨(dú)立在系統(tǒng)之外并且在不影響系統(tǒng)基本功能的情況下嵌入系統(tǒng)。

           

          應(yīng)用實(shí)例

          整個(gè)例子只有四個(gè)類(lèi)。應(yīng)用是關(guān)于Shape的。TwoShape類(lèi)是Shape類(lèi)等級(jí)的基類(lèi)。

          public abstract class TwoDShape {

              protected double x, y;

              protected TwoDShape(double x, double y) {

                  this.x = x; this.y = y;

              }

              public double getX() { return x; }

              public double getY() { return y; }

              public double distance(TwoDShape s) {

                  double dx = Math.abs(s.getX() - x);

                  double dy = Math.abs(s.getY() - y);

                  return Math.sqrt(dx*dx + dy*dy);

              }

              public abstract double perimeter();

              public abstract double area();

              public String toString() {

                  return (" @ (" + String.valueOf(x) + ", " + String.valueOf(y) + ") ");

              }

          }

          TwoShape類(lèi)有兩個(gè)子類(lèi),Circle和Square  

          public class Circle extends TwoDShape {

              protected double r;

              public Circle(double x, double y, double r) {

                  super(x, y); this.r = r;

              }

              public Circle(double x, double y) { this(  x,   y, 1.0); }

              public Circle(double r)           { this(0.0, 0.0,   r); }

              public Circle()                   { this(0.0, 0.0, 1.0); }

              public double perimeter() {

                  return 2 * Math.PI * r;

              }

              public double area() {

                  return Math.PI * r*r;

              }

              public String toString() {

                  return ("Circle radius = " + String.valueOf(r) + super.toString());

              }

          }

          public class Square extends TwoDShape {

              protected double s;    // side

              public Square(double x, double y, double s) {

                  super(x, y); this.s = s;

              }

              public Square(double x, double y) { this(  x,   y, 1.0); }

              public Square(double s)           { this(0.0, 0.0,   s); }

              public Square()                   { this(0.0, 0.0, 1.0); }

              public double perimeter() {

                  return 4 * s;

              }

              public double area() {

                  return s*s;

              }

              public String toString() {

                  return ("Square side = " + String.valueOf(s) + super.toString());

              }

          }

           

          Tracing版本一

          首先我們直接實(shí)現(xiàn)一個(gè)Trace類(lèi)并不使用方面。公共接口Trace.java

          public class Trace {

              public static int TRACELEVEL = 0;

              public static void initStream(PrintStream s) {...}

              public static void traceEntry(String str) {...}

              public static void traceExit(String str) {...}

          }

          如果我們沒(méi)有AspectJ,我們需要在所有需要跟蹤的方法或構(gòu)造子中直接調(diào)用traceEntry和traceExit方法并且初試化TRACELEVEL和輸出流。以上面的例子來(lái)說(shuō),如果我們要跟蹤所有的方法調(diào)用(包括構(gòu)造子)則需要40次的方法調(diào)用并且還要時(shí)刻注意沒(méi)有漏掉什么方法,但是使用方面我們可以一致而可靠的完成。TraceMyClasses.java

          aspect TraceMyClasses {

              pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square);

              pointcut myConstructor(): myClass() && execution(new(..));

              pointcut myMethod(): myClass() && execution(* *(..));

           

              before (): myConstructor() {

                  Trace.traceEntry("" + thisJoinPointStaticPart.getSignature());

              }

              after(): myConstructor() {

                  Trace.traceExit("" + thisJoinPointStaticPart.getSignature());

              }

           

              before (): myMethod() {

                  Trace.traceEntry("" + thisJoinPointStaticPart.getSignature());

              }

              after(): myMethod() {

                  Trace.traceExit("" + thisJoinPointStaticPart.getSignature());

              }

          }

          這個(gè)方面在合適的時(shí)候調(diào)用了跟蹤方法。根據(jù)此方面,跟蹤方法在Shape等級(jí)中每個(gè)方法或構(gòu)造子的入口和出口處調(diào)用,輸出的是各個(gè)方法的簽名。因?yàn)榉椒ê灻庆o態(tài)信息,我們可以利用thisJoinPointStaticPart對(duì)象獲得。運(yùn)行這個(gè)方面的main方法可以獲得以下輸出:

            --> tracing.TwoDShape(double, double)

            <-- tracing.TwoDShape(double, double)

            --> tracing.Circle(double, double, double)

            <-- tracing.Circle(double, double, double)

            --> tracing.TwoDShape(double, double)

            <-- tracing.TwoDShape(double, double)

            --> tracing.Circle(double, double, double)

            <-- tracing.Circle(double, double, double)

            --> tracing.Circle(double)

            <-- tracing.Circle(double)

            --> tracing.TwoDShape(double, double)

            <-- tracing.TwoDShape(double, double)

            --> tracing.Square(double, double, double)

            <-- tracing.Square(double, double, double)

            --> tracing.Square(double, double)

            <-- tracing.Square(double, double)

            --> double tracing.Circle.perimeter()

            <-- double tracing.Circle.perimeter()

          c1.perimeter() = 12.566370614359172

            --> double tracing.Circle.area()

            <-- double tracing.Circle.area()

          c1.area() = 12.566370614359172

            --> double tracing.Square.perimeter()

            <-- double tracing.Square.perimeter()

          s1.perimeter() = 4.0

            --> double tracing.Square.area()

            <-- double tracing.Square.area()

          s1.area() = 1.0

            --> double tracing.TwoDShape.distance(TwoDShape)

              --> double tracing.TwoDShape.getX()

              <-- double tracing.TwoDShape.getX()

              --> double tracing.TwoDShape.getY()

              <-- double tracing.TwoDShape.getY()

            <-- double tracing.TwoDShape.distance(TwoDShape)

          c2.distance(c1) = 4.242640687119285

            --> double tracing.TwoDShape.distance(TwoDShape)

              --> double tracing.TwoDShape.getX()

              <-- double tracing.TwoDShape.getX()

              --> double tracing.TwoDShape.getY()

              <-- double tracing.TwoDShape.getY()

            <-- double tracing.TwoDShape.distance(TwoDShape)

          s1.distance(c1) = 2.23606797749979

            --> String tracing.Square.toString()

              --> String tracing.TwoDShape.toString()

              <-- String tracing.TwoDShape.toString()

            <-- String tracing.Square.toString()

          s1.toString(): Square side = 1.0 @ (1.0, 2.0)

           

          Tracing版本二

                 版本二實(shí)現(xiàn)了可重用的tracing方面,使其不僅僅用于Shape的例子。首先定義如下的抽象方面Trace.java

          abstract aspect Trace {

           

              public static int TRACELEVEL = 2;

              public static void initStream(PrintStream s) {...}

              protected static void traceEntry(String str) {...}

              protected static void traceExit(String str) {...}

          abstract pointcut myClass();

           

          }

          為了使用它,我們需要定義我們自己的子類(lèi)。

          public aspect TraceMyClasses extends Trace {

              pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square);

           

              public static void main(String[] args) {

                  Trace.TRACELEVEL = 2;

                  Trace.initStream(System.err);

                  ExampleMain.main(args);

              }

          }

          注意我們僅僅在類(lèi)中聲明了一個(gè)切點(diǎn),它是超類(lèi)中聲明的抽象切點(diǎn)的具體實(shí)現(xiàn)。版本二的Trace類(lèi)的完整實(shí)現(xiàn)如下

          abstract aspect Trace {

           

              // implementation part

           

              public static int TRACELEVEL = 2;

              protected static PrintStream stream = System.err;

              protected static int callDepth = 0;

           

              public static void initStream(PrintStream s) {

                  stream = s;

              }

              protected static void traceEntry(String str) {

                  if (TRACELEVEL == 0) return;

                  if (TRACELEVEL == 2) callDepth++;

                  printEntering(str);

              }

              protected static void traceExit(String str) {

                  if (TRACELEVEL == 0) return;

                  printExiting(str);

                  if (TRACELEVEL == 2) callDepth--;

              }

              private static void printEntering(String str) {

                  printIndent();

                  stream.println("--> " + str);

              }

              private static void printExiting(String str) {

                  printIndent();

                  stream.println("<-- " + str);

              }

              private static void printIndent() {

                  for (int i = 0; i < callDepth; i++)

                      stream.print("  ");

              }

           

              // protocol part

           

              abstract pointcut myClass();

           

              pointcut myConstructor(): myClass() && execution(new(..));

              pointcut myMethod(): myClass() && execution(* *(..));

           

              before(): myConstructor() {

                  traceEntry("" + thisJoinPointStaticPart.getSignature());

              }

              after(): myConstructor() {

                  traceExit("" + thisJoinPointStaticPart.getSignature());

              }

           

              before(): myMethod() {

                  traceEntry("" + thisJoinPointStaticPart.getSignature());

              }

              after(): myMethod() {

                  traceExit("" + thisJoinPointStaticPart.getSignature());

              }

          }

          它與版本一的不同包括幾個(gè)部分。首先在版本一中Trace用單獨(dú)的類(lèi)來(lái)實(shí)現(xiàn)而方面是針對(duì)特定應(yīng)用實(shí)現(xiàn)的,而版本二則將Trace所需的方法和切點(diǎn)定義融合在一個(gè)抽象方面中。這樣做的結(jié)果是traceEntry和traceExit方法不需要看作是公共方法,它們將由方面內(nèi)部的通知調(diào)用,客戶(hù)完全不需要知道它們的存在。這個(gè)方面的一個(gè)關(guān)鍵點(diǎn)是使用了抽象切點(diǎn),它其實(shí)與抽象方法類(lèi)似,它并不提供具體實(shí)現(xiàn)而是由子方面實(shí)現(xiàn)它。

           

          Tracing版本三

                 在前一版本中,我們將traceEntry和traceExit方法隱藏在方面內(nèi)部,這樣做的好處是我們可以方便的更改接口而不影響余下的代碼。

                 重新考慮不使用AspectJ的程序。假設(shè),一段時(shí)間以后,tracing的需求變了,我們需要在輸出中加入方法所屬對(duì)象的信息。至少有兩種方法實(shí)現(xiàn),一是保持traceEntry和traceExit方法不變,那么調(diào)用者有責(zé)任處理顯示對(duì)象的邏輯,代碼可能如下

                 Trace.traceEntry("Square.distance in " + toString());

          另一種方法是增強(qiáng)方法的功能,添加一個(gè)參數(shù)表示對(duì)象,例如

            public static void traceEntry(String str, Object obj);

            public static void traceExit(String str, Object obj);

          然而客戶(hù)仍然有責(zé)任傳遞正確的對(duì)象,調(diào)用代碼如下

                 Trace.traceEntry("Square.distance", this);

          這兩種方法都需要?jiǎng)討B(tài)改變其余代碼,每個(gè)對(duì)traceEntry和traceExit方法的調(diào)用都需要改變。

                 這里體現(xiàn)了方面實(shí)現(xiàn)的另一個(gè)好處,在版本二的實(shí)現(xiàn)中,我們只需要改變Trace方面內(nèi)部的一小部分代碼,下面是版本三的Trace方面實(shí)現(xiàn)

          abstract aspect Trace {

           

              public static int TRACELEVEL = 0;

              protected static PrintStream stream = null;

              protected static int callDepth = 0;

           

              public static void initStream(PrintStream s) {

                  stream = s;

              }

           

              protected static void traceEntry(String str, Object o) {

                  if (TRACELEVEL == 0) return;

                  if (TRACELEVEL == 2) callDepth++;

                  printEntering(str + ": " + o.toString());

              }

           

              protected static void traceExit(String str, Object o) {

                  if (TRACELEVEL == 0) return;

                  printExiting(str + ": " + o.toString());

                  if (TRACELEVEL == 2) callDepth--;

              }

           

              private static void printEntering(String str) {

                  printIndent();

                  stream.println("Entering " + str);

              }

           

              private static void printExiting(String str) {

                  printIndent();

                  stream.println("Exiting " + str);

              }

           

              private static void printIndent() {

                  for (int i = 0; i < callDepth; i++)

                      stream.print("  ");

              }

           

              abstract pointcut myClass(Object obj);

           

              pointcut myConstructor(Object obj): myClass(obj) && execution(new(..));

              pointcut myMethod(Object obj): myClass(obj) &&

                  execution(* *(..)) && !execution(String toString());

           

              before(Object obj): myConstructor(obj) {

                  traceEntry("" + thisJoinPointStaticPart.getSignature(), obj);

              }

              after(Object obj): myConstructor(obj) {

                  traceExit("" + thisJoinPointStaticPart.getSignature(), obj);

              }

           

              before(Object obj): myMethod(obj) {

                  traceEntry("" + thisJoinPointStaticPart.getSignature(), obj);

              }

              after(Object obj): myMethod(obj) {

                  traceExit("" + thisJoinPointStaticPart.getSignature(), obj);

              }

          }

          在此我們必須在methods切點(diǎn)排除toString方法的執(zhí)行。問(wèn)題是toString方法在通知內(nèi)部調(diào)用,因此如果我們跟蹤它,我們將陷入無(wú)限循環(huán)中。這一點(diǎn)不明顯,所以必須在寫(xiě)通知時(shí)格外注意。如果通知回調(diào)對(duì)象,通常都回存在循環(huán)的可能性。

                 事實(shí)上,簡(jiǎn)單的排除連接點(diǎn)的執(zhí)行并不夠,如果在這之中調(diào)用了其他跟蹤方法,那么就必須提供以下限制

          && !cflow(execution(String toString()))

          排除toString方法的執(zhí)行以及在這之下的所有連接點(diǎn)。

                 總之,為了實(shí)現(xiàn)需求的改變我們必須在Trace方面中做一些改變,包括切點(diǎn)說(shuō)明。但是實(shí)現(xiàn)的改變只局限于Trace方面內(nèi)部,而如果沒(méi)有方面,則需要更改每個(gè)應(yīng)用類(lèi)的實(shí)現(xiàn)。
          (來(lái)源:http://befresh.blogbus.com/logs/2004/08/339330.html;
          Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=15565)

           

           

          posted @ 2008-10-25 00:08 hk2000c 閱讀(944) | 評(píng)論 (2)編輯 收藏

          .概述

          1.1 JMS與ActiveMQ特性  

             JMS始終在JavaEE五花八門(mén)的協(xié)議里,WebService滿(mǎn)天飛的時(shí)候占一位置,是因?yàn)椋?/p>

          • 它可以把不影響用戶(hù)執(zhí)行結(jié)果又比較耗時(shí)的任務(wù)(比如發(fā)郵件通知管理員)異步的扔給JMS 服務(wù)端去做,而盡快的把屏幕返還給用戶(hù)。
          • 服務(wù)端能夠多線(xiàn)程排隊(duì)響應(yīng)高并發(fā)的請(qǐng)求,并保證請(qǐng)求不丟失。
          • 可以在Java世界里達(dá)到最高的解耦。客戶(hù)端與服務(wù)端無(wú)需直連,甚至無(wú)需知曉對(duì)方是誰(shuí)、在哪里、有多少人,只要對(duì)流過(guò)的信息作響應(yīng)就行了,在企業(yè)應(yīng)用環(huán)境復(fù)雜時(shí)作用明顯。

              ActiveMQ的特性:

          • 完全支持JMS1.1和J2EE 1.4規(guī)范的 JMS Provider實(shí)現(xiàn),也是Apache Geronimo默認(rèn)的JMS provider。
          • POJO withdout EJB Container,不需要實(shí)現(xiàn)EJB繁瑣復(fù)雜的Message Bean接口和配置。
          • Spring Base,可以使用Spring的各種特性如IOC、AOP 。
          • Effective,基于Jencks的JCA Container實(shí)現(xiàn) pool connection,control transactions and manage security。 

          1.2 SpringSide 的完全POJO的JMS方案   

            SpringSide 2.0在BookStore示例中,演示了用戶(hù)下訂單時(shí),將發(fā)通知信到用戶(hù)郵箱的動(dòng)作,通過(guò)JMS交給JMS服務(wù)端異步完成,避免了郵件服務(wù)器的堵塞而影響用戶(hù)的下訂。

            全部代碼于examples\bookstore\src\java\org\springside\bookstore\components\activemq 目錄中。

            一個(gè)JMS場(chǎng)景通常需要三者參與:

          • 一個(gè)POJO的的Message Producer,負(fù)責(zé)使用Spring的JMS Template發(fā)送消息。
          • 一個(gè)Message Converter,負(fù)責(zé)把Java對(duì)象如訂單(Order)轉(zhuǎn)化為消息,使得Producer能夠直接發(fā)送POJO。
          • 一個(gè)MDP Message Consumer,負(fù)責(zé)接收并處理消息。

            SpringSide 2.0采用了ActiveMQ 4.1-incubator 與Spring 2.0 集成,對(duì)比SS1.0M3,有三個(gè)值得留意的地方,使得代碼中幾乎不見(jiàn)一絲JMS的侵入代碼:

          1. 采用Spring2.0的Schema式簡(jiǎn)化配置。
          2. 實(shí)現(xiàn)Message Converter轉(zhuǎn)化消息與對(duì)象,使得Producer能夠直接發(fā)送POJO而不是JMS Message。
          3. 使用了Spring2.0的DefaultMessageListenerContainer與MessageListenerAdapter,消息接收者不用實(shí)現(xiàn)MessageListener 接口。
          4. 同時(shí),Spring 2.0 的DefaultMessageListenerContainer 代替了SS1.0M3中的Jenck(JCA Container),充當(dāng)MDP Container的角色。

          2.引入ActiveMQ的XSD

            ActiveMQ4.1 響應(yīng)Spring 2.0號(hào)召,支持了引入XML Schema namespace的簡(jiǎn)單配置語(yǔ)法,簡(jiǎn)化了配置的語(yǔ)句。 

            在ApplicationContext.xml(Spring的配置文件)中引入ActiveMQ的XML Scheam 配置文件),如下:

          <beans
            xmlns="http://www.springframework.org/schema/beans"
            xmlns:amq="http://activemq.org/config/1.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://activemq.org/config/1.0 http://people.apache.org/repository/org.apache.activemq/xsds/activemq-core-4.1-incubator-SNAPSHOT.xsd">

          由于ActiveMQ4.1 SnapShot的那個(gè)XSD有部分錯(cuò)誤,因此使用的是自行修改過(guò)的XSD。

          先在ClassPath根目錄放一個(gè)修改過(guò)的activemq-core-4.1-incubator-SNAPSHOT.xsd。

          在ClassPath 下面建立META-INF\spring.schemas 內(nèi)容如下。這個(gè)spring.schemas是spring自定義scheam的配置文件,請(qǐng)注意"http:\://"部分寫(xiě)法

          http\://people.apache.org/repository/org.apache.activemq/xsds/activemq-core-4.1-incubator-SNAPSHOT.xsd=/activemq-core-4.1-incubator-SNAPSHOT.xsd

          3. 配置方案

          3.1 基礎(chǔ)零件 

          1. 配置ActiveMQ Broker  

             暫時(shí)采用在JVM中嵌入這種最簡(jiǎn)單的模式,  當(dāng)spring初始化時(shí)候,ActiveMQ embedded Broker 就會(huì)啟動(dòng)了。

          <!--  lets create an embedded ActiveMQ Broker -->
          <amq:broker useJmx="false" persistent="false">
            	<amq:transportConnectors>
              		<amq:transportConnector uri="tcp://localhost:0"/>
           	</amq:transportConnectors>
           </amq:broker>

          2. 配置(A)ConnectionFactory

            由于前面配置的Broker是JVM embedded 所以URL為:vm://localhost

          <!--  ActiveMQ connectionFactory to use  -->
           <amq:connectionFactory id="jmsConnectionFactory" brokerURL="vm://localhost"/>

          3 配置(B)Queue

          <!--  ActiveMQ destinations to use  -->
           <amq:queue name="destination" physicalName="org.apache.activemq.spring.Test.spring.embedded"/>

          4. 配置(C)Converter

             配置Conveter,使得Producer能夠直接發(fā)送Order對(duì)象,而不是JMS的Message對(duì)象。

          <!--  OrderMessage converter  -->
           <bean id="orderMessageConverter" class="org.springside.bookstore.components.activemq.OrderMessageConverter"/>  

          3.2  發(fā)送端 

          1 配置JmsTemplate

             Spring提供的Template,綁定了(A)ConnectionFactory與(C)Converter。

          <!--  Spring JmsTemplate config -->
           <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory">
             <!--  lets wrap in a pool to avoid creating a connection per send -->
             <bean class="org.springframework.jms.connection.SingleConnectionFactory">
              <property name="targetConnectionFactory" ref="jmsConnectionFactory"/>
             </bean>
            </property>
            <!-- custom MessageConverter -->
            <property name="messageConverter" ref="orderMessageConverter"/>
           </bean>

          2.Producer

             消息發(fā)送者,使用JmsTemplate發(fā)送消息,綁定了JmsTemplate (含A、C)與(B)Queue。

          <!-- POJO which send Message uses  Spring JmsTemplate,綁定JMSTemplate 與Queue -->
           <bean id="orderMessageProducer" class="org.springside.bookstore.components.activemq.OrderMessageProducer">
            <property name="template" ref="jmsTemplate"/>
            <property name="destination" ref="destination"/>
           </bean>

          3.3 接收端

            1.接收處理者(MDP)

              使用Spring的MessageListenerAdapter,指定負(fù)責(zé)處理消息的POJO及其方法名,綁定(C)Converter。

            <!--  Message Driven POJO (MDP),綁定Converter -->
           <bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
            <constructor-arg>
             <bean class="org.springside.bookstore.components.activemq.OrderMessageConsumer">
              <property name="mailService" ref="mailService"/>
             </bean>
            </constructor-arg>
            <!--  may be other method -->
            <property name="defaultListenerMethod" value="sendEmail"/>
            <!-- custom MessageConverter define -->
            <property name="messageConverter" ref="orderMessageConverter"/>
           </bean> 

          2. listenerContainer

              負(fù)責(zé)調(diào)度MDP, 綁定(A) connectionFactory, (B)Queue和MDP。

          <!--  this is the attendant message listener container -->
           <bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
            <property name="connectionFactory" ref="jmsConnectionFactory"/>
            <property name="destination" ref="destination"/>
            <property name="messageListener" ref="messageListener"/>
           </bean>

            互相綁定的關(guān)系有點(diǎn)暈,發(fā)送端和接收端都以不同形式綁定了(A) connectionFactory, (B)Queue和 (C)Converter。

          4. 下篇


          1. 說(shuō)明

             請(qǐng)先閱讀ActiveMQ4.1 +Spring2.0的POJO JMS方案(上)

             本篇將補(bǔ)充說(shuō)明了:

             1) 使用數(shù)據(jù)庫(kù)持久化消息,保證服務(wù)器重啟時(shí)消息不會(huì)丟失
             2) 使用Jencks作正宗的JCA Container。

          2.持久化消息

          2.1 給Broker加入Persistence 配置

          在配置文件applicationContext-activemq-embedded-persitence.xml中的<amq:broker>節(jié)點(diǎn)加入  

          <amq:persistenceAdapter>
          <amq:jdbcPersistenceAdapter id="jdbcAdapter" dataSource="#hsql-ds" createTablesOnStartup="true" useDatabaseLock="false"/>
          </amq:persistenceAdapter>

          請(qǐng)注意MSSQL(2000/2005)和HSQL由于不支持[SELECT  * ACTIVEMQ_LOCK FOR UPDATE ]語(yǔ)法,因此不能使用默認(rèn)的userDatabaseLock="true",只能設(shè)置成useDatabaseLock="false"

          2.2 配置多種數(shù)據(jù)源

          配置多種數(shù)據(jù)源,給jdbcPersistenceAdapter使用,SpringSide 中使用的內(nèi)嵌HSQL

           <!-- The HSQL Datasource that will be used by the Broker -->
          <bean id="hsql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
          <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
          <property name="url" value="jdbc:hsqldb:res:hsql/activemq"/>
          <property name="username" value="sa"/>
          <property name="password" value=""/>
          <property name="poolPreparedStatements" value="true"/>
          </bean>

          2. 3 說(shuō)明

             筆者僅僅使用了jdbcPersistenceAdapter,其實(shí)在ActiveMQ的XSD已經(jīng)描述了多種PersistenceAdapter,可以參考對(duì)應(yīng)的XSD文件.

            另外對(duì)于數(shù)據(jù)庫(kù)的差異主要表現(xiàn)在設(shè)置了userDatabaseLock="true"之后,ActiveMQ使用的[SELECT * ACTIVEMQ_LOCK FOR UPDATE] 上面,會(huì)導(dǎo)致一些數(shù)據(jù)庫(kù)出錯(cuò)(測(cè)試中MSSQL2000/2005,HSQL都會(huì)導(dǎo)致出錯(cuò))。另外HSQL的腳本請(qǐng)參見(jiàn)activemq.script。

          3. Jenck(JCA Container)  

             Spring 2.0本身使用DefaultMessageListenerContainer 可以充當(dāng)MDP中的Container角色,但是鑒于Jencks是JCA標(biāo)準(zhǔn)的,它不僅僅能夠提供jms的jca整合,包括其他資源比如jdbc都可以做到j(luò)ca管理

          所以,同時(shí)完成了這個(gè)ActiveMQ+Spring+Jencks 配置演示,更多的針對(duì)生產(chǎn)系統(tǒng)的JCA特性展示,會(huì)在稍后的開(kāi)發(fā)計(jì)劃討論中確定。

               此文檔適用于說(shuō)明使用 Jecncks 和 使用Spring 2.0(DefaultMessageListenerContainer)  充當(dāng)MDP Container時(shí)的區(qū)別,同時(shí)演示Jecnks 的Spring 2.0 新配置實(shí)例。

          3.1 引入ActiveMQ ResourceAdapter 和Jencks 的XSD

            在ApplicationContext.xml(Spring的配置文件)中引入ActiveMQ ResourceAdapter 和Jencks 的XML Scheam 配置文件),如下:

             ActiveMQ4.1 響應(yīng)Spring 2.0號(hào)召,支持了引入XML Schema namespace的簡(jiǎn)單配置語(yǔ)法,簡(jiǎn)化了配置的語(yǔ)句。 

            在ApplicationContext.xml(Spring的配置文件)中引入ActiveMQ的XML Scheam 配置文件),如下:

          <beans
          xmlns="http://www.springframework.org/schema/beans"   xmlns:amq="http://activemq.org/config/1.0"   xmlns:ampra="http://activemq.org/ra/1.0"   xmlns:jencks="http://jencks.org/1.3"   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://activemq.org/config/1.0 http://people.apache.org/repository/org.apache.activemq/xsds/activemq-core-4.1-incubator-SNAPSHOT.xsd
            http://activemq.org/ra/1.0 http://people.apache.org/repository/org.apache.activemq/xsds/activemq-ra-4.1-incubator-SNAPSHOT.xsd
            http://jencks.org/1.3 http://repository.codehaus.org/org/jencks/jencks/1.3/jencks-1.3.xsd">

          由于ActiveMQ RA和Jencks 那個(gè)XSD 仍然有部分錯(cuò)誤,因此使用的是自行修改過(guò)的XSD。(是xs:any元素引起的錯(cuò)誤)

          先在ClassPath根目錄放一個(gè)修改過(guò)的activemq-ra-4.1-incubator-SNAPSHOT.xsd和jencks-1.3.xsd。

          同樣修改 ClassPath 下面META-INF\spring.schemas 增加內(nèi)容如下。這個(gè)spring.schemas是spring自定義scheam的配置文件,請(qǐng)注意"http:\://"部分寫(xiě)法

          http\://people.apache.org/repository/org.apache.activemq/xsds/activemq-ra-4.1-incubator-SNAPSHOT.xsd=/activemq-ra-4.1-incubator-SNAPSHOT.xsd
          http\://repository.codehaus.org/org/jencks/jencks/1.3/jencks-1.3.xsd=/jencks-1.3.xsd

          3.2  配置方案

          3.2.1 基礎(chǔ)零件 

          1. 配置ActiveMQ Broker  參見(jiàn) ActiveMQ+Spring

          2. 配置ActiveMQ Resource Adapter

          <amqra:managedConnectionFactory id="jmsManagedConnectionFactory" resourceAdapter="#resourceAdapter"/><amqra:resourceAdapter id="resourceAdapter" serverUrl="vm://localhost" />

          3. 配置Jencks 基礎(chǔ)配置

             具體的配置可以參見(jiàn)Jencks的XSD

          <!-- jencks PoolFactory config-->
          <jencks:singlePoolFactory id="poolingSupport" maxSize="16" minSize="5" blockingTimeoutMilliseconds="60" idleTimeoutMinutes="60" matchOne="true" matchAll="true" selectOneAssumeMatch="true" /> <!-- jencks XATransactionFactory -->
          <jencks:xATransactionFactory id="transactionSupport" useTransactionCaching="true" useThreadCaching="true" />  
          <!-- jencks ConnectionManagerFactory -->
          <jencks:connectionManagerFactory id="connectionManager" containerManagedSecurity="false"  poolingSupport="#poolingSupport" transactionSupport="#transactionSupport" /> <!-- jencks TransactionContextManagerFactory -->
          <jencks:transactionContextManagerFactory id="transactionContextManagerFactory"/>
            

          4. 配置給JmsTemplate使用的connectionFactory (主要是生成者/發(fā)送者 使用)

             這里注意下,在配置jmsTemplate的使用的targetConnectionFactory就是使用jencks配置的connectionManager

          <!-- spring config jms with jca-->
           <bean id="jmsManagerConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
            <property name="managedConnectionFactory">
             <ref local="jmsManagedConnectionFactory" />
            </property>
            <property name="connectionManager">
             <ref local="connectionManager" />
            </property>
           </bean>
           
           <!--  Spring JmsTemplate config -->
           <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory">
             <!--  lets wrap in a pool to avoid creating a connection per send -->
             <bean class="org.springframework.jms.connection.SingleConnectionFactory">
                  <property name="targetConnectionFactory" ref="jmsManagerConnectionFactory" />
             </bean>
            </property>
            <!-- custom MessageConverter -->
            <property name="messageConverter" ref="orderMessageConverter" />
           </bean>  

          5. 配置Spring 2.0的MessageListenerAdapter,保證不需要用戶(hù)實(shí)現(xiàn)MessageListener

             見(jiàn)ActiveMQ+Spring

           6.配置Jecnks 充當(dāng)MDP的Container

            就是把上面的MessageListenerAdapter配置到Jencks里面,完成整個(gè)MDP的配置

           <!-- Jencks Container-->
           <jencks:jcaContainer>  	<jencks:bootstrapContext>
             		<jencks:bootstrapContextFactory threadPoolSize="25" />
            	</jencks:bootstrapContext>
            		<jencks:connectors>
             	   <!-- use jencks container (use spring MessageListenerAdapter)-->
             		<jencks:connector ref="messageListener">
              			<jencks:activationSpec>
               				<amqra:activationSpec destination="org.apache.activemq.spring.Test.spring.embedded" destinationType="javax.jms.Queue" />
              			</jencks:activationSpec>
             		</jencks:connector>  	</jencks:connectors> 		 <jencks:resourceAdapter>
             		<amqra:resourceAdapter serverUrl="vm://localhost" />
            	</jencks:resourceAdapter>
           </jencks:jcaContainer>

          posted @ 2008-04-12 01:37 hk2000c 閱讀(755) | 評(píng)論 (0)編輯 收藏

          請(qǐng)參考一下馬琳、王皓的主管教練吳敬平的文章

          直板反膠正手拉球的基本原理和訓(xùn)練方法

          乒乓球基本上是一項(xiàng)圓周運(yùn)動(dòng),正手和反手拉球都是以運(yùn)動(dòng)員的身體重心為軸心、以身體到身體重心的連線(xiàn)為半徑進(jìn)行圓周運(yùn)動(dòng)。因此,不管是正手還是反手擊球都必須符合這個(gè)原理,從這個(gè)意義上講,正手拉球動(dòng)作的基本原理就是一種力的傳遞。
          正手拉球的基本要點(diǎn)
          基本站位:兩腿張開(kāi)與肩的寬度為差不多相同,身體稍微前傾,重心在前腳掌上,拉球時(shí),身體向右轉(zhuǎn)(以右手為例),重心放在右腳上,在轉(zhuǎn)腰的過(guò)程中,用腰控制大臂,右肩稍底,小臂自然下垂,用手腕控制板型,板型前傾(拇指用力壓住球板,食指稍微放松,中指頂住球板),板型前傾的角度因來(lái)球的旋轉(zhuǎn)不同而調(diào)整。
          擊球原理:擊球的時(shí)候,以右手為例,首先是腿上發(fā)力,向左蹬腿,身體重心從右腳向左腳轉(zhuǎn)換,交換重心,身體前迎,身體前迎的方向要和擊球的方向一致。然后是腰上發(fā)力,用腰帶動(dòng)大臂轉(zhuǎn)動(dòng),把力傳遞到前臂,在擊球一瞬間,收縮前臂用力擊球。從力學(xué)的原理講,正手拉球前,小臂和大臂之間的角度越小越好,這是加大半徑,半徑越大,初速度就越大,在擊球瞬間突然收縮前臂,使半徑變小而獲得加速度,使速度加快,力量加大。擊球時(shí),小臂和大臂之間角度的變化要根據(jù)來(lái)球和擊球的需要進(jìn)行變化。很多運(yùn)動(dòng)員在進(jìn)行正手拉球時(shí)往往只注意了收前臂而忽略了轉(zhuǎn)腰,用腰來(lái)控制手臂的發(fā)力,或者是注意了用腰的發(fā)力來(lái)帶動(dòng)手臂而忽略了收前臂,前臂和大臂之間的角度幾乎沒(méi)有變化或變化很小。總結(jié)起來(lái)正手拉球應(yīng)注意四點(diǎn):1、必須注意重心的交換,重心迎前的方向要和擊球的方向一致。2、一定要用腰控制大臂,是腰上發(fā)力,而不是用手臂發(fā)力,注意拉球時(shí)腿、腰、大臂、前臂、手腕發(fā)力的協(xié)調(diào)。3、擊球瞬間必須快速收縮前臂、手腕發(fā)力,前臂收縮的速度越快,發(fā)出的力量就越大。4、擊球點(diǎn)必須保持在身體的右前方,擊球點(diǎn)離身體越近,越容易控制球。有一點(diǎn)值得注意的是不管是什么樣的拉球動(dòng)作,必須和你自身具備的身體條件相符合,只要不影響動(dòng)作的發(fā)力就可以,沒(méi)有什么固定的動(dòng)作模式。另外就是在擊球前,球板和球之間要有一定的距離,盡量主動(dòng)去擊球,而不要讓球來(lái)撞你的球拍,或者是球與球拍之間的距離太小,容易被來(lái)球頂住,影響你的發(fā)力。

          正手拉球的方法與技巧
          正手拉球是一門(mén)很復(fù)雜的技術(shù),有近臺(tái)拉球、中近臺(tái)拉球、遠(yuǎn)臺(tái)拉球,有拉上旋球、下旋球,有近臺(tái)快帶、反拉弧圈球,拉半出臺(tái)球等等。不管拉球有多么復(fù)雜,但有一點(diǎn)是最重要的基礎(chǔ),就是步法。步法的好壞,直接關(guān)系到正手拉球的命中率、力量的大小和拉球時(shí)的調(diào)節(jié)能力。要想練好正手拉球,就必須先練好步法。而在這一點(diǎn)上,是專(zhuān)業(yè)運(yùn)動(dòng)員和業(yè)余運(yùn)動(dòng)員最大的區(qū)別所在,業(yè)余運(yùn)動(dòng)員不可能像專(zhuān)業(yè)運(yùn)動(dòng)員那樣進(jìn)行大量的高強(qiáng)度的步法訓(xùn)練。但有一點(diǎn)是相同的,那就是擊球的技巧。只要能夠做到因勢(shì)利導(dǎo),充分發(fā)揮現(xiàn)有的條件,也會(huì)收到一定的效果。下面,我給大家介紹一些比較實(shí)用的訓(xùn)練方法和技巧:
          1、拉好定點(diǎn)下旋球:拉沖下旋球是直板反膠最基礎(chǔ)的基本功,在拉下旋球時(shí)除了注意前面提到的基本動(dòng)作要領(lǐng)以外,要特別注意手腕的用力方法。在擊球的瞬間是用手腕去摩擦球,擊球點(diǎn)在來(lái)球的中上部,在用手腕摩擦球時(shí)還要根據(jù)來(lái)球旋轉(zhuǎn)的強(qiáng)弱再加上一定的撞擊。就是人們常說(shuō)的又摩又打。拉沖下旋球旋轉(zhuǎn)弱的來(lái)球要連摩擦帶撞擊,撞擊可稍大于摩擦。拉沖下旋球旋轉(zhuǎn)強(qiáng)的來(lái)球必須用力摩擦擊球,用自己拉球的力量抵消來(lái)球的旋轉(zhuǎn)。在擊球的瞬間要特別注意擊球時(shí)一定要把球往前送,不能靠力量去硬碰球。這就是我們常說(shuō)的“吃球”,盡量讓球在球板上停留的時(shí)間長(zhǎng)一些。經(jīng)常這樣訓(xùn)練拉球,你對(duì)球的感覺(jué)就會(huì)越來(lái)越好,拉球就會(huì)越來(lái)越有數(shù),慢慢達(dá)到運(yùn)用自如。訓(xùn)練的方法,在沒(méi)有多球條件的情況下可采用拉球一方發(fā)下旋球到對(duì)方的反手位讓對(duì)方搓長(zhǎng)球到側(cè)身位,然后發(fā)力拉沖這個(gè)球。拉球時(shí)一定要注意用全力拉沖,不要考慮下一板球?qū)Ψ绞欠衲軌蚍肋^(guò)來(lái)。要的就是讓你防不過(guò)來(lái)。經(jīng)常這樣訓(xùn)練,你的拉球力量一定會(huì)提高。在有多球的條件下,可讓對(duì)方發(fā)下旋球到你的側(cè)身位,定點(diǎn)發(fā)力拉沖這種球。拉球時(shí)要掌握好擊球時(shí)間,在對(duì)方來(lái)球跳到最高點(diǎn)或下降前期擊球最好。擊球時(shí)間一定要相對(duì)固定,這樣容易掌握拉球的命中率,好調(diào)節(jié)。出界多就向前送一點(diǎn),下網(wǎng)多就多摩擦一點(diǎn)。在定點(diǎn)拉沖下旋球比較有數(shù)的情況下,再把來(lái)球的落點(diǎn)擴(kuò)大到全臺(tái)的定點(diǎn)拉沖,這樣不斷加大拉球的難度,拉球的水平就會(huì)不斷提高。
          2、拉好定點(diǎn)上旋球:拉上旋球和下旋球不同的是,拉上旋球擊球點(diǎn)在來(lái)球的上部,摩擦球要大于撞擊球,擊球的瞬間一定要往前送。訓(xùn)練的方法基本和搶拉下旋球一樣,只是來(lái)球的旋轉(zhuǎn)不一樣,是上旋球。在推擋后側(cè)身發(fā)力拉沖這板球,或?qū)Ψ阶兡阏治缓蟀l(fā)力拉沖,反復(fù)練習(xí)。有多球訓(xùn)練的條件,可以由對(duì)方直接發(fā)上旋球到你的正手位和側(cè)身位搶沖,落點(diǎn)可以從定點(diǎn)到不定點(diǎn),逐步提高擊球的難度。
          3、練好反拉弧圈球:反拉弧圈球是一種高級(jí)技術(shù),尤其是業(yè)余運(yùn)動(dòng)員掌握了這項(xiàng)技術(shù)就像如魚(yú)得水,你就掌握了比賽的主動(dòng)權(quán)。因?yàn)橐话愕臉I(yè)余運(yùn)動(dòng)員在拉弧圈球時(shí)拉高吊弧圈球的時(shí)候多,你掌握了反拉弧圈球的技術(shù),你就站在了比對(duì)方高一擋的層次上。反拉弧圈球的要領(lǐng),首先要自己發(fā)力,盡量少借對(duì)方的旋轉(zhuǎn),用自己拉球的力量去抵消對(duì)方來(lái)球的旋轉(zhuǎn)。其次是在反拉時(shí)摩擦球一定要薄,摩擦球的上部甚至頂部,既要借對(duì)方來(lái)球的旋轉(zhuǎn)的力,還要自己發(fā)力摩擦球。越是自己發(fā)力反拉,命中率越高。越是怕對(duì)方的旋轉(zhuǎn)去碰球,越是容易吃對(duì)方的旋轉(zhuǎn)。訓(xùn)練的方法,對(duì)方發(fā)下旋球到你的反手位,你搓球到對(duì)方側(cè)身位,對(duì)方拉高吊弧圈球到你反手位,你側(cè)身反拉,這樣反復(fù)練習(xí),等基本掌握了反拉弧圈球的規(guī)律以后,再把反拉擴(kuò)大到全臺(tái)和不定點(diǎn)反拉。
          4、近臺(tái)正手快帶弧圈球:這項(xiàng)技術(shù)是防守中很先進(jìn)的技術(shù),也是很難掌握的技術(shù),是90年代后期才逐漸被采用的技術(shù)。在這之前人們?cè)谡治坏姆朗囟际瞧綋酰鑼?duì)方來(lái)球的旋轉(zhuǎn)把球擋過(guò)去,因而在比賽關(guān)鍵的時(shí)刻就很容易因緊張而造成失誤,即使不失誤,防過(guò)去的球也沒(méi)有威脅,很容易被對(duì)方連續(xù)進(jìn)攻。到90年代后期,中國(guó)的運(yùn)動(dòng)員把反拉的技術(shù)運(yùn)用在近臺(tái)的防守上,特別是直板反膠打法的運(yùn)動(dòng)員運(yùn)用更多,加快了攻防轉(zhuǎn)換的節(jié)奏,收到了很好的效果,馬林在這項(xiàng)技術(shù)的運(yùn)用上是非常突出的。這項(xiàng)技術(shù)要求運(yùn)動(dòng)員的對(duì)來(lái)球的判斷要非常快、準(zhǔn)確,手上對(duì)球的感覺(jué)要求很高,因?yàn)橛泻芏嗲蚴窃谑ド眢w重心或不到位的情況下,完全靠運(yùn)動(dòng)員手上的功夫去完成技術(shù)動(dòng)作。我想雖然目前在業(yè)余運(yùn)動(dòng)員中能真正掌握這項(xiàng)技術(shù)的不多,但已經(jīng)具備了一定水平的運(yùn)動(dòng)員可以去嘗試一下,也許你會(huì)有意外的收獲。
          這項(xiàng)技術(shù)的技巧主要在于掌握好擊球時(shí)間和手腕的用力,擊球時(shí)間盡量在球的起跳前期(上升期),當(dāng)步法實(shí)在到不了位的情況下,還可以在球剛一跳起時(shí)就擊球。擊球時(shí)靠腰和手腕發(fā)力,接觸球的頂部。接觸球時(shí)既要借對(duì)方來(lái)球旋轉(zhuǎn)的力,同時(shí)自己一定要發(fā)力去摩擦球,盡量摩擦薄一點(diǎn),摩擦厚就容易下網(wǎng),在摩擦球的瞬間一定要把球往前頂。訓(xùn)練方法可采用搓下旋球到對(duì)方正手位讓對(duì)方拉弧圈球到自己的正手位,然后正手近臺(tái)快帶。這樣反復(fù)練習(xí)就會(huì)逐漸掌握擊球的基本方法,在快帶對(duì)方從下旋球拉起來(lái)的弧圈球比較熟練的情況下,再進(jìn)行推直線(xiàn)讓對(duì)方拉弧圈球到自己的正手位快帶上旋弧圈球的訓(xùn)練。這樣,你就會(huì)慢慢掌握在防守中正手近臺(tái)快帶弧圈球的技術(shù)。這項(xiàng)技術(shù)的關(guān)鍵點(diǎn)是在擊球時(shí)一定摩擦球要薄,而且自己一定要主動(dòng)發(fā)力去帶球。

          正手拉球的注意事項(xiàng)
          業(yè)余選手在練習(xí)正手拉球時(shí),要注意掌握以下幾點(diǎn):
          1、收前臂:在正手拉球時(shí)一定要注意收前臂,大臂和小臂之間的角度一定不能固定,要根據(jù)來(lái)球來(lái)決定擺臂的大小。但要注意一點(diǎn),收前臂一定要用腰來(lái)控制。
          2、轉(zhuǎn)腰:由于乒乓球是圓周運(yùn)動(dòng),擊球時(shí)用腰來(lái)控制手是非常重要的環(huán)節(jié),擊球時(shí)球拍的后引不是用手往后拉手,而是用轉(zhuǎn)腰來(lái)完成,用腰固定大臂,轉(zhuǎn)腰的速度要遠(yuǎn)遠(yuǎn)快于拉手。就是說(shuō),在擊球前的擺臂是先轉(zhuǎn)腰而不是先拉手。而我們好多球迷們?cè)诖蚯驎r(shí)都是先拉手,不知道轉(zhuǎn)腰,因而在擊球時(shí)經(jīng)常出現(xiàn)身體不協(xié)調(diào)導(dǎo)致發(fā)力不集中或發(fā)不出力。
          3、擊球點(diǎn):擊球點(diǎn)的最佳位置是在身體的右前方(以右手為例),要保持最佳的擊球位置就必須學(xué)好步法,保持好身體的重心,重心的高低要根據(jù)來(lái)球來(lái)決定。馬林經(jīng)常使用的側(cè)身倒地爆沖是不得已而為之,對(duì)方搓過(guò)來(lái)的球又低又長(zhǎng),拉完以后不可能再還原,只有搏殺。馬林在拉這種球的時(shí)候重心低,但是擊球點(diǎn)是球的最高點(diǎn)或下降前期。正手位大角度的球擊球點(diǎn)要根據(jù)自己步法移動(dòng)的情況來(lái)決定擊球點(diǎn)的高低。一般情況下是在球的下降中期和后期擊球。
          4、手腕的運(yùn)用:在拉球時(shí),手腕要相對(duì)固定,不能晃動(dòng)太大,擊球瞬間用中指頂住球板發(fā)力摩擦球。另外手腕還具有擊球瞬間的調(diào)節(jié)功能,比如在拉球時(shí)突然感到球的旋轉(zhuǎn)比自己預(yù)想的要轉(zhuǎn)時(shí)就靠手腕來(lái)調(diào)節(jié)擊球的力量大小和摩擦球的部位。在不到位和頂住自己的情況下,就要靠腰和手腕來(lái)調(diào)節(jié)擊球點(diǎn)。特別是在比賽中,很多球都不是很規(guī)則,來(lái)球的落點(diǎn)也是你最難受的地方,這時(shí)候就要靠手腕來(lái)調(diào)節(jié),手腕的調(diào)節(jié)主要靠大拇指和中指用力來(lái)完成。其次拉球時(shí)板型的控制也要靠手腕來(lái)完成,有很多的直板運(yùn)動(dòng)員正手拉球時(shí)吊腕很厲害,這影響發(fā)力,一般情況下,手腕和前臂幾乎在一條直線(xiàn)上,球板把與手腕之間的角度在45度左右。
          5、吃球:我們看一個(gè)運(yùn)動(dòng)員拉球的好壞,主要是看他拉球時(shí)是否吃球。吃球就是球在球板上的停留時(shí)間比較長(zhǎng),而不是球一碰球板就出去了。要做到拉球時(shí)吃球,就必須每一板球都主動(dòng)發(fā)力去摩擦球,在平時(shí)的訓(xùn)練中盡量少打借力球。拉球吃球的好壞,在平時(shí)訓(xùn)練中不是很明顯,但在比賽中就有很大的區(qū)別。很多球都是在你不到位的情況下要完成拉球的動(dòng)作,就全靠你用手腕主動(dòng)發(fā)力去摩擦球來(lái)調(diào)節(jié),你習(xí)慣了主動(dòng)發(fā)力拉球,就能在比賽中控制拉球時(shí)力量和擊球部位的調(diào)節(jié),拉過(guò)去很多高難度的球。
          6、搶沖上旋球和下旋球的區(qū)別:動(dòng)作上沒(méi)有多大的區(qū)別,區(qū)別在于搶沖下旋球時(shí)擊球點(diǎn)在球的中上部,發(fā)力的時(shí)候根據(jù)來(lái)球的旋轉(zhuǎn)可帶點(diǎn)撞擊;搶沖上旋球時(shí)擊球點(diǎn)在球的頂部,主動(dòng)發(fā)力摩擦球,擊球時(shí)身體重心也隨之向前。特別是在反拉弧圈球時(shí),摩擦薄反而容易過(guò)去,摩擦厚或帶點(diǎn)撞擊就容易失誤。
          7、微調(diào):很多球迷朋友提出這個(gè)問(wèn)題,我認(rèn)為要在比賽中做到這一點(diǎn)是比較難的。這首先取決于你個(gè)人本身的球感,就是你手上對(duì)球的感覺(jué)。其次是在訓(xùn)練中不斷地培養(yǎng)你對(duì)球的旋轉(zhuǎn)的理解,要清楚地知道你打過(guò)去的球是什么樣的旋轉(zhuǎn),對(duì)方回過(guò)來(lái)的球又是什么樣的旋轉(zhuǎn)。只有這樣,你才會(huì)根據(jù)來(lái)球的不同,在很困難正常擊球的情況下,在來(lái)球很不規(guī)則的情況下,在球落在邊邊角角很難回?fù)舻那闆r下,通過(guò)手上的調(diào)節(jié)把球回?fù)暨^(guò)去。因此,對(duì)于業(yè)余球迷朋友們來(lái)講,最主要的是去琢磨球的旋轉(zhuǎn)變化,把這個(gè)規(guī)律基本掌握住了,你就具備了微調(diào)的能力
          正手弧圈球技術(shù)是乒乓球技術(shù)中最基本也是最重要的技術(shù)之一。拉好弧圈球的三個(gè)要素就是腿、腰、手;三者要協(xié)調(diào)一致,才能發(fā)揮弧圈球的最大威力。


          從弧圈球的風(fēng)格上來(lái)講,目前主要分為歐洲派和亞洲派。歐洲選手拉弧圈球的時(shí)候,撞擊的成分比較多,因此球在飛行的過(guò)程中速度快,力量大,弧線(xiàn)低;亞洲選手拉弧圈球的時(shí)候,摩擦的成分比較多,因此球在彈起后的過(guò)程中速度快,旋轉(zhuǎn)強(qiáng),弧線(xiàn)低。隨著弧圈球技術(shù)的發(fā)展,目前各國(guó)選手都在相互學(xué)習(xí),相互借鑒,因此并沒(méi)有十分明顯的風(fēng)格區(qū)別,而是根據(jù)不同的球運(yùn)用不同的技術(shù)。

          弧圈球的基本技術(shù)動(dòng)作并不難,但是要想拉好弧圈球必須要勤學(xué)苦練,才能是自己的技術(shù)有大幅度的提高。如何掌握基本的弧圈球技術(shù)呢?(以右手握拍選手為例)

          一、技術(shù)動(dòng)作分解

          1.準(zhǔn)備動(dòng)作:

          拉球之前,站位一定要合理。一般來(lái)說(shuō),站位距球臺(tái)邊緣1.5米左右。左腳前,右腳后,兩腳間距略比肩寬,右腳尖于左腳腳窩的位置平齊,以?xún)赡_前腳掌內(nèi)側(cè)著地。兩腿彎曲,含胸,重心放低,身體與球臺(tái)邊緣的夾角大概為45度左右。

          2.拉球:

          拉上旋球時(shí),右肩略微下沉,同時(shí)橫向轉(zhuǎn)腰,右臂自然放松,靠橫向轉(zhuǎn)腰動(dòng)作完成引拍的過(guò)程。此時(shí),以右腳為軸,重心放到右腿上。然后,右腿蹬地,腰部橫向回轉(zhuǎn),并帶動(dòng)右臂,注意此時(shí)右臂仍為放松狀態(tài)。待腰轉(zhuǎn)到基本與球臺(tái)邊緣平行的時(shí)候開(kāi)始收縮前臂,擊球。重心由右腿轉(zhuǎn)移到兩腿上,兩肩持平。擊球時(shí),要找好擊球時(shí)間。擊球時(shí)間分為上升期和下降期,上升期是指來(lái)球即將達(dá)到最高點(diǎn)的時(shí)候,下降期是指來(lái)球從最高點(diǎn)剛剛下落的時(shí)候。一般來(lái)說(shuō),來(lái)球位于右腹部前方一尺多的距離時(shí)擊球感覺(jué)最好,可以發(fā)出力。擊球時(shí),要注意摩擦球,主要向前發(fā)力。擊球后要注意大臂、小臂立刻放松,還原。
          此主題相關(guān)圖片如下:


          關(guān)于擊球部位,對(duì)于以拉打?yàn)橹骱湍Σ翞橹魇怯袇^(qū)別的。 以拉打?yàn)橹鞯倪x手,擊球的部位一般為B點(diǎn)或B、C點(diǎn)之間。以摩擦為主的選手,擊球部位一般為C點(diǎn)。
          拉下旋球的動(dòng)作要領(lǐng)與拉上旋球基本一致。只是拉下旋球時(shí),右肩沉的更低一些,擊球的部位一般為B點(diǎn),且用力的方向向上多一些。

          3.步法

          拉球時(shí),要根據(jù)來(lái)球的位置,時(shí)刻跑動(dòng)來(lái)調(diào)節(jié)擊球的最佳位置。跑動(dòng)時(shí)要保證重心盡量平穩(wěn),身體不要亂晃。

          二、高吊弧圈與前沖弧圈

          高吊弧圈一般是針對(duì)拉下旋球而言的。高吊弧圈以旋轉(zhuǎn)見(jiàn)長(zhǎng),但是弧線(xiàn)略高,速度較慢。高吊弧圈的擊球部位一般為B點(diǎn),甚至是A、B點(diǎn)之間,這要根據(jù)來(lái)球的旋轉(zhuǎn)而定。拉高吊弧圈,右肩下沉的較低,用力方向向上的比較多,先要制造一個(gè)高過(guò)球網(wǎng)的弧線(xiàn),然后用力方向向前,再制造一個(gè)向前的弧線(xiàn)。如果一味的向上硬拉,則球很容易出界。

          前沖弧圈速度快,力量大,但旋轉(zhuǎn)稍遜。拉前沖弧圈,擊球部位一般為C點(diǎn)或B、C點(diǎn)之間。右肩略微下沉,用力方向向前比較多。若來(lái)球的下旋旋轉(zhuǎn)很強(qiáng),則必須增加轉(zhuǎn)腰的幅度和前臂收縮的速度,以增大對(duì)球的摩擦力。

          三、臺(tái)內(nèi)弧圈球技術(shù)

          臺(tái)內(nèi)弧圈球的技術(shù)難度比較大。首先要判斷來(lái)球的位置和高度,根據(jù)來(lái)球的高度來(lái)決定引拍的高度。拉臺(tái)內(nèi)弧圈球,一般引拍的高度較高,往往與臺(tái)面高度持平,甚至高于臺(tái)面。擊球部位一般為D點(diǎn)。由于摩擦球的部位很薄,因?yàn)閷?duì)于下旋非常強(qiáng)的臺(tái)內(nèi)球,處理起來(lái)難度很大。而對(duì)于不太轉(zhuǎn)的下旋球來(lái)說(shuō),臺(tái)內(nèi)弧圈球給對(duì)方造成的威脅還是很大的。拉臺(tái)內(nèi)弧圈球,要注意用力方向向上多一些,繼而向前,要把弧線(xiàn)拉短。

          四、套膠與弧圈球

          進(jìn)口套膠與國(guó)產(chǎn)套膠的性能不同,對(duì)于拉弧圈球的風(fēng)格有一定的影響。
          歐洲人拉球多為拉打,因?yàn)闅W洲的套膠膠皮黏性差,海綿偏軟,但彈性好。使用進(jìn)口套膠,球在接觸到拍子之后,海綿被擠壓的程度較深,海綿被壓縮的行程長(zhǎng),這樣就削減了來(lái)球的大部分旋轉(zhuǎn)和力量,因此采用拉打的手法可以很好的控制來(lái)球,加之歐洲人身高馬大,爆發(fā)力非常好。這樣的拉球威力不小。

          亞洲人拉球多摩擦,因?yàn)閲?guó)產(chǎn)的套膠,如狂飆系列套膠,膠皮黏性強(qiáng),海綿彈性非常實(shí)在,非常大。在球接觸拍子的時(shí)候,膠皮給了來(lái)球很大的阻力,而海綿被壓縮的程度也不大,這樣就造成的脫板速度很快。因此只有多摩擦,以旋轉(zhuǎn)克旋轉(zhuǎn)才能拉出高質(zhì)量的弧圈球。所以使用國(guó)產(chǎn)套膠對(duì)拉球的技術(shù)要求較高。

          隨著乒乓器材的發(fā)展,國(guó)內(nèi)已經(jīng)生產(chǎn)出很多新產(chǎn)品,兼具了國(guó)產(chǎn)與進(jìn)口的很多優(yōu)點(diǎn),對(duì)于眾多的乒乓球愛(ài)好者來(lái)說(shuō),又多了很多的選擇。

          五、拉球的常見(jiàn)問(wèn)題
          1. 重心后坐。
          重心后坐,自然使腿部力量不能發(fā)揮出來(lái),使手臂的走向多為向上,削減了拉球的速度、力量和旋轉(zhuǎn)。
          2. 手臂僵硬。
          引手的過(guò)程中,肌肉僵硬,大大降低了控制球的能力,并鎖住了力量。擊球后肌肉僵硬,使力量不能全部發(fā)揮出來(lái),并降低了還原速度。
          3. 轉(zhuǎn)腰不夠。
          只靠手臂拉球,速度、力量、旋轉(zhuǎn)都有很大的損失。
          4. 抬肘、抬肩。
          使腿、腰、手不能協(xié)調(diào)一致,當(dāng)力量從腿、腰傳到手的時(shí)候,能量中斷。
          5.步法遲鈍。
          等球,使擊球點(diǎn)太低,使全身的力量用不到球上。
          posted @ 2008-03-08 01:23 hk2000c 閱讀(725) | 評(píng)論 (0)編輯 收藏


          拉弧圈最重要的環(huán)節(jié)是什么?是吃球。

          就是盡量延長(zhǎng)球和膠皮接觸的時(shí)間,主動(dòng)發(fā)力控球,把揮拍的能量充分作用到球體上。吃球就是球在球板上的停留時(shí)間比較長(zhǎng),而不是球一碰球板就出去了。要做到拉球時(shí)吃球,就必須每一板球都主動(dòng)發(fā)力去摩擦球,在平時(shí)的訓(xùn)練中盡量少打借力球。

          延長(zhǎng)控球時(shí)間靠是什么?反膠、軟板、灌膠、先打后摩,還有最重要的一點(diǎn)是在加速揮拍過(guò)程中擊球。加速擊球就好比在阻力較小的平面上推箱子,只有不斷加速去推,才能一直不離手,力量才能充分傳遞到箱子上。也就是說(shuō),拉弧圈最好是不斷加速追著球摩擦。

          如果拉上旋來(lái)球,就是逆旋轉(zhuǎn)擊球,球和膠皮接觸的瞬間,球和膠皮的相對(duì)速度大,來(lái)球減轉(zhuǎn)的過(guò)程就是個(gè)緩沖過(guò)程,球不會(huì)很快脫板,一般不會(huì)有吃不住球的感覺(jué)。

          如果拉下旋來(lái)球,則是順旋轉(zhuǎn)擊球,如果揮拍向上的速度低于旋轉(zhuǎn), 便無(wú)法吃得住球。就好比玩陀螺,抓住轉(zhuǎn)動(dòng)的陀螺容易,因?yàn)槭悄嫘D(zhuǎn),而給陀螺加轉(zhuǎn)就很困難,要用比陀螺更快速度的鞭子去抽。這一點(diǎn)對(duì)著削球手感覺(jué)最為明顯,力量還沒(méi)作用到球上,球就脫板了,常會(huì)有吃不住球的情況發(fā)生。如果仔細(xì)觀察錄像,國(guó)手們拉削球時(shí)揮拍摩擦都極快,揮拍之所以快的就是靠發(fā)力抵消來(lái)球的旋轉(zhuǎn)。對(duì)下旋來(lái)球, 揮拍速度是不能低于旋轉(zhuǎn)的。

          拉下旋球?yàn)楸WC能吃住球需掌握三個(gè)要點(diǎn):

          一是增大球和球板的正壓力,就是“又摩又打”,增大正壓力便于摩擦。

          二是加快向上向前的揮拍速度,包括手腕也要加上摩擦球的動(dòng)作。

          三是掌握擊球時(shí)間。一般是在下降期拉,一方面下旋球在空中飛行時(shí)會(huì)逐漸減轉(zhuǎn),另一方面,球在下落時(shí)由于下落速度和球的旋轉(zhuǎn)方向相反,兩個(gè)速度相抵,揮拍相對(duì)速度就體現(xiàn)的更快一些。

          第一板從下旋拉起的弧圈很難防守,也是因?yàn)榫哂衅?#8220;順旋”的加成效果。一旦練成,對(duì)對(duì)手威懾極大。





          前言:都說(shuō)下旋球下降期好拉,為甚么?什么出轉(zhuǎn)沒(méi)出轉(zhuǎn),都是憑感覺(jué)。請(qǐng)看物理學(xué)的精確分析。我們對(duì)事物不僅要知其然,也要知其所以然。

          如圖:球?yàn)橄滦O(shè)球拍垂直向上摩擦,與球在a點(diǎn)接觸。假設(shè)球旋轉(zhuǎn)每秒30轉(zhuǎn),直徑40MM,則a點(diǎn)線(xiàn)速度為:

          V2=2πr*30 = 3.768m /s(即如果球原地向上轉(zhuǎn),a點(diǎn)的對(duì)地速度)

          1、拉上升期,a點(diǎn)速度為轉(zhuǎn)速加球速。

          設(shè)球向上的分速度v0 = 2m/s.a點(diǎn)對(duì)于地面的速度為 v0+v2 = 5.768m/s .如果靠摩擦把球拉起,拍速必須大于a點(diǎn)速度。約21公里/小時(shí)。

          2、拉下降期,a點(diǎn)速度為轉(zhuǎn)速減球速。

          設(shè)球向下落的分速度v0 = 2m/s.a點(diǎn)對(duì)于地面的速度為 v0-v2 = 1.768m/s .如果靠摩擦把球拉起,拍速必須大于a點(diǎn)速度。約6.3公里/小時(shí)。

          可見(jiàn)拉上升期比下降期需要三倍的速度!

           

           

          posted @ 2008-03-08 01:19 hk2000c 閱讀(1003) | 評(píng)論 (0)編輯 收藏

             我們?cè)趩挝焕镎{(diào)試用戶(hù)系統(tǒng)時(shí),單位的網(wǎng)絡(luò)地址一般和用戶(hù)的網(wǎng)絡(luò)地址不在一個(gè)網(wǎng)段上,如果沒(méi)有路由器則兩網(wǎng)不能互通,那對(duì)工作會(huì)很有影響。硬路由器價(jià)格昂貴也沒(méi)有必要去配,因?yàn)镾OLARIS可以很容易地設(shè)成軟件路由器,而不需另外花費(fèi)。

            1、編輯文件/etc/hosts,為該工作站加另一個(gè)網(wǎng)段地址:

             #vi/etc/hosts

             127.0.0.1localhost

             192.9.200.1serverloghost;本例的主機(jī)名及地址

             192.9.201.1 anoserver;另一個(gè)對(duì)應(yīng)的名稱(chēng)及地址

            2、編輯文件/etc/nerworks,將兩個(gè)網(wǎng)絡(luò)的地址加入:

             #vi /etc/networks

             loc 192.9.200;本網(wǎng)網(wǎng)址

             ano 192.9.201;另一個(gè)網(wǎng)的網(wǎng)址

            3、新建文件/etc/gateways,該文件只要存在沒(méi)有內(nèi)容也可,以使SOLARIS在啟動(dòng)時(shí)運(yùn)行路由器服務(wù)進(jìn)程。

             #cat/dev/null>/etc/gateways

            4、查詢(xún)主網(wǎng)卡的名稱(chēng):

             #ifconfig-a;列出系統(tǒng)中的所有網(wǎng)絡(luò)接口

             loO:flags=849<UP,LOOPBACK,RUN-NONG,MULTICAST>mtu 8232

             inet 127.0.0.1 netmask

             ff000000

             hneO:flags=863<UP,BROADCAST,NO-TRAILRS,RUNNNHG,MULTICAST>mtu1500

             inet 192.2.200.1 netmask ffffff00 broadcast

             192.2.200.255

             ether 8:0:20:1:2:3

             hme即為工作站上所配的100M網(wǎng)卡名,如果你所用的是10M網(wǎng)卡則名為le。

            5、新建文件/etc/hostname.hme0:1,將/etc/josts中的另一個(gè)主機(jī)名填入,以使SOLARIS啟動(dòng)時(shí)在物理接口hme0上建立一個(gè)邏輯接口。

            6、設(shè)置完以上各步后,重啟工作站

            7、效果:

             在工作站啟動(dòng)中,可以看到“machine is a router.”的噗顯示。表明本機(jī)已成為一個(gè)路由器,會(huì)向網(wǎng)絡(luò)上發(fā)RIP包,用接口查詢(xún)命令可見(jiàn):

             #ifcofig -a ;列出系統(tǒng)中的所有網(wǎng)絡(luò)接口

             lo0:flags=849<UP,LOOPBACK,RUNNNG,MULTICAST> mtu8232

             inet 127.0.0 .1etmask ff00000

             hne0:flags=863<UP,BROADCAST,NOTRAILERS,RUN-NING,MULTICAST>mtu 1500

             inet 192.9.200.1 netmask ffff00 broadcast

             192.9.200.255

             hne0:1:flags=8d0<UP,BROADCAST,NOTRAULERS,RUMNNNG,MULTICAST>mtu 1500

             inet 192.9.201.1 netmask ffff00 broadcast

             192.9.201.255

            以上表明已啟動(dòng)了hme0上的一個(gè)邏輯接口,地址為192.9.201.1。

            在別的UNIX機(jī)器上,會(huì)根據(jù)RIP包自動(dòng)將該工作站加入到路由表中,在PC機(jī)上(例如WIN95),只要在控制面板中將TCP/IPM網(wǎng)絡(luò)的網(wǎng)關(guān)設(shè)置為該工作站的地址(使用與本機(jī)同一個(gè)網(wǎng)絡(luò)的地址),就可以與另一網(wǎng)絡(luò)的機(jī)器通迅了。
          posted @ 2008-03-05 16:36 hk2000c 閱讀(454) | 評(píng)論 (0)編輯 收藏

          乓球意識(shí),是指運(yùn)動(dòng)員在乒乓球教學(xué)訓(xùn)練和比賽中的一種具有明確目的性和方向性的自覺(jué)的心理活動(dòng)。乒乓球意識(shí)的最顯著特點(diǎn)是它的能動(dòng)性。
            近年來(lái),意識(shí)一詞的使用日趨廣泛,如:“樹(shù)立首都意識(shí)”、“樹(shù)立奧運(yùn)意識(shí)”等。這里所說(shuō)的意識(shí)概念,和我們乒乓球運(yùn)動(dòng)中所說(shuō)的意識(shí)概念是一致的。就是要你想到北京是祖國(guó)的首都,想到奧運(yùn)會(huì),并以此來(lái)指導(dǎo)我們的思想和行動(dòng)。
            這樣,意識(shí)又可以理解為是一個(gè)思路或觀點(diǎn),它只是讓你自覺(jué)地想著這個(gè)問(wèn)題。至于怎樣想、用什么方法去解決這個(gè)問(wèn)題哪是技術(shù)問(wèn)題。以判斷意識(shí)為例,它只是要運(yùn)動(dòng)員想著判斷球,注意區(qū)別來(lái)球的不同特點(diǎn)。至于怎樣判斷來(lái)球,那是技術(shù)方法問(wèn)題,不屬判斷意識(shí)的范疇了。如有人打球時(shí),不看對(duì)方打的是什么球,一律愣頭愣腦地抽,結(jié)果失誤頻頻。這是缺乏判斷意識(shí)的典型表現(xiàn)。另一人懂得應(yīng)該判斷對(duì)方來(lái)球,實(shí)踐中也在緊緊地盯著球,但由于對(duì)方發(fā)球質(zhì)量高,結(jié)果接發(fā)球時(shí)還是“吃”了。這就不是判斷意識(shí)的問(wèn)題,而是還未掌握好接發(fā)球的方法。
            科學(xué)意識(shí)。一般人都能打乒乓球,但卻不是誰(shuí)都能打好。乒乓球運(yùn)動(dòng)有其自身的客觀規(guī)律。欲打好,則必須使自己的行為和心理符合乒乓球運(yùn)動(dòng)的客觀規(guī)律。為此,我們必須不斷地總結(jié)自己和他人的訓(xùn)練和比賽經(jīng)驗(yàn),不斷地學(xué)習(xí)科學(xué)文化知識(shí)(采用時(shí)代可能提供的先進(jìn)思想和先進(jìn)的科學(xué)技術(shù)方法、手段),不斷地探求乒乓球運(yùn)動(dòng)的規(guī)律,并用這些規(guī)律來(lái)指導(dǎo)自己的實(shí)踐。
            苦練與巧練相結(jié)合的意識(shí)。沒(méi)有一名優(yōu)秀的乒乓球運(yùn)動(dòng)員是不苦練的,但卻不是所有苦練者都能成為優(yōu)秀運(yùn)動(dòng)員。乒乓球運(yùn)動(dòng)有其自身的規(guī)律,只有當(dāng)人們的行動(dòng)符合其規(guī)律時(shí),才能獲得成功。探索規(guī)律,并用此來(lái)指導(dǎo)自己,這就是巧。所以,誰(shuí)要想打好乒乓球,就必須苦練與巧練相結(jié)合。沒(méi)有巧練的苦練,是傻練,甚至在一定意義上可說(shuō)是白練;沒(méi)有苦練做基礎(chǔ)的巧練,也稱(chēng)不上是巧,因?yàn)樗`反了訓(xùn)練的最基本規(guī)律。
            立志意識(shí)。志向,是一種巨大的力量,它能使人產(chǎn)生堅(jiān)強(qiáng)的意志和毅力,推動(dòng)人們的實(shí)踐活動(dòng)。志向總是同毅力相伴而行。一名運(yùn)動(dòng)員沒(méi)有堅(jiān)定不移的志向,就不可能有堅(jiān)強(qiáng)的意志和毅力,也就不可能實(shí)現(xiàn)自己的志向。少兒學(xué)打乒乓球,一般多從興趣開(kāi)始,而教練員則應(yīng)隨其進(jìn)步不斷地培養(yǎng)他們立志的意識(shí)。否則,就會(huì)如同古人所云:“志不立,天下無(wú)可成之事。”

            判斷意識(shí)。對(duì)付不同的來(lái)球,應(yīng)用不同的打法。若想打好球,首先應(yīng)對(duì)來(lái)球作出及時(shí)、準(zhǔn)確的判斷。這是正確還擊來(lái)球的前提。
            盯球意識(shí)。盯球,是正確判斷的基礎(chǔ)。不少人對(duì)來(lái)球判斷不及時(shí)或錯(cuò)誤,都是因?yàn)槎⑶虿粔颉_\(yùn)動(dòng)員每打完一板球后,都應(yīng)隨球密切注視對(duì)方擊球的動(dòng)作(尤其是擊球瞬間的動(dòng)作),并緊盯對(duì)方擊出球的弧線(xiàn)。
            移步意識(shí)。對(duì)方來(lái)球落點(diǎn)和節(jié)奏不定,為確保在最佳的位置和時(shí)間擊球,或最大限度地發(fā)揮個(gè)人的特長(zhǎng)技術(shù)(如反手位用側(cè)身攻),必須移步擊球。應(yīng)明確,打乒乓球絕不是單純的手法問(wèn)題,隨技術(shù)水平的提高,腳步移動(dòng)的重要性將越來(lái)越明;顯、它是爭(zhēng)取主動(dòng)、搶先進(jìn)攻的有力保證。

            探索合理?yè)羟螯c(diǎn)位置的意識(shí)。所謂的擊球點(diǎn)位置,即擊球點(diǎn)與身體的相對(duì)位置。各種技術(shù)動(dòng)作,都有一個(gè)最適宜的擊球位置。它雖有個(gè)一般的規(guī)律,但因人而宜十分重要。所以,運(yùn)動(dòng)員在打球的實(shí)踐中必須不斷地琢磨與研究自己擊球時(shí)最適宜的位置…
            打、摩結(jié)合意識(shí)。打乒乓球有兩個(gè)最基本的力,一個(gè)是撞擊球的力,簡(jiǎn)稱(chēng)為打;另一個(gè)是摩擦球之力,簡(jiǎn)稱(chēng)為摩。除近網(wǎng)大高球,可以用單純的打外,打其它的球,都必須是打與摩的結(jié)合。細(xì)究起來(lái),這里還有兩層意思。
            1、快抽時(shí),以打?yàn)橹鳎Σ翞檩o。打,可增加球的速度和力量;摩,可使球產(chǎn)生上旋,上旋有利于制造合理的擊球弧線(xiàn)。
            2、制造旋轉(zhuǎn)時(shí)(如拉弧圈球),應(yīng)以摩擦球?yàn)橹鳌5且晃蹲非竽Σ粒瑒?shì)必物極必反。擦球太薄,反而用不上力,自然難以打出旋轉(zhuǎn)強(qiáng)烈的球來(lái)。應(yīng)先打后摩,即以打的動(dòng)作將球近似粘于拍面,然后再加力摩擦。
            調(diào)節(jié)意識(shí)。無(wú)論哪種技術(shù)動(dòng)作,在還擊不同性能的來(lái)球時(shí),都必須自覺(jué)地調(diào)節(jié)動(dòng)作。具體可細(xì)分為:
            1、力量調(diào)節(jié)意識(shí):根據(jù)來(lái)球情況,適當(dāng)調(diào)節(jié)自己的發(fā)力。來(lái)球慢且高,發(fā)大力;攻對(duì)方搓過(guò)來(lái)的下旋球,自己發(fā)力為主,稍借對(duì)方來(lái)球之力;對(duì)方拉沖不特別兇、球略向前拱時(shí),借力中發(fā)力;對(duì)方發(fā)力抽或沖時(shí),自己應(yīng)借力擋一板或?qū)Ω兑话澹灰税l(fā)大力。
            2、拍形調(diào)節(jié)意識(shí):應(yīng)視來(lái)球旋轉(zhuǎn)與高低,適當(dāng)調(diào)節(jié)拍形。來(lái)球低或帶強(qiáng)烈下旋時(shí),拍形稍后仰;來(lái)球不轉(zhuǎn)或與可網(wǎng)高時(shí),拍形與臺(tái)面垂直;來(lái)球上旋或高于球時(shí),拍形前傾;
            3、引拍調(diào)節(jié)意識(shí):應(yīng)視來(lái)球的快慢、高低、旋轉(zhuǎn)等變化,相應(yīng)調(diào)整引拍動(dòng)作的快慢、大小和高低,切忌習(xí)慣性引拍(即不看來(lái)球,打完一板球后就習(xí)慣地將球拍引至原來(lái)位置)。如,對(duì)方拉過(guò)強(qiáng)烈上旋的弧圈球來(lái),應(yīng)高手引拍,并及時(shí)向前迎球(不要等球,更不能有向后的拉拍動(dòng)作);對(duì)方來(lái)球下旋且低,應(yīng)低手引拍;對(duì)方來(lái)球很快,應(yīng)減小引拍動(dòng)作幅度,加快引拍速度;來(lái)球慢且高,應(yīng)適當(dāng)加大引拍幅度,以利加力抽殺。
            4、手指調(diào)節(jié)意識(shí):打乒乓球,無(wú)論身體何部位發(fā)力,最后都要通過(guò)手指作用于球拍。手指是身體發(fā)力時(shí)離球最近的部位,感覺(jué)最敏銳。在發(fā)力時(shí),手指有如長(zhǎng)鞭之梢兒,往往起到畫(huà)龍點(diǎn)睛的作用。尤其是在發(fā)球時(shí),觸球瞬間的技巧全在手腕、手指的發(fā)力上。
            5、調(diào)節(jié)用力方向意識(shí):打球時(shí),應(yīng)視不同來(lái)球,注意調(diào)節(jié)用力方向。如,攻下旋低球,應(yīng)多向上用力;攻不轉(zhuǎn)球,以向前打?yàn)橹鳎还ゴ蛏闲龔?qiáng)烈的加轉(zhuǎn)弧圈球時(shí),應(yīng)向前并稍向下用力。
            還原意識(shí)。每打完一板球后,應(yīng)迅速調(diào)整重心,將身體盡量還原至接近準(zhǔn)備姿勢(shì),以為還擊下一板球做好準(zhǔn)備。有些人因缺乏此意識(shí),打完一板球后,身體重心、手臂和球拍較長(zhǎng)時(shí)間地停留在結(jié)束動(dòng)作上,待對(duì)方將球還擊過(guò)來(lái),往往有來(lái)不及的感覺(jué)。
            體會(huì)擊球動(dòng)作的意識(shí)。每打一板球,都要對(duì)整個(gè)擊球動(dòng)作有清晰的肌肉感覺(jué)和表象,尤其是拍觸球瞬間的發(fā)力情況應(yīng)該清清楚楚。打丟一板球,應(yīng)立刻回憶動(dòng)作,哪兒錯(cuò)了?怎樣才算正確?隨著技術(shù)水平的提高,動(dòng)腦筋還應(yīng)越來(lái)越細(xì)。如攻球出界了,出界多少?剛才的擊球動(dòng)作為什么把球打出界這么遠(yuǎn)?有了這種意識(shí),練技術(shù)才會(huì)有收獲。否則,一點(diǎn)體會(huì)沒(méi)有,技術(shù)怎么能進(jìn)步?
            掌握擊球動(dòng)作實(shí)質(zhì)的意識(shí)。研究技術(shù)動(dòng)作,要注意它的外形,但尤為重要的是應(yīng)分析擊球動(dòng)作的實(shí)質(zhì)。擺速快、能發(fā)力、打摩結(jié)合好、命中率高、適應(yīng)來(lái)球的范圍廣(即能依打不同來(lái)球的要求相應(yīng)調(diào)整動(dòng)作),這樣的動(dòng)作,就是好動(dòng)作。

            擊球動(dòng)作的時(shí)空意識(shí)。分析技術(shù)動(dòng)作,應(yīng)從兩方面入手。一是時(shí)間節(jié)奏方面,如快帶弧圈,上升期擊球;攻打弧圈球,上升后期擊球;拉下旋球,下降期擊球。二是空間位置(或幾何圖形的變化)。如揮拍路線(xiàn)、拍形、用力方法等。在時(shí)間節(jié)奏上,還要特別講究從引拍到向前揮拍擊球這段時(shí)間與來(lái)球的節(jié)奏合拍,這樣才能打出又快又狠的球來(lái)。即在自己發(fā)力的同時(shí),又充分借用了對(duì)方來(lái)球之力。研究與掌握這個(gè)節(jié)奏非常重要。
            動(dòng)作不斷分化的意識(shí)。在技術(shù)訓(xùn)練中,應(yīng)不斷將對(duì)方的來(lái)球總結(jié)分類(lèi),并明確回?fù)裘恳活?lèi)來(lái)球的方法和注意事項(xiàng)。不少運(yùn)動(dòng)員樂(lè)于打順手球,來(lái)球突變,失誤一球,甚為不快。其實(shí),這時(shí)應(yīng)好好想想,此球與順手球有什么不同,長(zhǎng)了、短了?旋轉(zhuǎn)強(qiáng)了?還是節(jié)奏變了?弄清楚此球特點(diǎn),繼而明確打此類(lèi)球的方法。這樣不斷將對(duì)方來(lái)球區(qū)分、歸類(lèi),并明確打每一類(lèi)球的不同方法,就可以使自己的技術(shù)越來(lái)越精細(xì),水平亦越來(lái)越高。
            動(dòng)作定與變的辯證意識(shí)。來(lái)球變了,打球動(dòng)作應(yīng)隨之而變;但打同類(lèi)球的動(dòng)作,則是越固定越好。掌握與提高技術(shù)的整個(gè)過(guò)程是,對(duì)來(lái)球的區(qū)分越來(lái)越細(xì),相應(yīng)打球的動(dòng)作也越分化越細(xì);但打同類(lèi)球的動(dòng)作,又越來(lái)越固定。這“固定”與“變化”的統(tǒng)一,就促進(jìn)了技術(shù)水平的不斷提高。
            戰(zhàn)術(shù)意識(shí)。實(shí)踐中有兩層含義。一是注意研究在比賽中運(yùn)用戰(zhàn)術(shù)的方法。因?yàn)橹挥泻侠淼剡\(yùn)用戰(zhàn)術(shù),才能使技術(shù)充分發(fā)揮。二是在訓(xùn)練中應(yīng)帶著戰(zhàn)術(shù)意識(shí)練技術(shù)。拿最簡(jiǎn)單的右方斜線(xiàn)對(duì)攻作例。有人在練習(xí)右斜對(duì)攻時(shí),能把比賽中的打大角和攻追身的戰(zhàn)術(shù)聯(lián)系起來(lái),有意打大角度或時(shí)而打?qū)Ψ街新芬话濉A硪蝗酥皇且晃兜孛つ抗バ本€(xiàn)。很明顯,前者帶著戰(zhàn)術(shù)意識(shí)練習(xí)技術(shù)的效果要好。
            戰(zhàn)略意識(shí)。在訓(xùn)練中,尤其是在比賽中,要有一個(gè)全局觀念。如一年中有幾次比賽?哪個(gè)比賽最重要?每個(gè)比賽的目的和任務(wù)都是什么?有時(shí)為參加某次有決定意義的大賽,還會(huì)有意放棄一些小的比賽。又如,對(duì)參加一次大賽而言,確立參賽人員和明確重點(diǎn)突破口(是團(tuán)體、單打,還是雙打?),則屬帶全局性的戰(zhàn)略問(wèn)題,必須認(rèn)真對(duì)待。如果這些大題目未解決好,盡管你費(fèi)了很大的氣力,其結(jié)果也難以如愿。
            落點(diǎn)意識(shí)。訓(xùn)練中,特別是在比賽中,要注意擊球的落點(diǎn)。一般情況下,大角度球、追身球、近網(wǎng)小球、底線(xiàn)長(zhǎng)球和似出臺(tái)未出臺(tái)的球的落點(diǎn)較好。但不同對(duì)手,還會(huì)因其打法和個(gè)人掌握技術(shù)的情況,有其特殊點(diǎn)。如左推右攻者,一般最怕反手底線(xiàn)下旋球和調(diào)右壓左的落點(diǎn)變化。比賽中,既要研究自己打球的落點(diǎn),對(duì)方最怕什么落點(diǎn),又要注意總結(jié)對(duì)方回球落點(diǎn)的規(guī)律。
            旋轉(zhuǎn)意識(shí)。充分認(rèn)識(shí)到旋轉(zhuǎn)是乒乓球的重要制勝因素之一。在訓(xùn)練中,要自覺(jué)地提高發(fā)球、搓球、拉球、放高球等技術(shù)的旋轉(zhuǎn)強(qiáng)度和變化。在比賽中,要善于利用旋轉(zhuǎn)變化來(lái)擾亂以至戰(zhàn)勝對(duì)方。
            速度意識(shí)。應(yīng)充分認(rèn)識(shí)到速度是我國(guó)快攻打法的靈魂。中國(guó)選手要戰(zhàn)勝外國(guó)選手,主要靠的仍是速度——提早擊球時(shí)間,重視手腕、手指的力量,能快則快,不能快時(shí),先過(guò)渡一板,爭(zhēng)取機(jī)會(huì)再轉(zhuǎn)入快。
            變化意識(shí)。應(yīng)充分認(rèn)識(shí)到變化乃是乒乓球的重要制勝因素之一,自覺(jué)、主動(dòng)地變化擊球的速度、旋轉(zhuǎn)、力量、落點(diǎn)和弧線(xiàn)。比賽中,雙方都在為形成利我、不利對(duì)方的戰(zhàn)局而變化著戰(zhàn)術(shù),誰(shuí)的觀察能力強(qiáng),能及時(shí)察覺(jué)對(duì)方的戰(zhàn)術(shù)意圖,迅速變換相應(yīng)的戰(zhàn)術(shù),誰(shuí)就容易獲取勝利。
            變化擊球節(jié)奏的意識(shí)。比賽中,不僅應(yīng)主動(dòng)變化落點(diǎn)、旋轉(zhuǎn)等,而且應(yīng)主動(dòng)變化擊球的節(jié)奏。如原來(lái)都是上升或高點(diǎn)期的搶沖,現(xiàn)主動(dòng)將擊球時(shí)間后移,拉一板上旋強(qiáng)烈的加轉(zhuǎn)弧圈球。對(duì)方已熟悉了你原來(lái)快沖的節(jié)奏,突然變成慢一拍的加轉(zhuǎn)弧圈,往往就會(huì)上當(dāng),又如,同一類(lèi)發(fā)球,有節(jié)奏較慢的,有節(jié)奏特快的,若再能保持拍觸球前的動(dòng)作盡量一致,則效果更好,這都是有變化節(jié)奏意識(shí)的表現(xiàn)。
            搶攻意識(shí)。這是積極主動(dòng)的指導(dǎo)思想,力爭(zhēng)搶攻在先(即平常說(shuō)的先上手),能搶則搶、實(shí)在不能搶時(shí),控制一板,爭(zhēng)取下板搶。這種意識(shí)很重要。如有人的側(cè)身攻球技術(shù)很不錯(cuò),但就因缺乏搶攻意識(shí),所以使他的側(cè)身攻球技術(shù)英雄無(wú)用武之地。兵書(shū)講:“兩強(qiáng)相遇,勇者勝。”這“勇”字的一個(gè)重要含義就是先發(fā)制人。在弧圈球風(fēng)靡世界的今天,快攻者只搓一板就攻攻的打法,即是搶攻意識(shí)強(qiáng)的表現(xiàn)。
            搶先發(fā)力的意識(shí)。近年來(lái),世界乒乓球技術(shù)朝著更加積極主動(dòng)的方向發(fā)展,不僅要求搶攻在先,而且應(yīng)該盡量爭(zhēng)取先發(fā)力,以使自己更加主動(dòng)。
            連續(xù)進(jìn)攻的意識(shí)。發(fā)起進(jìn)攻后,應(yīng)連續(xù)進(jìn)攻,乘勝追擊,直至得分。切忌攻一板后,再無(wú)繼續(xù)進(jìn)攻的準(zhǔn)備,將已到手的主動(dòng)又變成了相持、甚至被動(dòng)。
            控、防、反意識(shí)。在不能主動(dòng)進(jìn)攻或?qū)Ψ綋尮ヒ庾R(shí)極強(qiáng)時(shí),應(yīng)注意控制對(duì)方,并做好防守的準(zhǔn)備。在防守或相持中,一旦有機(jī)會(huì),應(yīng)立即轉(zhuǎn)入進(jìn)攻。
            爭(zhēng)取局部?jī)?yōu)勢(shì)的意識(shí)。我的所有技術(shù)都比對(duì)方強(qiáng),這叫絕對(duì)優(yōu)勢(shì)。比賽中自然怎么打都行。但在實(shí)踐中,這種情況比較少見(jiàn)。多數(shù)情況是相對(duì)優(yōu)勢(shì)。這就要求運(yùn)動(dòng)員在比賽中應(yīng)自覺(jué)地爭(zhēng)取局部?jī)?yōu)勢(shì)。能以己之長(zhǎng),打?qū)Ψ街蹋?dāng)然最好。但有時(shí)不一定能實(shí)現(xiàn)此策。如,我發(fā)球的特長(zhǎng)是轉(zhuǎn)與不轉(zhuǎn),而對(duì)方的特短是接高拋發(fā)球。我之特長(zhǎng)對(duì)不上對(duì)方的特短。高拋發(fā)球雖為我之特短,但與對(duì)方接此球相比,我還占便宜。此時(shí)用我之特短打?qū)Ψ教囟叹褪亲詈玫膽?zhàn)術(shù)。因?yàn)槲耀@得了局部?jī)?yōu)勢(shì)。
            記球意識(shí)。比賽時(shí),要有意記雙方戰(zhàn)術(shù)變化的過(guò)程,對(duì)方發(fā)的什么球,我怎么回的,他又怎么打的……從一個(gè)球,到整個(gè)戰(zhàn)局的變化,要自覺(jué)地記。時(shí)間長(zhǎng)了,就會(huì)大大提高自己的戰(zhàn)術(shù)意識(shí)。
            互怕意識(shí)。比賽中“怕”是相互的。你怕他,他也怕你。誰(shuí)能透過(guò)現(xiàn)象看到本質(zhì),誰(shuí)能想得好一點(diǎn),誰(shuí)就容易主動(dòng)。在緊張時(shí),應(yīng)多想對(duì)自己有利的方面,多想對(duì)方是多么怕你,多想戰(zhàn)術(shù),這樣就可以長(zhǎng)自己志氣,滅對(duì)方威風(fēng)。
            戰(zhàn)略上藐視對(duì)手、戰(zhàn)術(shù)上重視對(duì)手的意識(shí)。戰(zhàn)略者,戰(zhàn)爭(zhēng)的全局也;戰(zhàn)術(shù)者,戰(zhàn)爭(zhēng)的局部也。運(yùn)動(dòng)員應(yīng)樹(shù)立在全局或總體上藐視對(duì)手、藐視困難,而在具體的局部上應(yīng)重視對(duì)手、重視困難的指導(dǎo)思想。如對(duì)某某比賽,首先應(yīng)相信自己能戰(zhàn)勝對(duì)方,并認(rèn)真地分析對(duì)手的技術(shù)、戰(zhàn)術(shù)、身體和心理特點(diǎn)。在此基礎(chǔ)上制訂自己的戰(zhàn)術(shù),如發(fā)什么球,怎么搶攻,接發(fā)球注意什么,相持、領(lǐng)先或落后怎么打等等,一步一步、詳詳細(xì)細(xì)。
            樹(shù)立技術(shù)風(fēng)格的意識(shí)。在技、戰(zhàn)術(shù)訓(xùn)練中、應(yīng)特別強(qiáng)調(diào)樹(shù)立正確的技術(shù)風(fēng)格。技術(shù)風(fēng)格,常被喻為運(yùn)動(dòng)員的技術(shù)“靈魂”。培養(yǎng)什么樣的技術(shù)風(fēng)格,將直接關(guān)系到運(yùn)動(dòng)員的發(fā)展方向和可能達(dá)到的水平。無(wú)數(shù)事實(shí)證明,一個(gè)沒(méi)有鮮明技術(shù)風(fēng)格的選手,要攀登世界乒壇的高峰是不可能的!
            全面訓(xùn)練的意識(shí)。運(yùn)動(dòng)員應(yīng)明確決定其競(jìng)技能力的諸因素(形態(tài)、機(jī)能、素質(zhì)、技術(shù)、戰(zhàn)術(shù)、心理和智力等),并自覺(jué)地提高之。這里,應(yīng)反對(duì)狹隘的技術(shù)論。有人以為,要提高乒乓球運(yùn)動(dòng)成績(jī),就應(yīng)該全力抓技術(shù),什么身體素質(zhì)、心理等統(tǒng)統(tǒng)不問(wèn)。結(jié)果,盡管訓(xùn)練時(shí)間練的都是技術(shù),但技術(shù)水平的提高卻難以如愿。運(yùn)動(dòng)員一定要樹(shù)立全面訓(xùn)練的觀點(diǎn)。
            抓主要矛盾的意識(shí)。每一名運(yùn)動(dòng)員在某一時(shí)期必有一個(gè)主要矛盾影響著他整體水平的提高。誰(shuí)善于捕捉之,并設(shè)法解決,誰(shuí)就會(huì)獲得明顯的進(jìn)步。如中國(guó)乒乓隊(duì)在1957年參加第二十三屆世乒賽后,發(fā)現(xiàn)自己的打法因缺乏準(zhǔn)確性而使快速、兇狠的優(yōu)點(diǎn)難以發(fā)揮作用。之后,狠抓了提高擊球準(zhǔn)確性的訓(xùn)練,很快就見(jiàn)到了明顯的效果。不善于抓主要矛盾的人,不是漫無(wú)邊際,什么都抓,就是雖有重點(diǎn),但抓得不準(zhǔn)。其結(jié)果都是一樣——費(fèi)力不討好!
            兇穩(wěn)結(jié)合的意識(shí)。乒乓球的技術(shù)各種各樣,每個(gè)人的打法又各具特點(diǎn),但凡在比賽中有實(shí)用價(jià)值的技術(shù),無(wú)不同時(shí)具備威脅性(兇)和準(zhǔn)確性(穩(wěn))。威脅性,即打出的球給對(duì)方回球造成困難,甚至使對(duì)方失誤。準(zhǔn)確性,即擊球不失誤。二者相互依存,并在一定的條件下可以互相轉(zhuǎn)化。我們?cè)谟?xùn)練或比賽中,一定要注意二者的結(jié)合,決不可以偏蓋全。
            練絕招的意識(shí)。一個(gè)運(yùn)動(dòng)員的技術(shù)一定要有特長(zhǎng),即絕招,否則往往難以給對(duì)方造成威脅,也難以攀登技術(shù)高峰。絕招,可根據(jù)個(gè)人打法的特點(diǎn)、身體素質(zhì)的特點(diǎn)、心理特點(diǎn)和所用球拍的性能等因素有目的地確立。“傷其十指,不如斷其一指。”絕招就是起碼能斷其一指的技術(shù)。
            表現(xiàn)意識(shí)。明確訓(xùn)練是為了比賽。運(yùn)動(dòng)員在平日訓(xùn)練就應(yīng)有一種要在比賽中強(qiáng)烈表現(xiàn)自己的欲望。這如同演員,排練節(jié)目就是為了上臺(tái)表演;不但要上臺(tái),還要演得呱呱叫。平時(shí)這種意識(shí)強(qiáng)烈,演出時(shí)才可能發(fā)揮出色。當(dāng)運(yùn)動(dòng)員亦是同樣道理。
            重視理論的意識(shí)。運(yùn)動(dòng)員應(yīng)充分認(rèn)識(shí)到理論對(duì)實(shí)踐的指導(dǎo)作用,自覺(jué)地學(xué)習(xí)和鉆研乒乓球運(yùn)動(dòng)的理論,并注意理論與實(shí)踐的結(jié)合。
            創(chuàng)新意識(shí),無(wú)論是教練員還是運(yùn)動(dòng)員,都要十分重視創(chuàng)新。乒乓球運(yùn)動(dòng)的創(chuàng)新,應(yīng)包括技術(shù)、戰(zhàn)術(shù)、打法、訓(xùn)練、管理、器材設(shè)備和理論等七個(gè)方面。運(yùn)動(dòng)員主要是前四個(gè)方面。
            超前意識(shí)。教練員和運(yùn)動(dòng)員應(yīng)能預(yù)測(cè)出未來(lái)的技術(shù)發(fā)展趨勢(shì),并以此來(lái)指導(dǎo)訓(xùn)練,使自己的訓(xùn)練能走在現(xiàn)實(shí)技術(shù)的前面。
            定量意識(shí)。運(yùn)動(dòng)員在訓(xùn)練或比賽中,應(yīng)自覺(jué)地注意數(shù)量的變化。如,1500米跑多少秒?其中的每一圈(400米)又跑幾秒?一周到底練多少時(shí)間?個(gè)人的競(jìng)技狀態(tài)周期規(guī)律是怎樣的?個(gè)人晨脈的規(guī)律是怎樣的?某技術(shù)在訓(xùn)練中的命中率是多少,比賽中又是多少?一場(chǎng)、一局比賽的比分起伏是怎樣的?切忌什么都是含含糊糊。
            檔案意識(shí)。教練員和運(yùn)動(dòng)員應(yīng)自覺(jué)地建立業(yè)務(wù)檔案,堅(jiān)持寫(xiě)好訓(xùn)練日記、階段小結(jié)及年終總結(jié)。每年比賽勝負(fù)各多少場(chǎng)?身體素質(zhì)或技術(shù)測(cè)驗(yàn)的具體成績(jī)是多少,都應(yīng)分門(mén)別類(lèi)記錄清楚。
          posted @ 2008-02-28 22:48 hk2000c 閱讀(329) | 評(píng)論 (0)編輯 收藏

          乒乓球基本技術(shù)動(dòng)作口訣

            一、頭訣
            口訣不能羅萬(wàn)象,僅把要點(diǎn)來(lái)提供, 速度旋轉(zhuǎn)多變化,有賴(lài)觸類(lèi)能旁通。
            二、準(zhǔn)備姿勢(shì)
            立足肩寬微提踵,屈膝彎腰莫挺胸, 拍置腹前眼注視,準(zhǔn)備移動(dòng)體放松。
            三、發(fā)球
            伸掌拋球向上空,球落擊法有多種, 上下側(cè)旋擦球面,長(zhǎng)短輕急力不同。
            四、接發(fā)球
            “準(zhǔn)備姿勢(shì)”接發(fā)球,來(lái)球旋轉(zhuǎn)反向送, 上旋推擋下旋搓,長(zhǎng)抽短吊爭(zhēng)搶攻。
            五、正手攻球
            切忌抬肘握拍松,前臂向前向上動(dòng), 左腳稍前體右轉(zhuǎn),傾拍一般擊球中。
            六、反手攻球
            前臂擱腹臂貼胸,肘為軸心臂腕動(dòng), 左腳移后腰左轉(zhuǎn),傾拍斜擊球上中。
            七、推擋球
             推擋多用反手方,動(dòng)作猶似反手攻, 前臂發(fā)力向前下,傾拍推擋球上中。
            八、搓球
            拍先稍仰后平送,向前摩擦球下中, 手腕配合小臂動(dòng),球轉(zhuǎn)不轉(zhuǎn)靠腕動(dòng)。
            九、削球
            判斷來(lái)球先移動(dòng),屈腿轉(zhuǎn)體引拍送, 先仰后平擦球底,向前下作弧形動(dòng)。
            十、弧圈球
            揮拍力向前上沖,薄擦球面位上中, 越薄越轉(zhuǎn)成弧圈,擦面要寬力加重。
            十一、回?fù)艋∪η?
            傾拍蓋住球上中、及時(shí)調(diào)拍心放松, 削擊移拍上而下,短促截球位偏中。
            十二、放高球
            離臺(tái)抖切球下中,高拋物線(xiàn)前上送, 遠(yuǎn)吊對(duì)方左右角,越高、遠(yuǎn)、轉(zhuǎn)越成功。
            十三、殺高球
            球彈高起莫著急,移位待落額上空, 手臂環(huán)轉(zhuǎn)向前壓,亦可斜砍球側(cè)中。  
            十四、滑板球
            拍向右前似擊球,手腕突轉(zhuǎn)向左送, 斜擦球左側(cè)力抽,聲東擊西奏奇功。
            十五、短球
            對(duì)方離臺(tái)宜短吊,貌似長(zhǎng)拍宜輕送, 拍平減力輕遞球,豎板輕擋亦可用。
            十六、步法
            練球切忌丟步法,擊球應(yīng)先步移動(dòng), 先動(dòng)后打是關(guān)鍵,步法混亂手法空

          posted @ 2008-02-28 22:37 hk2000c 閱讀(386) | 評(píng)論 (0)編輯 收藏

          (一)正手發(fā)奔球
            1、 特點(diǎn) 球速急、落點(diǎn)長(zhǎng)、沖力大,發(fā)至對(duì)方右大角或中左位置,對(duì)對(duì)方威脅較大。
            2、 要點(diǎn)①拋球不宜太高;②提高擊球瞬間的揮拍速度;③第一落點(diǎn)要靠近本方臺(tái)面的端線(xiàn);④擊球點(diǎn)與網(wǎng)同高或稍低于網(wǎng)。

            (二)反手發(fā)急球與發(fā)急下旋球
            1、 特點(diǎn) 球速快、弧線(xiàn)低,前沖大,迫使對(duì)方后退接球,有利于搶攻,常與發(fā)急下旋球配合使用。
            2、 要點(diǎn)①擊球點(diǎn)應(yīng)在身體的左前側(cè)與網(wǎng)同高或比網(wǎng)稍低;②注意手腕的抖動(dòng)發(fā)力;③第一落點(diǎn)在本方臺(tái)區(qū)的端線(xiàn)附近。

            (三)發(fā)短球
            1、 特點(diǎn) 擊球動(dòng)作小,出手快,球落到對(duì)方臺(tái)面后的第二跳下不出臺(tái),使對(duì)方不易發(fā)力搶拉、沖或搶攻。
            2、 要點(diǎn) ①拋球不宜太高;②擊球時(shí),手腕的力量大于前臂的力量;③發(fā)球的第一落點(diǎn)在球臺(tái)中區(qū),不要離網(wǎng)太近;④發(fā)球動(dòng)作盡可能與發(fā)長(zhǎng)球相似,使對(duì)方不易判斷。

            (四)正手發(fā)轉(zhuǎn)與不轉(zhuǎn)球
            1、 特點(diǎn) 球速較慢,前沖力小,主要用相似的發(fā)球動(dòng)作,制造旋轉(zhuǎn)變化去迷惑對(duì)方,造成對(duì)方接發(fā)球失誤或?yàn)樽约簱尮?chuàng)造機(jī)會(huì)。
            2、 要點(diǎn)①拋球不宜太高;②發(fā)轉(zhuǎn)球時(shí),拍面稍后抑,切球的中下部;越是加轉(zhuǎn)球,越應(yīng)注意手臂的前送動(dòng)作;③發(fā)不轉(zhuǎn)球時(shí),擊球瞬間減小拍面后仰角度,增加前推的力量。

            (五)正手發(fā)左側(cè)上(下)旋球
            1、 特點(diǎn) 左側(cè)上(下)旋轉(zhuǎn)力較強(qiáng),對(duì)方擋球時(shí)向其右側(cè)上(下)方反彈,一般站在中線(xiàn)偏左或側(cè)身發(fā)球。
            2、 要點(diǎn):①發(fā)球時(shí)要收腹,擊球點(diǎn)不可遠(yuǎn)離身體;②盡量加大由右向左揮動(dòng)的幅度和弧線(xiàn),以增強(qiáng)側(cè)旋強(qiáng)度。③發(fā)左側(cè)上旋時(shí),擊球瞬間手腕快速內(nèi)收,球拍從球的正中向左上方摩擦。④發(fā)左側(cè)下旋時(shí),拍面稍后仰,球拍從球的中下部向左下方摩擦。

            (六) 反手發(fā)右側(cè)上(下)旋球
            1. 特點(diǎn) 右側(cè)上(下)旋球力強(qiáng),對(duì)方擋住后,向其左側(cè)上(下)反彈。發(fā)球落點(diǎn)以左方斜線(xiàn)長(zhǎng)球配合中右近網(wǎng)短球?yàn)榧选?
            2. 要點(diǎn) ①注意收腹和轉(zhuǎn)腰動(dòng)作;②充分利用手腕轉(zhuǎn)動(dòng)配合前臂發(fā)力;③發(fā)右側(cè)上旋球時(shí),擊球瞬間球拍從球的中部向右上方摩擦,手腕有一個(gè)上勾動(dòng)作;④發(fā)右側(cè)下旋球時(shí),拍面稍后仰,擊球瞬間球拍從球的中下部向右側(cè)下摩擦。

            (七)下蹲發(fā)球
            1.特點(diǎn) 下蹲發(fā)球?qū)儆谏鲜诸?lèi)發(fā)球,我國(guó)運(yùn)動(dòng)員早在50年代就開(kāi)始使用。橫拍選手發(fā)下蹲球比直拍選手方便些,直拍選手發(fā)球時(shí)需變化握拍方法,即將食指移放到球拍的背面。下蹲發(fā)球可以發(fā)出左側(cè)旋和右側(cè)旋,在對(duì)方不適應(yīng)的情況下,威脅很大,關(guān)鍵時(shí)候發(fā)出高質(zhì)量的球,往往能直接得分。
            2. 要點(diǎn)①注意拋球和揮拍擊球動(dòng)作的配合,掌握好擊球時(shí)間。②發(fā)球要有質(zhì)量,發(fā)球動(dòng)作要利落,以防在還未完全站起時(shí)已被對(duì)方搶攻③發(fā)下蹲右側(cè)上、下旋球時(shí),左腳稍前,身體略向右偏轉(zhuǎn),揮拍路線(xiàn)為從左后方向右前方。拍觸球中部向右側(cè)上摩擦為右側(cè)上旋;從球中下部向右側(cè)下摩擦為右側(cè)下旋。④發(fā)下蹲左側(cè)上、下旋球時(shí),站位稍平,身體基本正對(duì)球臺(tái),揮拍路線(xiàn)為從右后方向左前方。拍觸球右中部向左上方摩擦為左側(cè)上旋;從球中部向左下部摩擦為左側(cè)下旋。⑤發(fā)左(右)側(cè)上、下旋球時(shí),要特別注意快速做半圓形摩擦球的動(dòng)作。

            (八)正手高拋發(fā)球
            1、 特點(diǎn) 最顯著的特點(diǎn)是拋球高,增大了球下降時(shí)對(duì)拍的正壓力,發(fā)出的球速度快,沖力大,旋轉(zhuǎn)變化多,著臺(tái)后拐彎飛行。但高拋發(fā)球動(dòng)作復(fù)雜,有一定的難度。
            2、 要點(diǎn):①拋球勿離臺(tái)及身體太遠(yuǎn)。②擊球點(diǎn)與網(wǎng)同高或比網(wǎng)稍低,在近腰的中右處(15厘米)為好③盡量加大向內(nèi)擺動(dòng)的幅度和弧線(xiàn)。④發(fā)左側(cè)上、下旋球與低拋發(fā)球同。⑤觸球后,附加一個(gè)向右前方的回收動(dòng)作,可增加對(duì)方的判斷(結(jié)合發(fā)右側(cè)旋球,更有威力)。

          posted @ 2008-02-05 13:13 hk2000c 閱讀(303) | 評(píng)論 (0)編輯 收藏

           接連看了幾場(chǎng)中歐女子乒乓球?qū)官悾瑢?duì)歐洲女隊(duì)鮑羅斯的反手拉球在這次系列比賽中的運(yùn)用效果印象頗深。盡管原來(lái)也見(jiàn)過(guò)她的拉球,感覺(jué)很有一些男子運(yùn)動(dòng)員的味道,但每當(dāng)與中國(guó)人對(duì)抗,由于對(duì)中國(guó)運(yùn)動(dòng)員的節(jié)奏適應(yīng)不好,其力量和速度都難以發(fā)揮,能用上的時(shí)候也很少。

            仔細(xì)觀察鮑羅斯的反手拉球,大致上主要用于以下三種情況:一是反手位的第一板上手,二是正手變反手位時(shí),三是反手位的機(jī)會(huì)比較好時(shí)。而這三種球也恰恰是中國(guó)運(yùn)動(dòng)員,特別是水平不高的運(yùn)動(dòng)員常感到很別扭的球。

            中國(guó)隊(duì)的打法歷來(lái)講究積極主動(dòng),體現(xiàn)在打法上的最明顯特征就是突出正手單面進(jìn)攻的使用率和殺傷力。曾有一段時(shí)間,歐洲人在比較清楚地把握了中國(guó)人的慣性套路后,以接發(fā)球“晃撇”反手底線(xiàn)長(zhǎng)球和“調(diào)右壓左”,從正手突破的戰(zhàn)術(shù),一度使中國(guó)隊(duì)陷入非常被動(dòng)的境地,常常令中國(guó)隊(duì)“撥”不出手來(lái),難以形成有效的正手單面攻,造成失利。所以,無(wú)論是橫板快攻還是弧圈打法,在訓(xùn)練過(guò)程中,掌握一板比較高質(zhì)量的反手拉是非常有必要的,可以為發(fā)揮正手的殺傷力創(chuàng)造更多的機(jī)會(huì)。

            談橫板反手拉球,不能不說(shuō)一說(shuō)它的基本要領(lǐng)。常有業(yè)余愛(ài)好者問(wèn),正手拉球時(shí),一腳在前,一腳在后,那么反手拉球時(shí),是否需要進(jìn)行重心交換呢?可以說(shuō),其基本的道理是差不多的。但反手拉球由于受到身體解剖結(jié)構(gòu)的限制,不能像正手拉球時(shí)過(guò)于側(cè)向的站位,一般要接近于兩腳的平行,只要能夠完成基本的重心交換即可。在拉球引拍時(shí),也不能像正手拉一樣有很大的幅度,適當(dāng)?shù)匕阎怅P(guān)節(jié)抬起來(lái)一點(diǎn),以增加手臂的發(fā)力距離,這是與正手拉本質(zhì)的區(qū)別;在接觸球的一瞬間,重心往上撥,盡可能向前發(fā)力,以使球體現(xiàn)足夠的向前質(zhì)量。

            在練習(xí)和運(yùn)用反手拉球過(guò)程中,要注意,作為具有中國(guó)人特點(diǎn)打法的運(yùn)動(dòng)員,其進(jìn)攻和上手的指導(dǎo)思想都應(yīng)建立在正手的基礎(chǔ)上,再運(yùn)用反手,千萬(wàn)不能形成左來(lái)左打,右來(lái)右打的打法風(fēng)格,有失積極意義,畢竟正手和反手的殺傷力是不同的,這也是中國(guó)隊(duì)在長(zhǎng)期的實(shí)踐中摸索的打法風(fēng)格的成功經(jīng)驗(yàn)之一。但作為輔助技術(shù)手段,鮑羅斯反手拉球的運(yùn)用和所產(chǎn)生的效果是值得我們借鑒和學(xué)習(xí)的。
          posted @ 2008-02-05 13:10 hk2000c 閱讀(1128) | 評(píng)論 (0)編輯 收藏

          這樣可以做到對(duì)所有對(duì)象的安全性控制。
          可以擴(kuò)展到一切的業(yè)務(wù)對(duì)象。
          包括基礎(chǔ)方法,操作。
          甚至安全對(duì)象本身
          有點(diǎn)神學(xué)的味道。

          可能是因?yàn)椴捎闷渌粍?chuàng)造者不知道的機(jī)制,所以被創(chuàng)造者無(wú)法理解和知曉。



          posted @ 2008-01-24 00:04 hk2000c 閱讀(260) | 評(píng)論 (0)編輯 收藏

          您認(rèn)為把 NIO 和 Servlet API 組合在一起是不可能的?請(qǐng)?jiān)俸煤孟胍幌隆T诒疚闹校琂ava 開(kāi)發(fā)人員 Taylor Cowan 向您展示了如何把生產(chǎn)者/消費(fèi)者模型應(yīng)用到消費(fèi)者非阻塞 I/O,從而輕松地讓 Servlet API 全新地兼容 NIO。在這個(gè)過(guò)程中,您將會(huì)看到采用了什么來(lái)創(chuàng)建實(shí)際的基于 Servlet 并實(shí)現(xiàn)了 NIO 的 Web 服務(wù)器;您也將發(fā)現(xiàn)在企業(yè)環(huán)境中,那個(gè)服務(wù)器是如何以標(biāo)準(zhǔn)的 Java I/O 服務(wù)器(Tomcat 5.0)為基礎(chǔ)而創(chuàng)建的。

          NIO 是帶有 JDK 1.4 的 Java 平臺(tái)的最有名(如果不是最出色的)的添加部分之一。下面的許多文章闡述了 NIO 的基本知識(shí)及如何利用非阻塞通道的好處。但它們所遺漏的一件事正是,沒(méi)有充分地展示 NIO 如何可以提高 J2EE Web 層的可伸縮性。對(duì)于企業(yè)開(kāi)發(fā)人員來(lái)說(shuō),這些信息特別密切相關(guān),因?yàn)閷?shí)現(xiàn) NIO 不像把少數(shù)幾個(gè) import 語(yǔ)句改變成一個(gè)新的 I/O 包那樣簡(jiǎn)單。首先,Servlet API 采用阻塞 I/O 語(yǔ)義,因此默認(rèn)情況下,它不能利用非阻塞 I/O。其次,不像 JDK 1.0 中那樣,線(xiàn)程不再是“資源獨(dú)占”(resource hog),因此使用較少的線(xiàn)程不一定表明服務(wù)器可以處理更多的客戶(hù)機(jī)。

          在本文中,為了創(chuàng)建基于 Servlet 并實(shí)現(xiàn)了 NIO 的 Web 服務(wù)器,您將學(xué)習(xí)如何解決 Servlet API 與非阻塞 I/O 的不配合問(wèn)題。我們將會(huì)看到在多元的 Web 服務(wù)器環(huán)境中,這個(gè)服務(wù)器是如何針對(duì)標(biāo)準(zhǔn) I/O 服務(wù)器(Tomcat 5.0)進(jìn)行伸縮的。為符合企業(yè)中生存期的事實(shí),我們將重點(diǎn)放在當(dāng)保持 socket 連接的客戶(hù)機(jī)數(shù)量以指數(shù)級(jí)增長(zhǎng)時(shí),NIO 與標(biāo)準(zhǔn) I/O 相比較的情況如何。

          注意,本文針對(duì)某些 Java 開(kāi)發(fā)人員,他們已經(jīng)熟悉了 Java 平臺(tái)上 I/O 編程的基礎(chǔ)知識(shí)。有關(guān)非阻塞 I/O 的介紹,請(qǐng)參閱 參考資料 部分。

          線(xiàn)程不再昂貴

          大家都知道,線(xiàn)程是比較昂貴的。在 Java 平臺(tái)的早期(JDK 1.0),線(xiàn)程的開(kāi)銷(xiāo)是一個(gè)很大負(fù)擔(dān),因此強(qiáng)制開(kāi)發(fā)人員自定義生成解決方案。一個(gè)常見(jiàn)的解決方案是使用 VM 啟動(dòng)時(shí)創(chuàng)建的線(xiàn)程池,而不是按需創(chuàng)建每個(gè)新線(xiàn)程。盡管最近在 VM 層上提高了線(xiàn)程的性能,但標(biāo)準(zhǔn) I/O 仍然要求分配惟一的線(xiàn)程來(lái)處理每個(gè)新打開(kāi)的 socket。就短期而言,這工作得相當(dāng)不錯(cuò),但當(dāng)線(xiàn)程的數(shù)量增加超過(guò)了 1K,標(biāo)準(zhǔn) I/O 的不足就表現(xiàn)出來(lái)了。由于要在線(xiàn)程間進(jìn)行上下文切換,因此 CPU 簡(jiǎn)直變成了超載。

          由于 JDK 1.4 中引入了 NIO,企業(yè)開(kāi)發(fā)人員最終有了“單線(xiàn)程”模型的一個(gè)內(nèi)置解決方案:多元 I/O 使得固定數(shù)量的線(xiàn)程可以服務(wù)不斷增長(zhǎng)的用戶(hù)數(shù)量。

          多路復(fù)用(Multiplexing)指的是通過(guò)一個(gè)載波來(lái)同時(shí)發(fā)送多個(gè)信號(hào)或流。當(dāng)使用手機(jī)時(shí),日常的多路復(fù)用例子就發(fā)生了。無(wú)線(xiàn)頻率是稀有的資源,因此無(wú)線(xiàn)頻率提供商使用多路復(fù)用技術(shù)通過(guò)一個(gè)頻率發(fā)送多個(gè)呼叫。在一個(gè)例子中,把呼叫分成一些段,然后給這些段很短的持續(xù)時(shí)間,并在接收端重新裝配。這就叫做 時(shí)分多路復(fù)用(time-division multiplexing),即 TDM。

          在 NIO 中,接收端相當(dāng)于“選擇器”(參閱 java.nio.channels.Selector )。不是處理呼叫,選擇器是處理多個(gè)打開(kāi)的 socket。就像在 TDM 中那樣,選擇器重新裝配從多個(gè)客戶(hù)機(jī)寫(xiě)入的數(shù)據(jù)段。這使得服務(wù)器可以用單個(gè)線(xiàn)程管理多個(gè)客戶(hù)機(jī)。





          回頁(yè)首


          Servlet API 和 NIO

          對(duì)于 NIO,非阻塞讀寫(xiě)是必要的,但它們并不是完全沒(méi)有麻煩。除了不會(huì)阻塞之外,非阻塞讀不能給呼叫方任何保證。客戶(hù)機(jī)或服務(wù)器應(yīng)用程序可能讀取完整信息、部分消息或者根本讀取不到消息。另外,非阻塞讀可能讀取到太多的消息,從而強(qiáng)制為下一個(gè)呼叫準(zhǔn)備一個(gè)額外的緩沖區(qū)。最后,不像流那樣,讀取了零字節(jié)并不表明已經(jīng)完全接收了消息。

          這些因素使得沒(méi)有輪詢(xún)就不可能實(shí)現(xiàn)甚至是簡(jiǎn)單的 readline 方法。所有的 servlet 容器必須在它們的輸入流上提供 readline 方法。因此,許多開(kāi)發(fā)人員放棄了創(chuàng)建基于 Servlet 并實(shí)現(xiàn)了 NIO 的 Web 應(yīng)用程序服務(wù)器。不過(guò)這里有一個(gè)解決方案,它組合了 Servlet API 和 NIO 的多元 I/O 的能力。

          在下面的幾節(jié)中,您將學(xué)習(xí)如何使用 java.io.PipedInputPipedOutputStream 類(lèi)來(lái)把生產(chǎn)者/消費(fèi)者模型應(yīng)用到消費(fèi)者非阻塞 I/O。當(dāng)讀取非阻塞通道時(shí),把它寫(xiě)到正由第二個(gè)線(xiàn)程消費(fèi)的管道。注意,這種分解映射線(xiàn)程不同于大多數(shù)基于 Java 的客戶(hù)機(jī)/服務(wù)器應(yīng)用程序。這里,我們讓一個(gè)線(xiàn)程單獨(dú)負(fù)責(zé)處理非阻塞通道(生產(chǎn)者),讓另一個(gè)線(xiàn)程單獨(dú)負(fù)責(zé)把數(shù)據(jù)作為流消費(fèi)(消費(fèi)者)。管道也為應(yīng)用程序服務(wù)器解決了非阻塞 I/O 問(wèn)題,因?yàn)?servlet 在消費(fèi) I/O 時(shí)將采用阻塞語(yǔ)義。





          回頁(yè)首


          示例服務(wù)器

          示例服務(wù)器展示了 Servlet API 和 NIO 不兼容的生產(chǎn)者/消費(fèi)者解決方案。該服務(wù)器與 Servlet API 非常相似,可以為成熟的基于 NIO 應(yīng)用程序服務(wù)器提供 POC (proof of concept),是專(zhuān)門(mén)編寫(xiě)來(lái)衡量 NIO 相對(duì)于標(biāo)準(zhǔn) Java I/O 的性能的。它處理簡(jiǎn)單的 HTTP get 請(qǐng)求,并支持來(lái)自客戶(hù)機(jī)的 Keep-Alive 連接。這是重要的,因?yàn)槎嗦窂?fù)用 I/O 只證明在要求服務(wù)器處理大量打開(kāi)的 scoket 連接時(shí)是有意的。

          該服務(wù)器被分成兩個(gè)包: org.sse.serverorg.sse.http 包中有提供主要 服務(wù)器 功能的類(lèi),比如如下的一些功能:接收新客戶(hù)機(jī)連接、閱讀消息和生成工作線(xiàn)程以處理請(qǐng)求。 http 包支持 HTTP 協(xié)議的一個(gè)子集。詳細(xì)闡述 HTTP 超出了本文的范圍。有關(guān)實(shí)現(xiàn)細(xì)節(jié),請(qǐng)從 參考資料 部分下載代碼示例。

          現(xiàn)在讓我們來(lái)看一下 org.sse.server 包中一些最重要的類(lèi)。





          回頁(yè)首


          Server 類(lèi)

          Server 類(lèi)擁有多路復(fù)用循環(huán) —— 任何基于 NIO 服務(wù)器的核心。在清單 1 中,在服務(wù)器接收新客戶(hù)機(jī)或檢測(cè)到正把可用的字節(jié)寫(xiě)到打開(kāi)的 socket 前, select() 的調(diào)用阻塞了。這與標(biāo)準(zhǔn) Java I/O 的主要區(qū)別是,所有的數(shù)據(jù)都是在這個(gè)循環(huán)中讀取的。通常會(huì)把從特定 socket 中讀取字節(jié)的任務(wù)分配給一個(gè)新線(xiàn)程。使用 NIO 選擇器事件驅(qū)動(dòng)方法,實(shí)際上可以用單個(gè)線(xiàn)程處理成千上萬(wàn)的客戶(hù)機(jī),不過(guò),我們還會(huì)在后面看到線(xiàn)程仍有一個(gè)角色要扮演。

          每個(gè) select() 調(diào)用返回一組事件,指出新客戶(hù)機(jī)可用;新數(shù)據(jù)準(zhǔn)備就緒,可以讀取;或者客戶(hù)機(jī)準(zhǔn)備就緒,可以接收響應(yīng)。server 的 handleKey() 方法只對(duì)新客戶(hù)機(jī)( key.isAcceptable() )和傳入數(shù)據(jù) ( key.isReadable() ) 感興趣。到這里,工作就結(jié)束了,轉(zhuǎn)入 ServerEventHandler 類(lèi)。


          清單 1. Server.java 選擇器循環(huán)
          public void listen() {
                      SelectionKey key = null;
                      try {
                      while (true) {
                      selector.select();
                      Iterator it = selector.selectedKeys().iterator();
                      while (it.hasNext()) {
                      key = (SelectionKey) it.next();
                      handleKey(key);
                      it.remove();
                      }
                      }
                      } catch (IOException e) {
                      key.cancel();
                      } catch (NullPointerException e) {
                      // NullPointer at sun.nio.ch.WindowsSelectorImpl, Bug: 4729342
                      e.printStackTrace();
                      }
                      }
                      





          回頁(yè)首


          ServerEventHandler 類(lèi)

          ServerEventHandler 類(lèi)響應(yīng)服務(wù)器事件。當(dāng)新客戶(hù)機(jī)變?yōu)榭捎脮r(shí),它就實(shí)例化一個(gè)新的 Client 對(duì)象,該對(duì)象代表了那個(gè)客戶(hù)機(jī)的狀態(tài)。數(shù)據(jù)是以非阻塞方式從通道中讀取的,并被寫(xiě)到 Client 對(duì)象中。 ServerEventHandler 對(duì)象也維護(hù)請(qǐng)求隊(duì)列。為了處理(消費(fèi))隊(duì)列中的請(qǐng)求,生成了不定數(shù)量的工作線(xiàn)程。在傳統(tǒng)的生產(chǎn)者/消費(fèi)者方式下,為了在隊(duì)列變?yōu)榭諘r(shí)線(xiàn)程會(huì)阻塞,并在新請(qǐng)求可用時(shí)線(xiàn)程會(huì)得到通知,需要寫(xiě) Queue

          為了支持等待的線(xiàn)程,在清單 2 中已經(jīng)重寫(xiě)了 remove() 方法。如果列表為空,就會(huì)增加等待線(xiàn)程的數(shù)量,并阻塞當(dāng)前線(xiàn)程。它實(shí)質(zhì)上提供了非常簡(jiǎn)單的線(xiàn)程池。


          清單 2. Queue.java
          public class Queue extends LinkedList
                      {
                      private int waitingThreads = 0;
                      public synchronized void insert(Object obj)
                      {
                      addLast(obj);
                      notify();
                      }
                      public synchronized Object remove()
                      {
                      if ( isEmpty() ) {
                      try	{ waitingThreads++; wait();}
                      catch (InterruptedException e)  {Thread.interrupted();}
                      waitingThreads--;
                      }
                      return removeFirst();
                      }
                      public boolean isEmpty() {
                      return 	(size() - waitingThreads <= 0);
                      }
                      }
                      

          工作線(xiàn)程的數(shù)量與 Web 客戶(hù)機(jī)的數(shù)量無(wú)關(guān)。不是為每個(gè)打開(kāi)的 socket 分配一個(gè)線(xiàn)程,相反,我們把所有請(qǐng)求放到一個(gè)由一組 RequestHandlerThread 實(shí)例所服務(wù)的通用隊(duì)列中。理想情況下,線(xiàn)程的數(shù)量應(yīng)該根據(jù)處理器的數(shù)量和請(qǐng)求的長(zhǎng)度或持續(xù)時(shí)間進(jìn)行調(diào)整。如果請(qǐng)求通過(guò)資源或處理需求花了很長(zhǎng)時(shí)間,那么通過(guò)添加更多的線(xiàn)程,可以提高感知到的服務(wù)質(zhì)量。

          注意,這不一定提高整體的吞吐量,但確實(shí)改善了用戶(hù)體驗(yàn)。即使在超載的情況下,也會(huì)給每個(gè)線(xiàn)程一個(gè)處理時(shí)間片。這一原則同樣適用于基于標(biāo)準(zhǔn) Java I/O 的服務(wù)器;不過(guò)這些服務(wù)器是受到限制的,因?yàn)闀?huì) 要求 它們?yōu)槊總€(gè)打開(kāi)的 socket 連接分配一個(gè)線(xiàn)程。NIO 服務(wù)器完全不用擔(dān)心這一點(diǎn),因此它們可以擴(kuò)展到大量用戶(hù)。最后的結(jié)果是 NIO 服務(wù)器仍然需要線(xiàn)程,只是不需要那么多。





          回頁(yè)首


          請(qǐng)求處理

          Client 類(lèi)有兩個(gè)用途。首先,通過(guò)把傳入的非阻塞 I/O 轉(zhuǎn)換成可由 Servlet API 消費(fèi)的阻塞 InputStream ,它解決了阻塞/非阻塞問(wèn)題。其次,它管理特定客戶(hù)機(jī)的請(qǐng)求狀態(tài)。因?yàn)楫?dāng)全部讀取消息時(shí),非阻塞通道沒(méi)有給出任何提示,所以強(qiáng)制我們?cè)趨f(xié)議層處理這一情況。 Client 類(lèi)在任意指定的時(shí)刻都指出了它是否正在參與進(jìn)行中的請(qǐng)求。如果它準(zhǔn)備處理新請(qǐng)求, write() 方法就會(huì)為請(qǐng)求處理而將該客戶(hù)機(jī)排到隊(duì)列中。如果它已經(jīng)參與了請(qǐng)求,它就只是使用 PipedInputStreamPipedOutputStream 類(lèi)把傳入的字節(jié)轉(zhuǎn)換成一個(gè) InputStream

          圖 1 展示了兩個(gè)線(xiàn)程圍繞管道進(jìn)行交互。主線(xiàn)程把從通道讀取的數(shù)據(jù)寫(xiě)到管道中。管道把相同的數(shù)據(jù)作為 InputStream 提供給消費(fèi)者。管道的另一個(gè)重要特性是:它是進(jìn)行緩沖處理的。如果沒(méi)有進(jìn)行緩沖處理,主線(xiàn)程在嘗試寫(xiě)到管道時(shí)就會(huì)阻塞。因?yàn)橹骶€(xiàn)程單獨(dú)負(fù)責(zé)所有客戶(hù)機(jī)間的多路復(fù)用,因此我們不能讓它阻塞。


          圖 1. PipedInput/OutputStream
          關(guān)系的圖形表示

          Client 自己排隊(duì)后,工作線(xiàn)程就可以消費(fèi)它了。 RequestHandlerThread 類(lèi)承擔(dān)了這個(gè)角色。至此,我們已經(jīng)看到主線(xiàn)程是如何連續(xù)地循環(huán)的,它要么接受新客戶(hù)機(jī),要么讀取新的 I/O。工作線(xiàn)程循環(huán)等待新請(qǐng)求。當(dāng)客戶(hù)機(jī)在請(qǐng)求隊(duì)列上變?yōu)榭捎脮r(shí),它就馬上被 remove() 方法中阻塞的第一個(gè)等待線(xiàn)程所消費(fèi)。


          清單 3. RequestHandlerThread.java
          public void run() {
                      while (true) {
                      Client client = (Client) myQueue.remove();
                      try {
                      for (; ; ) {
                      HttpRequest req = new HttpRequest(client.clientInputStream,
                      myServletContext);
                      HttpResponse res = new HttpResponse(client.key);
                      defaultServlet.service(req, res);
                      if (client.notifyRequestDone())
                      break;
                      }
                      } catch (Exception e) {
                      client.key.cancel();
                      client.key.selector().wakeup();
                      }
                      }
                      }
                      

          然后該線(xiàn)程創(chuàng)建新的 HttpRequestHttpResponse 實(shí)例,并調(diào)用 defaultServlet 的 service 方法。注意, HttpRequest 是用 Client 對(duì)象的 clientInputStream 屬性構(gòu)造的。 PipedInputStream 就是負(fù)責(zé)把非阻塞 I/O 轉(zhuǎn)換成阻塞流。

          從現(xiàn)在開(kāi)始,請(qǐng)求處理就與您在 J2EE Servlet API 中期望的相似。當(dāng)對(duì) servlet 的調(diào)用返回時(shí),工作線(xiàn)程在返回到池中之前,會(huì)檢查是否有來(lái)自相同客戶(hù)機(jī)的另一個(gè)請(qǐng)求可用。注意,這里用到了單詞 池 (pool)。事實(shí)上,線(xiàn)程會(huì)對(duì)隊(duì)列嘗試另一個(gè) remove() 調(diào)用,并變成阻塞,直到下一個(gè)請(qǐng)求可用。





          回頁(yè)首


          運(yùn)行示例

          示例服務(wù)器實(shí)現(xiàn)了 HTTP 1.1 協(xié)議的一個(gè)子集。它處理普通的 HTTP get 請(qǐng)求。它帶有兩個(gè)命令行參數(shù)。第一個(gè)指定端口號(hào),第二個(gè)指定 HTML 文件所駐留的目錄。在解壓文件后, 切換到項(xiàng)目目錄,然后執(zhí)行下面的命令,注意要把下面的 webroot 目錄替換為您自己的目錄:

          java -cp bin org.sse.server.Start 8080
                      "C:\mywebroot"
                      

          還請(qǐng)注意,服務(wù)器并沒(méi)有實(shí)現(xiàn)目錄清單,因此必須指定有效的 URL 來(lái)指向您的 webroot 目錄下的文件。





          回頁(yè)首


          性能結(jié)果

          示例 NIO 服務(wù)器是在重負(fù)載下與 Tomcat 5.0 進(jìn)行比較的。選擇 Tomcat 是因?yàn)樗腔跇?biāo)準(zhǔn) Java I/O 的純 Java 解決方案。為了提高可伸縮性,一些高級(jí)的應(yīng)用程序服務(wù)器是用 JNI 本機(jī)代碼優(yōu)化的,因此它們沒(méi)有提供標(biāo)準(zhǔn) I/O 和 NIO 之間的很好比較。目標(biāo)是要確定 NIO 是否給出了大量的性能優(yōu)勢(shì),以及是在什么條件下給出的。

          如下是一些說(shuō)明:

          • Tomcat 是用最大的線(xiàn)程數(shù)量 2000 來(lái)配置的,而示例服務(wù)器只允許用 4 個(gè)工作線(xiàn)程運(yùn)行。
          • 每個(gè)服務(wù)器是針對(duì)相同的一組簡(jiǎn)單 HTTP get 測(cè)試的,這些 HTTP get 基本上由文本內(nèi)容組成。

          • 把加載工具(Microsoft Web Application Stress Tool)設(shè)置為使用“Keep-Alive”會(huì)話(huà),導(dǎo)致了大約要為每個(gè)用戶(hù)分配一個(gè) socket。然后它導(dǎo)致了在 Tomcat 上為每個(gè)用戶(hù)分配一個(gè)線(xiàn)程,而 NIO 服務(wù)器用固定數(shù)量的線(xiàn)程來(lái)處理相同的負(fù)載。

          圖 2 展示了在不斷增加負(fù)載下的“請(qǐng)求/秒”率。在 200 個(gè)用戶(hù)時(shí),性能是相似的。但當(dāng)用戶(hù)數(shù)量超過(guò) 600 時(shí),Tomcat 的性能開(kāi)始急劇下降。這最有可能是由于在這么多的線(xiàn)程間切換上下文的開(kāi)銷(xiāo)而導(dǎo)致的。相反,基于 NIO 的服務(wù)器的性能則以線(xiàn)性方式下降。記住,Tomcat 必須為每個(gè)用戶(hù)分配一個(gè)線(xiàn)程,而 NIO 服務(wù)器只配置有 4 個(gè)工作線(xiàn)程。


          圖 2. 請(qǐng)求/秒
          關(guān)系的圖形表示

          圖 3 進(jìn)一步顯示了 NIO 的性能。它展示了操作的 Socket 連接錯(cuò)誤數(shù)/分鐘。同樣,在大約 600 個(gè)用戶(hù)時(shí),Tomcat 的性能急劇下降,而基于 NIO 的服務(wù)器的錯(cuò)誤率保持相對(duì)較低。


          圖 3. Socket 連接錯(cuò)誤數(shù)/分鐘
          關(guān)系的圖形表式




          回頁(yè)首



          結(jié)束語(yǔ)

          在本文中您已經(jīng)學(xué)習(xí)了,實(shí)際上可以使用 NIO 編寫(xiě)基于 Servlet 的 Web 服務(wù)器,甚至可以啟用它的非阻塞特性。對(duì)于企業(yè)開(kāi)發(fā)人員來(lái)說(shuō),這是好消息,因?yàn)樵谄髽I(yè)環(huán)境中,NIO 比標(biāo)準(zhǔn) Java I/O 更能夠進(jìn)行伸縮。不像標(biāo)準(zhǔn)的 Java I/O,NIO 可以用固定數(shù)量的線(xiàn)程處理許多客戶(hù)機(jī)。當(dāng)基于 Servlet 的 NIO Web 服務(wù)器用來(lái)處理保持和擁有 socket 連接的客戶(hù)機(jī)時(shí),會(huì)獲得更好的性能。



          參考資料



          關(guān)于作者

           

          Taylor Cowan 是一位軟件工程師,也是一位專(zhuān)攻 J2EE 的自由撰稿人。他從 North Texas 大學(xué)的計(jì)算機(jī)科學(xué)專(zhuān)業(yè)獲得了碩士學(xué)位,另外,他還從 Jazz Arranging 獲得了音樂(lè)學(xué)士學(xué)位。

          posted @ 2008-01-03 22:58 hk2000c 閱讀(433) | 評(píng)論 (0)編輯 收藏


              一種是繼承自Thread類(lèi).Thread 類(lèi)是一個(gè)具體的類(lèi),即不是抽象類(lèi),該類(lèi)封裝了線(xiàn)程的行為。要?jiǎng)?chuàng)建一個(gè)線(xiàn)程,程序員必須創(chuàng)建一個(gè)從 Thread 類(lèi)導(dǎo)出的新類(lèi)。程序員通過(guò)覆蓋 Thread 的 run() 函數(shù)來(lái)完成有用的工作用戶(hù)并不直接調(diào)用此函數(shù);而是通過(guò)調(diào)用 Thread 的 start() 函數(shù),該函數(shù)再調(diào)用 run()。
             
              例如:

           

              public class Test extends Thread{
                public Test(){
                }
                public static void main(String args[]){
                  Test t1 = new Test();
                  Test t2 = new Test();
                  t1.start();
                  t2.start();
                }
                public void run(){
                  //do thread's things
                }
              }

           



              
              另一種是實(shí)現(xiàn)Runnable接口,此接口只有一個(gè)函數(shù),run(),此函數(shù)必須由實(shí)現(xiàn)了此接口的類(lèi)實(shí)現(xiàn)。
             
              例如:

           

              public class Test implements Runnable{
                Thread thread1;
                Thread thread2;
                public Test(){
                  thread1 = new Thread(this,"1");
                  thread2 = new Thread(this,"2");
                }
                public static void main(String args[]){
                  Test t = new Test();
                  t.startThreads();
                }
                public void run(){
                  //do thread's things
                }
                public void startThreads(){
                  thread1.start();
                  thread2.start();
                }
              }

              兩種創(chuàng)建方式看起來(lái)差別不大,但是弄不清楚的話(huà),也許會(huì)將你的程序弄得一團(tuán)糟。兩者區(qū)別有以下幾點(diǎn):

          1.當(dāng)你想繼承某一其它類(lèi)時(shí),你只能用后一種方式.

          2.第一種因?yàn)槔^承自Thread,只創(chuàng)建了自身對(duì)象,但是在數(shù)量上,需要幾個(gè)線(xiàn)程,就得創(chuàng)建幾個(gè)自身對(duì)象;第二種只創(chuàng)建一個(gè)自身對(duì)象,卻創(chuàng)建幾個(gè)Thread對(duì)象.而兩種方法重大的區(qū)別就在于此,請(qǐng)你考慮:如果你在第一種里創(chuàng)建數(shù)個(gè)自身對(duì)象并且start()后,你會(huì)發(fā)現(xiàn)好像synchronized不起作用了,已經(jīng)加鎖的代碼塊或者方法居然同時(shí)可以有幾個(gè)線(xiàn)程進(jìn)去,而且同樣一個(gè)變量,居然可以有好幾個(gè)線(xiàn)程同時(shí)可以去更改它。(例如下面的代碼)這是因?yàn)椋谶@個(gè)程序中,雖然你起了數(shù)個(gè)線(xiàn)程,可是你也創(chuàng)建了數(shù)個(gè)對(duì)象,而且,每個(gè)線(xiàn)程對(duì)應(yīng)了每個(gè)對(duì)象也就是說(shuō),每個(gè)線(xiàn)程更改和占有的對(duì)象都不一樣,所以就出現(xiàn)了同時(shí)有幾個(gè)線(xiàn)程進(jìn)入一個(gè)方法的現(xiàn)象,其實(shí),那也不是一個(gè)方法,而是不同對(duì)象的相同的方法。所以,這時(shí)候你要加鎖的話(huà),只能將方法或者變量聲明為靜態(tài),將static加上后,你就會(huì)發(fā)現(xiàn),線(xiàn)程又能管住方法了,同時(shí)不可能有兩個(gè)線(xiàn)程進(jìn)入同樣一個(gè)方法,那是因?yàn)椋F(xiàn)在不是每個(gè)對(duì)象都擁有一個(gè)方法了,而是所有的對(duì)象共同擁有一個(gè)方法,這個(gè)方法就是靜態(tài)方法。

              而你如果用第二種方法使用線(xiàn)程的話(huà),就不會(huì)有上述的情況,因?yàn)榇藭r(shí),你只創(chuàng)建了一個(gè)自身對(duì)象,所以,自身對(duì)象的屬性和方法對(duì)于線(xiàn)程來(lái)說(shuō)是共有的。

              因此,我建議,最好用后一種方法來(lái)使用線(xiàn)程。

          public class mainThread extends Thread{
            int i=0;
            public static void main(String args[]){
              mainThread m1 = new mainThread();
              mainThread m2 = new mainThread();
              mainThread m3 = new mainThread();
              mainThread m4 = new mainThread();
              mainThread m5 = new mainThread();
              mainThread m6 = new mainThread();
              m1.start();
              m2.start();
              m3.start();
              m4.start();
              m5.start();
              m6.start();
            }
            public synchronized void t1(){
              i=++i;
              try{
                Thread.sleep(500);
              }
              catch(Exception e){}
              //每個(gè)線(xiàn)程都進(jìn)入各自的t1()方法,分別打印各自的i
              System.out.println(Thread.currentThread().getName()+" "+i);
            }
            public void run(){
              synchronized(this){
                while (true) {
                  t1();
                }
              }
            }
          }

           

           


           

           

              下面我們來(lái)講synchronized的4種用法吧:

              1.方法聲明時(shí)使用,放在范圍操作符(public等)之后,返回類(lèi)型聲明(void等)之前.即一次只能有一個(gè)線(xiàn)程進(jìn)入該方法,其他線(xiàn)程要想在此時(shí)調(diào)用該方法,只能排隊(duì)等候,當(dāng)前線(xiàn)程(就是在synchronized方法內(nèi)部的線(xiàn)程)執(zhí)行完該方法后,別的線(xiàn)程才能進(jìn)入.
           
                例如:

                public synchronized void synMethod() {
                  //方法體
                }

              2.對(duì)某一代碼塊使用,synchronized后跟括號(hào),括號(hào)里是變量,這樣,一次只有一個(gè)線(xiàn)程進(jìn)入該代碼塊.例如:

                public int synMethod(int a1){
                  synchronized(a1) {
                    //一次只能有一個(gè)線(xiàn)程進(jìn)入
                  }
                }
              3.synchronized后面括號(hào)里是一對(duì)象,此時(shí),線(xiàn)程獲得的是對(duì)象鎖.例如:

          public class MyThread implements Runnable {
            public static void main(String args[]) {
              MyThread mt = new MyThread();
              Thread t1 = new Thread(mt, "t1");
              Thread t2 = new Thread(mt, "t2");
              Thread t3 = new Thread(mt, "t3");
              Thread t4 = new Thread(mt, "t4");
              Thread t5 = new Thread(mt, "t5");
              Thread t6 = new Thread(mt, "t6");
              t1.start();
              t2.start();
              t3.start();
              t4.start();
              t5.start();
              t6.start();
            }

            public void run() {
              synchronized (this) {
                System.out.println(Thread.currentThread().getName());
              }
            }
          }


           
              對(duì)于3,如果線(xiàn)程進(jìn)入,則得到對(duì)象鎖,那么別的線(xiàn)程在該類(lèi)所有對(duì)象上的任何操作都不能進(jìn)行.在對(duì)象級(jí)使用鎖通常是一種比較粗糙的方法。為什么要將整個(gè)對(duì)象都上鎖,而不允許其他線(xiàn)程短暫地使用對(duì)象中其他同步方法來(lái)訪(fǎng)問(wèn)共享資源?如果一個(gè)對(duì)象擁有多個(gè)資源,就不需要只為了讓一個(gè)線(xiàn)程使用其中一部分資源,就將所有線(xiàn)程都鎖在外面。由于每個(gè)對(duì)象都有鎖,可以如下所示使用虛擬對(duì)象來(lái)上鎖:

          class FineGrainLock {

             MyMemberClass x, y;
             Object xlock = new Object(), ylock = new Object();

             public void foo() {
                synchronized(xlock) {
                   //access x here
                }

                //do something here - but don't use shared resources

                synchronized(ylock) {
                   //access y here
                }
             }

             public void bar() {
                synchronized(this) {
                   //access both x and y here
                }
                //do something here - but don't use shared resources
             }
          }

           

              4.synchronized后面括號(hào)里是類(lèi).例如:

          class ArrayWithLockOrder{
            private static long num_locks = 0;
            private long lock_order;
            private int[] arr;

            public ArrayWithLockOrder(int[] a)
            {
              arr = a;
              synchronized(ArrayWithLockOrder.class) {//-----------------------------------------這里
                num_locks++;             // 鎖數(shù)加 1。
                lock_order = num_locks;  // 為此對(duì)象實(shí)例設(shè)置唯一的 lock_order。
              }
            }
            public long lockOrder()
            {
              return lock_order;
            }
            public int[] array()
            {
              return arr;
            }
          }

          class SomeClass implements Runnable
          {
            public int sumArrays(ArrayWithLockOrder a1,
                                 ArrayWithLockOrder a2)
            {
              int value = 0;
              ArrayWithLockOrder first = a1;       // 保留數(shù)組引用的一個(gè)
              ArrayWithLockOrder last = a2;        // 本地副本。
              int size = a1.array().length;
              if (size == a2.array().length)
              {
                if (a1.lockOrder() > a2.lockOrder())  // 確定并設(shè)置對(duì)象的鎖定
                {                                     // 順序。
                  first = a2;
                  last = a1;
                }
                synchronized(first) {              // 按正確的順序鎖定對(duì)象。
                  synchronized(last) {
                    int[] arr1 = a1.array();
                    int[] arr2 = a2.array();
                    for (int i=0; i<size; i++)
                      value += arr1[i] + arr2[i];
                  }
                }
              }
              return value;
            }
            public void run() {
              //...
            }
          }

           

              對(duì)于4,如果線(xiàn)程進(jìn)入,則線(xiàn)程在該類(lèi)中所有操作不能進(jìn)行,包括靜態(tài)變量和靜態(tài)方法,實(shí)際上,對(duì)于含有靜態(tài)方法和靜態(tài)變量的代碼塊的同步,我們通常用4來(lái)加鎖.

          以上4種之間的關(guān)系:

              鎖是和對(duì)象相關(guān)聯(lián)的,每個(gè)對(duì)象有一把鎖,為了執(zhí)行synchronized語(yǔ)句,線(xiàn)程必須能夠獲得synchronized語(yǔ)句中表達(dá)式指定的對(duì)象的鎖,一個(gè)對(duì)象只有一把鎖,被一個(gè)線(xiàn)程獲得之后它就不再擁有這把鎖,線(xiàn)程在執(zhí)行完synchronized語(yǔ)句后,將獲得鎖交還給對(duì)象。
              在方法前面加上synchronized修飾符即可以將一個(gè)方法聲明為同步化方法。同步化方法在執(zhí)行之前獲得一個(gè)鎖。如果這是一個(gè)類(lèi)方法,那么獲得的鎖是和聲明方法的類(lèi)相關(guān)的Class類(lèi)對(duì)象的鎖。如果這是一個(gè)實(shí)例方法,那么此鎖是this對(duì)象的鎖。

           


           

            下面談一談一些常用的方法:

            wait(),wait(long),notify(),notifyAll()等方法是當(dāng)前類(lèi)的實(shí)例方法,
             
                  wait()是使持有對(duì)象鎖的線(xiàn)程釋放鎖;
                  wait(long)是使持有對(duì)象鎖的線(xiàn)程釋放鎖時(shí)間為long(毫秒)后,再次獲得鎖,wait()和wait(0)等價(jià);
                  notify()是喚醒一個(gè)正在等待該對(duì)象鎖的線(xiàn)程,如果等待的線(xiàn)程不止一個(gè),那么被喚醒的線(xiàn)程由jvm確定;
                  notifyAll是喚醒所有正在等待該對(duì)象鎖的線(xiàn)程.
                  在這里我也重申一下,我們應(yīng)該優(yōu)先使用notifyAll()方法,因?yàn)閱拘阉芯€(xiàn)程比喚醒一個(gè)線(xiàn)程更容易讓jvm找到最適合被喚醒的線(xiàn)程.

              對(duì)于上述方法,只有在當(dāng)前線(xiàn)程中才能使用,否則報(bào)運(yùn)行時(shí)錯(cuò)誤java.lang.IllegalMonitorStateException: current thread not owner.

           


           

              下面,我談一下synchronized和wait()、notify()等的關(guān)系:

          1.有synchronized的地方不一定有wait,notify

          2.有wait,notify的地方必有synchronized.這是因?yàn)閣ait和notify不是屬于線(xiàn)程類(lèi),而是每一個(gè)對(duì)象都具有的方法,而且,這兩個(gè)方法都和對(duì)象鎖有關(guān),有鎖的地方,必有synchronized。

          另外,請(qǐng)注意一點(diǎn):如果要把notify和wait方法放在一起用的話(huà),必須先調(diào)用notify后調(diào)用wait,因?yàn)槿绻{(diào)用完wait,該線(xiàn)程就已經(jīng)不是current thread了。如下例:

          /**
           * Title:        Jdeveloper's Java Projdect
           * Description:  n/a
           * Copyright:    Copyright (c) 2001
           * Company:      soho  http://www.ChinaJavaWorld.com
           * @author jdeveloper@21cn.com
           * @version 1.0
           */
          import java.lang.Runnable;
          import java.lang.Thread;

          public class DemoThread
              implements Runnable {

            public DemoThread() {
              TestThread testthread1 = new TestThread(this, "1");
              TestThread testthread2 = new TestThread(this, "2");

              testthread2.start();
              testthread1.start();

            }

            public static void main(String[] args) {
              DemoThread demoThread1 = new DemoThread();

            }

            public void run() {

              TestThread t = (TestThread) Thread.currentThread();
              try {
                if (!t.getName().equalsIgnoreCase("1")) {
                  synchronized (this) {
                    wait();
                  }
                }
                while (true) {

                  System.out.println("@time in thread" + t.getName() + "=" +
                                     t.increaseTime());

                  if (t.getTime() % 10 == 0) {
                    synchronized (this) {
                      System.out.println("****************************************");
                      notify();
                      if (t.getTime() == 100)
                        break;
                      wait();
                    }
                  }
                }
              }
              catch (Exception e) {
                e.printStackTrace();
              }
            }

          }

          class TestThread
              extends Thread {
            private int time = 0;
            public TestThread(Runnable r, String name) {
              super(r, name);
            }

            public int getTime() {
              return time;
            }

            public int increaseTime() {
              return++time;
            }

          }

              下面我們用生產(chǎn)者/消費(fèi)者這個(gè)例子來(lái)說(shuō)明他們之間的關(guān)系:

              public class test {
            public static void main(String args[]) {
              Semaphore s = new Semaphore(1);
              Thread t1 = new Thread(s, "producer1");
              Thread t2 = new Thread(s, "producer2");
              Thread t3 = new Thread(s, "producer3");
              Thread t4 = new Thread(s, "consumer1");
              Thread t5 = new Thread(s, "consumer2");
              Thread t6 = new Thread(s, "consumer3");
              t1.start();
              t2.start();
              t3.start();
              t4.start();
              t5.start();
              t6.start();
            }
          }

          class Semaphore
              implements Runnable {
            private int count;
            public Semaphore(int n) {
              this.count = n;
            }

            public synchronized void acquire() {
              while (count == 0) {
                try {
                  wait();
                }
                catch (InterruptedException e) {
                  //keep trying
                }
              }
              count--;
            }

            public synchronized void release() {
              while (count == 10) {
                try {
                  wait();
                }
                catch (InterruptedException e) {
                  //keep trying
                }
              }
              count++;
              notifyAll(); //alert a thread that's blocking on this semaphore
            }

            public void run() {
              while (true) {
                if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {
                  acquire();
                }
                else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {
                  release();
                }
                System.out.println(Thread.currentThread().getName() + " " + count);
              }
            }
          }

                 生產(chǎn)者生產(chǎn),消費(fèi)者消費(fèi),一般沒(méi)有沖突,但當(dāng)庫(kù)存為0時(shí),消費(fèi)者要消費(fèi)是不行的,但當(dāng)庫(kù)存為上限(這里是10)時(shí),生產(chǎn)者也不能生產(chǎn).請(qǐng)好好研讀上面的程序,你一定會(huì)比以前進(jìn)步很多.

                上面的代碼說(shuō)明了synchronized和wait,notify沒(méi)有絕對(duì)的關(guān)系,在synchronized聲明的方法、代碼塊中,你完全可以不用wait,notify等方法,但是,如果當(dāng)線(xiàn)程對(duì)某一資源存在某種爭(zhēng)用的情況下,你必須適時(shí)得將線(xiàn)程放入等待或者喚醒.

           
          posted @ 2008-01-02 18:14 hk2000c 閱讀(369) | 評(píng)論 (0)編輯 收藏

          2003 年 11 月 24 日

          盡管 SSL 阻塞操作――當(dāng)讀寫(xiě)數(shù)據(jù)的時(shí)候套接字的訪(fǎng)問(wèn)被阻塞――與對(duì)應(yīng)的非阻塞方式相比提供了更好的 I/O 錯(cuò)誤通知,但是非阻塞操作允許調(diào)用的線(xiàn)程繼續(xù)運(yùn)行。本文中,作者同時(shí)就客戶(hù)端和服務(wù)器端描述了如何使用Java Secure Socket Extensions (JSSE) 和 Java NIO (新 I/O)庫(kù)創(chuàng)建非阻塞的安全連接,并且介紹了創(chuàng)建非阻塞套接字的傳統(tǒng)方法,以及使用JSSE 和 NIO 的一種可選的(必需的)方法。

          阻塞,還是非阻塞?這就是問(wèn)題所在。無(wú)論在程序員的頭腦中多么高貴……當(dāng)然這不是莎士比亞,本文提出了任何程序員在編寫(xiě) Internet 客戶(hù)程序時(shí)都應(yīng)該考慮的一個(gè)重要問(wèn)題。通信操作應(yīng)該是阻塞的還是非阻塞的?

          許多程序員在使用 Java 語(yǔ)言編寫(xiě) Internet 客戶(hù)程序時(shí)并沒(méi)有考慮這個(gè)問(wèn)題,主要是因?yàn)樵谝郧爸挥幸环N選擇――阻塞通信。但是現(xiàn)在 Java 程序員有了新的選擇,因此我們編寫(xiě)的每個(gè)客戶(hù)程序也許都應(yīng)該考慮一下。

          非阻塞通信在 Java 2 SDK 的 1.4 版被引入 Java 語(yǔ)言。如果您曾經(jīng)使用該版本編過(guò)程序,可能會(huì)對(duì)新的 I/O 庫(kù)(NIO)留下了印象。在引入它之前,非阻塞通信只有在實(shí)現(xiàn)第三方庫(kù)的時(shí)候才能使用,而第三方庫(kù)常常會(huì)給應(yīng)用程序引入缺陷。

          NIO 庫(kù)包含了文件、管道以及客戶(hù)機(jī)和服務(wù)器套接字的非阻塞功能。庫(kù)中缺少的一個(gè)特性是安全的非阻塞套接字連接。在 NIO 或者 JSSE 庫(kù)中沒(méi)有建立安全的非阻塞通道類(lèi),但這并不意味著不能使用安全的非阻塞通信。只不過(guò)稍微麻煩一點(diǎn)。

          要完全領(lǐng)會(huì)本文,您需要熟悉:

          • Java 套接字通信的概念。您也應(yīng)該實(shí)際編寫(xiě)過(guò)應(yīng)用程序。而且不只是打開(kāi)連接、讀取一行然后退出的簡(jiǎn)單應(yīng)用程序,應(yīng)該是實(shí)現(xiàn) POP3 或 HTTP 之類(lèi)協(xié)議的客戶(hù)機(jī)或通信庫(kù)這樣的程序。
          • SSL 基本概念和加密之類(lèi)的概念。基本上就是知道如何設(shè)置一個(gè)安全連接(但不必?fù)?dān)心 JSSE ――這就是關(guān)于它的一個(gè)“緊急教程”)。
          • NIO 庫(kù)。
          • 在您選擇的平臺(tái)上安裝 Java 2 SDK 1.4 或以后的版本。(我是在 Windows 98 上使用 1.4.1_01 版。)

          如果需要關(guān)于這些技術(shù)的介紹,請(qǐng)參閱 參考資料部分。

          那么到底什么是阻塞和非阻塞通信呢?

          阻塞和非阻塞通信

          阻塞通信意味著通信方法在嘗試訪(fǎng)問(wèn)套接字或者讀寫(xiě)數(shù)據(jù)時(shí)阻塞了對(duì)套接字的訪(fǎng)問(wèn)。在 JDK 1.4 之前,繞過(guò)阻塞限制的方法是無(wú)限制地使用線(xiàn)程,但這樣常常會(huì)造成大量的線(xiàn)程開(kāi)銷(xiāo),對(duì)系統(tǒng)的性能和可伸縮性產(chǎn)生影響。java.nio 包改變了這種狀況,允許服務(wù)器有效地使用 I/O 流,在合理的時(shí)間內(nèi)處理所服務(wù)的客戶(hù)請(qǐng)求。

          沒(méi)有非阻塞通信,這個(gè)過(guò)程就像我所喜歡說(shuō)的“為所欲為”那樣。基本上,這個(gè)過(guò)程就是發(fā)送和讀取任何能夠發(fā)送/讀取的東西。如果沒(méi)有可以讀取的東西,它就中止讀操作,做其他的事情直到能夠讀取為止。當(dāng)發(fā)送數(shù)據(jù)時(shí),該過(guò)程將試圖發(fā)送所有的數(shù)據(jù),但返回實(shí)際發(fā)送出的內(nèi)容。可能是全部數(shù)據(jù)、部分?jǐn)?shù)據(jù)或者根本沒(méi)有發(fā)送數(shù)據(jù)。

          阻塞與非阻塞相比確實(shí)有一些優(yōu)點(diǎn),特別是遇到錯(cuò)誤控制問(wèn)題的時(shí)候。在阻塞套接字通信中,如果出現(xiàn)錯(cuò)誤,該訪(fǎng)問(wèn)會(huì)自動(dòng)返回標(biāo)志錯(cuò)誤的代碼。錯(cuò)誤可能是由于網(wǎng)絡(luò)超時(shí)、套接字關(guān)閉或者任何類(lèi)型的 I/O 錯(cuò)誤造成的。在非阻塞套接字通信中,該方法能夠處理的唯一錯(cuò)誤是網(wǎng)絡(luò)超時(shí)。為了檢測(cè)使用非阻塞通信的網(wǎng)絡(luò)超時(shí),需要編寫(xiě)稍微多一點(diǎn)的代碼,以確定自從上一次收到數(shù)據(jù)以來(lái)已經(jīng)多長(zhǎng)時(shí)間了。

          哪種方式更好取決于應(yīng)用程序。如果使用的是同步通信,如果數(shù)據(jù)不必在讀取任何數(shù)據(jù)之前處理的話(huà),阻塞通信更好一些,而非阻塞通信則提供了處理任何已經(jīng)讀取的數(shù)據(jù)的機(jī)會(huì)。而異步通信,如 IRC 和聊天客戶(hù)機(jī)則要求非阻塞通信以避免凍結(jié)套接字。





          回頁(yè)首


          創(chuàng)建傳統(tǒng)的非阻塞客戶(hù)機(jī)套接字

          Java NIO 庫(kù)使用通道而非流。通道可同時(shí)用于阻塞和非阻塞通信,但創(chuàng)建時(shí)默認(rèn)為非阻塞版本。但是所有的非阻塞通信都要通過(guò)一個(gè)名字中包含 Channel 的類(lèi)完成。在套接字通信中使用的類(lèi)是 SocketChannel, 而創(chuàng)建該類(lèi)的對(duì)象的過(guò)程不同于典型的套接字所用的過(guò)程,如清單 1 所示。


          清單 1. 創(chuàng)建并連接 SocketChannel 對(duì)象
          SocketChannel sc = SocketChannel.open();
                      sc.connect("www.ibm.com",80);
                      sc.finishConnect();
                      

          必須聲明一個(gè) SocketChannel 類(lèi)型的指針,但是不能使用 new 操作符創(chuàng)建對(duì)象。相反,必須調(diào)用 SocketChannel 類(lèi)的一個(gè)靜態(tài)方法打開(kāi)通道。打開(kāi)通道后,可以通過(guò)調(diào)用 connect() 方法與它連接。但是當(dāng)該方法返回時(shí),套接字不一定是連接的。為了確保套接字已經(jīng)連接,必須接著調(diào)用 finishConnect()

          當(dāng)套接字連接之后,非阻塞通信就可以開(kāi)始使用 SocketChannel 類(lèi)的 read()write() 方法了。也可以把該對(duì)象強(qiáng)制轉(zhuǎn)換成單獨(dú)的 ReadableByteChannelWritableByteChannel 對(duì)象。無(wú)論哪種方式,都要對(duì)數(shù)據(jù)使用 Buffer 對(duì)象。因?yàn)?NIO 庫(kù)的使用超出了本文的范圍,我們不再對(duì)此進(jìn)一步討論。

          當(dāng)不再需要套接字時(shí),可以使用 close() 方法將其關(guān)閉:

          sc.close();
                      

          這樣就會(huì)同時(shí)關(guān)閉套接字連接和底層的通信通道。





          回頁(yè)首


          創(chuàng)建替代的非阻塞的客戶(hù)機(jī)套接字

          上述方法比傳統(tǒng)的創(chuàng)建套接字連接的例程稍微麻煩一點(diǎn)。不過(guò),傳統(tǒng)的例程也能用于創(chuàng)建非阻塞套接字,不過(guò)需要增加幾個(gè)步驟以支持非阻塞通信。

          SocketChannel 對(duì)象中的底層通信包括兩個(gè) Channel 類(lèi): ReadableByteChannelWritableByteChannel。 這兩個(gè)類(lèi)可以分別從現(xiàn)有的 InputStreamOutputStream 阻塞流中使用 Channels 類(lèi)的 newChannel() 方法創(chuàng)建,如清單 2 所示:


          清單 2. 從流中派生通道
          ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());
                      WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());
                      

          Channels 類(lèi)也用于把通道轉(zhuǎn)換成流或者 reader 和 writer。這似乎是把通信切換到阻塞模式,但并非如此。如果試圖讀取從通道派生的流,讀方法將拋出 IllegalBlockingModeException 異常。

          相反方向的轉(zhuǎn)換也是如此。不能使用 Channels 類(lèi)把流轉(zhuǎn)換成通道而指望進(jìn)行非阻塞通信。如果試圖讀從流派生的通道,讀仍然是阻塞的。但是像編程中的許多事情一樣,這一規(guī)則也有例外。

          這種例外適合于實(shí)現(xiàn) SelectableChannel 抽象類(lèi)的類(lèi)。 SelectableChannel 和它的派生類(lèi)能夠選擇使用阻塞或者非阻塞模式。 SocketChannel 就是這樣的一個(gè)派生類(lèi)。

          但是,為了能夠在兩者之間來(lái)回切換,接口必須作為 SelectableChannel 實(shí)現(xiàn)。對(duì)于套接字而言,為了實(shí)現(xiàn)這種能力必須使用 SocketChannel 而不是 Socket

          回顧一下,要?jiǎng)?chuàng)建套接字,首先必須像通常使用 Socket 類(lèi)那樣創(chuàng)建一個(gè)套接字。套接字連接之后,使用 清單 2中的兩行代碼把流轉(zhuǎn)換成通道。


          清單 3. 創(chuàng)建套接字的另一種方法
          Socket s = new Socket("www.ibm.com", 80);
                      ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());
                      WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());
                      

          如前所述,這樣并不能實(shí)現(xiàn)非阻塞套接字通信――所有的通信仍然在阻塞模式下。在這種情況下,非阻塞通信必須模擬實(shí)現(xiàn)。模擬層不需要多少代碼。讓我們來(lái)看一看。

          從模擬層讀數(shù)據(jù)

          模擬層在嘗試讀操作之前首先檢查數(shù)據(jù)的可用性。如果數(shù)據(jù)可讀則開(kāi)始讀。如果沒(méi)有數(shù)據(jù)可用,可能是因?yàn)樘捉幼直魂P(guān)閉,則返回表示這種情況的代碼。在清單 4 中要注意仍然使用了 ReadableByteChannel 讀,盡管 InputStream 完全可以執(zhí)行這個(gè)動(dòng)作。為什么這樣做呢?為了造成是 NIO 而不是模擬層執(zhí)行通信的假象。此外,還可以使模擬層與其他通道更容易結(jié)合,比如向文件通道內(nèi)寫(xiě)入數(shù)據(jù)。


          清單 4. 模擬非阻塞的讀操作
          /* The checkConnection method returns the character read when
                      determining if a connection is open.
                      */
                      y = checkConnection();
                      if(y <= 0) return y;
                      buffer.putChar((char ) y);
                      return rbc.read(buffer);
                      

          向模擬層寫(xiě)入數(shù)據(jù)

          對(duì)于非阻塞通信,寫(xiě)操作只寫(xiě)入能夠?qū)懙臄?shù)據(jù)。發(fā)送緩沖區(qū)的大小和一次可以寫(xiě)入的數(shù)據(jù)多少有很大關(guān)系。緩沖區(qū)的大小可以通過(guò)調(diào)用 Socket 對(duì)象的 getSendBufferSize() 方法確定。在嘗試非阻塞寫(xiě)操作時(shí)必須考慮到這個(gè)大小。如果嘗試寫(xiě)入比緩沖塊更大的數(shù)據(jù),必須拆開(kāi)放到多個(gè)非阻塞寫(xiě)操作中。太大的單個(gè)寫(xiě)操作可能被阻塞。


          清單 5. 模擬非阻塞的寫(xiě)操作
          int x, y = s.getSendBufferSize(), z = 0;
                      int expectedWrite;
                      byte [] p = buffer.array();
                      ByteBuffer buf = ByteBuffer.allocateDirect(y);
                      /* If there isn't any data to write, return, otherwise flush the stream */
                      if(buffer.remaining() == 0) return 0;
                      os.flush()
                      for(x = 0; x < p.length; x += y)
                      {
                      if(p.length - x < y)
                      {
                      buf.put(p, x, p.length - x);
                      expectedWrite = p.length - x;
                      }
                      else
                      {
                      buf.put(p, x, y);
                      expectedWrite = y;
                      }
                      /* Check the status of the socket to make sure it's still open */
                      if(!s.isConnected()) break;
                      /* Write the data to the stream, flushing immediately afterward */
                      buf.flip();
                      z = wbc.write(buf); os.flush();
                      if(z < expectedWrite) break;
                      buf.clear();
                      }
                      if(x > p.length) return p.length;
                      else if(x == 0) return -1;
                      else return x + z;
                      

          與讀操作類(lèi)似,首先要檢查套接字是否仍然連接。但是如果把數(shù)據(jù)寫(xiě)入 WritableByteBuffer 對(duì)象,就像清單 5 那樣,該對(duì)象將自動(dòng)進(jìn)行檢查并在沒(méi)有連接時(shí)拋出必要的異常。在這個(gè)動(dòng)作之后開(kāi)始寫(xiě)數(shù)據(jù)之前,流必須立即被清空,以保證發(fā)送緩沖區(qū)中有發(fā)送數(shù)據(jù)的空間。任何寫(xiě)操作都要這樣做。發(fā)送到塊中的數(shù)據(jù)與發(fā)送緩沖區(qū)的大小相同。執(zhí)行清除操作可以保證發(fā)送緩沖不會(huì)溢出而導(dǎo)致寫(xiě)操作被阻塞。

          因?yàn)榧俣▽?xiě)操作只能寫(xiě)入能夠?qū)懙膬?nèi)容,這個(gè)過(guò)程還必須檢查套接字保證它在每個(gè)數(shù)據(jù)塊寫(xiě)入后仍然是打開(kāi)的。如果在寫(xiě)入數(shù)據(jù)時(shí)套接字被關(guān)閉,則必須中止寫(xiě)操作并返回套接字關(guān)閉之前能夠發(fā)送的數(shù)據(jù)量。

          BufferedOutputReader 可用于模擬非阻塞寫(xiě)操作。如果試圖寫(xiě)入超過(guò)緩沖區(qū)兩倍長(zhǎng)度的數(shù)據(jù),則直接寫(xiě)入緩沖區(qū)整倍數(shù)長(zhǎng)度的數(shù)據(jù)(緩沖余下的數(shù)據(jù))。比如說(shuō),如果緩沖區(qū)的長(zhǎng)度是 256 字節(jié)而需要寫(xiě)入 529 字節(jié)的數(shù)據(jù),則該對(duì)象將清除當(dāng)前緩沖區(qū)、發(fā)送 512 字節(jié)然后保存剩下的 17 字節(jié)。

          對(duì)于非阻塞寫(xiě)而言,這并非我們所期望的。我們希望分次把數(shù)據(jù)寫(xiě)入同樣大小的緩沖區(qū)中,并最終把全部數(shù)據(jù)都寫(xiě)完。如果發(fā)送的大塊數(shù)據(jù)留下一些數(shù)據(jù)被緩沖,那么在所有數(shù)據(jù)被發(fā)送的時(shí)候,寫(xiě)操作就會(huì)被阻塞。

          模擬層類(lèi)模板

          整個(gè)模擬層可以放到一個(gè)類(lèi)中,以便更容易和應(yīng)用程序集成。如果要這樣做,我建議從 ByteChannel 派生這個(gè)類(lèi)。這個(gè)類(lèi)可以強(qiáng)制轉(zhuǎn)換成單獨(dú)的 ReadableByteChannelWritableByteChannel 類(lèi)。

          清單 6 給出了從 ByteChannel 派生的模擬層類(lèi)模板的一個(gè)例子。本文后面將一直使用這個(gè)類(lèi)表示通過(guò)阻塞連接執(zhí)行的非阻塞操作。


          清單 6. 模擬層的類(lèi)模板
          public class nbChannel implements ByteChannel
                      {
                      Socket s;
                      InputStream is; OutputStream os;
                      ReadableByteChannel rbc;
                      WritableByteChannel wbc;
                      public nbChannel(Socket socket);
                      public int read(ByteBuffer dest);
                      public int write(ByteBuffer src);
                      public void close();
                      protected int checkConnection();
                      }
                      

          使用模擬層創(chuàng)建套接字

          使用新建的模擬層創(chuàng)建套接字非常簡(jiǎn)單。只要像通常那樣創(chuàng)建 Socket 對(duì)象,然后創(chuàng)建 nbChannel 對(duì)象就可以了,如清單 7 所示:


          清單 7. 使用模擬層
          Socket s = new Socket("www.ibm.com", 80);
                      nbChannel socketChannel = new nbChannel(s);
                      ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
                      WritableByteChannel wbc = (WritableByteChannel)socketChannel;
                      





          回頁(yè)首


          創(chuàng)建傳統(tǒng)的非阻塞服務(wù)器套接字

          服務(wù)器端的非阻塞套接字和客戶(hù)端上的沒(méi)有很大差別。稍微麻煩一點(diǎn)的只是建立接受輸入連接的套接字。套接字必須通過(guò)從服務(wù)器套接字通道派生一個(gè)阻塞的服務(wù)器套接字綁定到阻塞模式。清單 8 列出了需要做的步驟。


          清單 8. 創(chuàng)建非阻塞的服務(wù)器套接字(SocketChannel)
          ServerSocketChannel ssc = ServerSocketChannel.open();
                      ServerSocket ss = ssc.socket();
                      ss.bind(new InetSocketAddress(port));
                      SocketChannel sc = ssc.accept();
                      

          與客戶(hù)機(jī)套接字通道相似,服務(wù)器套接字通道也必須打開(kāi)而不是使用 new 操作符或者構(gòu)造函數(shù)。在打開(kāi)之后,必須派生服務(wù)器套接字對(duì)象以便把套接字通道綁定到一個(gè)端口。一旦套接字被綁定,服務(wù)器套接字對(duì)象就可以丟棄了。

          通道使用 accept() 方法接收到來(lái)的連接并把它們轉(zhuǎn)給套接字通道。一旦接收了到來(lái)的連接并轉(zhuǎn)給套接字通道對(duì)象,通信就可以通過(guò) read()write() 方法開(kāi)始進(jìn)行了。





          回頁(yè)首


          創(chuàng)建替代的非阻塞服務(wù)器套接字

          實(shí)際上,并非真正的替代。因?yàn)榉?wù)器套接字通道必須使用服務(wù)器套接字對(duì)象綁定,為何不完全繞開(kāi)服務(wù)器套接字通道而僅使用服務(wù)器套接字對(duì)象呢?不過(guò)這里的通信不使用 SocketChannel ,而要使用模擬層 nbChannel。


          清單 9. 建立服務(wù)器套接字的另一種方法
          ServerSocket ss = new ServerSocket(port);
                      Socket s = ss.accept();
                      nbChannel socketChannel = new nbChannel(s);
                      ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
                      WritableByteChannel wbc = (WritableByteChannel)socketChannel;
                      





          回頁(yè)首


          創(chuàng)建 SSL 連接

          創(chuàng)建SSL連接,我們要分別從客戶(hù)端和服務(wù)器端考察。

          從客戶(hù)端

          創(chuàng)建 SS L連接的傳統(tǒng)方法涉及到使用套接字工廠和其他一些東西。我將不會(huì)詳細(xì)討論如何創(chuàng)建SSL連接,不過(guò)有一本很好的教程,“Secure your sockets with JSSE”(請(qǐng)參閱 參考資料),從中您可以了解到更多的信息。

          創(chuàng)建 SSL 套接字的默認(rèn)方法非常簡(jiǎn)單,只包括幾個(gè)很短的步驟:

          1. 創(chuàng)建套接字工廠。
          2. 創(chuàng)建連接的套接字。
          3. 開(kāi)始握手。
          4. 派生流。
          5. 通信。

          清單 10 說(shuō)明了這些步驟:


          清單 10. 創(chuàng)建安全的客戶(hù)機(jī)套接字
          SSLSocketFactory sslFactory =
                      (SSLSocketFactory)SSLSocketFactory.getDefault();
                      SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);
                      ssl.startHandshake();
                      InputStream is = ssl.getInputStream();
                      OutputStream os = ssl.getOutputStream();
                      

          默認(rèn)方法不包括客戶(hù)驗(yàn)證、用戶(hù)證書(shū)和其他特定連接可能需要的東西。

          從服務(wù)器端

          建立SSL服務(wù)器連接的傳統(tǒng)方法稍微麻煩一點(diǎn),需要加上一些類(lèi)型轉(zhuǎn)換。因?yàn)檫@些超出了本文的范圍,我將不再進(jìn)一步介紹,而是說(shuō)說(shuō)支持SSL服務(wù)器連接的默認(rèn)方法。

          創(chuàng)建默認(rèn)的 SSL 服務(wù)器套接字也包括幾個(gè)很短的步驟:

          1. 創(chuàng)建服務(wù)器套接字工廠。
          2. 創(chuàng)建并綁定服務(wù)器套接字。
          3. 接受傳入的連接。
          4. 開(kāi)始握手。
          5. 派生流。
          6. 通信。

          盡管看起來(lái)似乎與客戶(hù)端的步驟相似,要注意這里去掉了很多安全選項(xiàng),比如客戶(hù)驗(yàn)證。

          清單 11 說(shuō)明這些步驟:


          清單 11. 創(chuàng)建安全的服務(wù)器套接字
          SSLServerSocketFactory sslssf =
                      (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
                      SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
                      SSLSocket ssls = (SSLSocket)sslss.accept();
                      ssls.startHandshake();
                      InputStream is = ssls.getInputStream();
                      OutputStream os = ssls.getOutputStream();
                      





          回頁(yè)首


          創(chuàng)建安全的非阻塞連接

          要精心實(shí)現(xiàn)安全的非阻塞連接,也需要分別從客戶(hù)端和服務(wù)器端來(lái)看。

          從客戶(hù)端

          在客戶(hù)端建立安全的非阻塞連接非常簡(jiǎn)單:

          1. 創(chuàng)建并連接 Socket 對(duì)象。
          2. Socket 對(duì)象添加到模擬層上。
          3. 通過(guò)模擬層通信。

          清單 12 說(shuō)明了這些步驟:


          清單 12. 創(chuàng)建安全的客戶(hù)機(jī)連接
          /* Create the factory, then the secure socket */
                      SSLSocketFactory sslFactory =
                      (SSLSocketFactory)SSLSocketFactory.getDefault();
                      SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);
                      /* Start the handshake.  Should be done before deriving channels */
                      ssl.startHandshake();
                      /* Put it into the emulation layer and create separate channels */
                      nbChannel socketChannel = new nbChannel(ssl);
                      ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
                      WritableByteChannel wbc = (WritableByteChannel)socketChannel;
                      

          利用前面給出的 模擬層類(lèi) 就可以實(shí)現(xiàn)非阻塞的安全連接。因?yàn)榘踩捉幼滞ǖ啦荒苁褂?SocketChannel 類(lèi)打開(kāi),而 Java API 中又沒(méi)有完成這項(xiàng)工作的類(lèi),所以創(chuàng)建了一個(gè)模擬類(lèi)。模擬類(lèi)可以實(shí)現(xiàn)非阻塞通信,無(wú)論使用安全套接字連接還是非安全套接字連接。

          列出的步驟包括默認(rèn)的安全設(shè)置。對(duì)于更高級(jí)的安全性,比如用戶(hù)證書(shū)和客戶(hù)驗(yàn)證, 參考資料 部分提供了說(shuō)明如何實(shí)現(xiàn)的文章。

          從服務(wù)器端

          在服務(wù)器端建立套接字需要對(duì)默認(rèn)安全稍加設(shè)置。但是一旦套接字被接收和路由,設(shè)置必須與客戶(hù)端的設(shè)置完全相同,如清單 13 所示:


          清單 13. 創(chuàng)建安全的非阻塞服務(wù)器套接字
          /* Create the factory, then the socket, and put it into listening mode */
                      SSLServerSocketFactory sslssf =
                      (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
                      SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
                      SSLSocket ssls = (SSLSocket)sslss.accept();
                      /* Start the handshake on the new socket */
                      ssls.startHandshake();
                      /* Put it into the emulation layer and create separate channels */
                      nbChannel socketChannel = new nbChannel(ssls);
                      ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
                      WritableByteChannel wbc = (WritableByteChannel)socketChannel;
                      

          同樣,要記住這些步驟使用的是默認(rèn)安全設(shè)置。





          回頁(yè)首


          集成安全的和非安全的客戶(hù)機(jī)連接

          多數(shù) Internet 客戶(hù)機(jī)應(yīng)用程序,無(wú)論使用 Java 語(yǔ)言還是其他語(yǔ)言編寫(xiě),都需要提供安全和非安全連接。Java Secure Socket Extensions 庫(kù)使得這項(xiàng)工作非常容易,我最近在編寫(xiě)一個(gè) HTTP 客戶(hù)庫(kù)時(shí)就使用了這種方法。

          SSLSocket 類(lèi)派生自 Socket。 您可能已經(jīng)猜到我要怎么做了。所需要的只是該對(duì)象的一個(gè) Socket 指針。如果套接字連接不使用SSL,則可以像通常那樣創(chuàng)建套接字。如果要使用 SSL,就稍微麻煩一點(diǎn),但此后的代碼就很簡(jiǎn)單了。清單 14 給出了一個(gè)例子:


          清單 14. 集成安全的和非安全的客戶(hù)機(jī)連接
          Socket s;
                      ReadableByteChannel rbc;
                      WritableByteChannel wbc;
                      nbChannel socketChannel;
                      if(!useSSL) s = new Socket(host, port);
                      else
                      {
                      SSLSocketFactory sslsf = SSLSocketFactory.getDefault();
                      SSLSocket ssls = (SSLSocket)SSLSocketFactory.createSocket(host, port);
                      ssls.startHandshake();
                      s = ssls;
                      }
                      socketChannel = new nbChannel(s);
                      rbc = (ReadableByteChannel)socketChannel;
                      wbc = (WritableByteChannel)socketChannel;
                      ...
                      s.close();
                      

          創(chuàng)建通道之后,如果套接字使用了SSL,那么就是安全通信,否則就是普通通信。如果使用了 SSL,關(guān)閉套接字將導(dǎo)致握手中止。

          這種設(shè)置的一種可能是使用兩個(gè)單獨(dú)的類(lèi)。一個(gè)類(lèi)負(fù)責(zé)處理通過(guò)套接字沿著與非安全套接字的連接進(jìn)行的所有通信。一個(gè)單獨(dú)的類(lèi)應(yīng)該負(fù)責(zé)創(chuàng)建安全的連接,包括安全連接的所有必要設(shè)置,無(wú)論是否是默認(rèn)的。安全類(lèi)應(yīng)該直接插入通信類(lèi),只有在使用安全連接時(shí)被調(diào)用。

          posted @ 2007-12-31 05:07 hk2000c 閱讀(1523) | 評(píng)論 (0)編輯 收藏

          主站蜘蛛池模板: 宝坻区| 平塘县| 聂荣县| 梅州市| 巍山| 伊通| 临桂县| 鄂州市| 兴国县| 莒南县| 河曲县| 梁平县| 黄龙县| 黄冈市| 隆尧县| 湟源县| 郴州市| 车致| 固始县| 马公市| 汤原县| 道孚县| 井冈山市| 红原县| 双江| 满城县| 凉城县| 嘉祥县| 肃北| 文安县| 偏关县| 吉林市| 怀来县| 行唐县| 建平县| 伊吾县| 新兴县| 贵溪市| 怀来县| 上林县| 明光市|