<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    hk2000c技術專欄

    技術源于哲學,哲學來源于生活 關心生活,關注健康,關心他人

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      111 隨筆 :: 1 文章 :: 28 評論 :: 0 Trackbacks

    2014年4月21日 #

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

    2011年11月10日 #

    許多年以來,對于軟件項目,企業軟件開發的主流實踐一直都傾向于在單一的通用編程語言上進行標準化,從而使得Java和C#成為今天編程語言的主流選擇。隨著越來越多的目光開始投向DSL,也許我們的前腳已經踏在了一道新的門檻之上,向前望去,我們會發現在軟件項目中采用多種語言已經成為一個標準,但80年代和90年代初出現的問題不會重現。

    Martin Fowler提出,也許我們正在邁進這樣的一個新時期

    [……]在這個時期內,我們將見證多種語言在同一個項目上的應用,人們就像現在選擇框架一樣,根據功能來選擇相應的語言。

    Fowler稱:“像Hibernate、Struts和ADO這樣的大型框架,給人們在學習上帶來的挑戰,絕不亞于學習一門語言,即便你在單一一門宿主語言上使用這些框架編程也是如此。”此外,在它們的宿主語言中表述需求的難度可能會相當大,并可能引出笨拙難用的配置文件,“這些配置文件實際上就是使用XML寫的外部領域特定語言”。

    在語言中嵌入DSL,而不是使用類庫,可能會是一個更為合適的解決方案。Martin給出了這樣的一個分析結論:“API就好比是在聲明一個詞匯表,而DSL則為其增加了相應的語法,使得人們能夠寫出條理清晰的句子。”因此,使用DSL而不是框架會使代碼豐富表現力,為人們帶來“更良好的抽象處理方式”,并使“閱讀我們寫出的代碼及對我們意圖的展示變得更加容易”。

    Piers Cawley稱,DSL的主要特性并非其可讀性,而是“它們對去相應領域的高度專注”使得它們能夠更加明確地表義。Cawley為了闡述他的觀點舉了一個例子,說明DSL不僅僅能讓我們“寫出讀起來像領域專家說出來的話一樣的程序”,也可以很技術化,用來代表一個使用它們的語法進行操控的框架。

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

    不再使用單一語言進行開發所帶來的風險之一可能讓80年代末90年代初所出現的問題又再次重現,當時語言就是完全獨立的平臺,既不能互操作也不能放在一起良好地使用。Martin Fowler指出,現在的情況有這樣的一個重要區別:

    在80年代末期,人們很難讓各個語言之間緊密地互操作。這些年來,人們花了很大精力創建出可以讓不同語言緊密共存的環境。腳本語言在傳統上與C語言有著很密切的關系。在JVM和CLR平臺上也有人為互操作花費了大量精力。另外人們也在類庫上投入了很多人力物力,為的是讓語言忽視類庫的存在。

    最終,要學習并使用多種語言,對于業界乃至開發人員都可能會變成一項重要資產。《Pragmatic Programmers》這本書里面就說到,由于這樣做會對人們對編程的思考方式產生影響,因此這樣能幫助人們發現解決問題的新途徑。

    您是怎樣認為的呢?在下去的五年中,我們會開始混合使用語言,并像用類庫一樣頻繁地使用DSL嗎?

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

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

         摘要: java 和groovy 集成

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

    2008年10月25日 #

    AspectJ

     

          AspectJ是一個面向切面的框架,它擴展了Java語言。AspectJ定義了AOP語法所以它有一個專門的編譯器用來生成遵守Java字節編碼規范的Class文件


    一、AspectJ概述

    圖1 :FigureEditor例子的UML

          AspectJ(也就是AOP)的動機是發現那些使用傳統的編程方法無法很好處理的問題。考慮一個要在某些應用中實施安全策略的問題。安全性是貫穿于系統所有模塊間的問題,每個模塊都需要應用安全機制才能保證整個系統的安全性,很明顯這里的安全策略的實施問題就是一個橫切關注點,使用傳統的編程解決此問題非常的困難而且容易產生差錯,這就正是AOP發揮作用的時候了。

          傳統的面向對象編程中,每個單元就是一個,而類似于安全性這方面的問題,它們通常不能集中在一個類中處理因為它們橫跨多個類,這就導致了代碼無法重用,可維護性差而且產生了大量代碼冗余,這是我們不愿意看到的。

          面向方面編程的出現正好給處于黑暗中的我們帶來了光明,它針對于這些橫切關注點進行處理,就好象面向對象編程處理一般的關注點一樣。而作為AOP的具體實現之一的AspectJ,它向Java中加入了連接點(Join Point)這個新概念,其實它也只是現存的一個Java概念的名稱而已。它向Java語言中加入少許新結構:切點(pointcut)、通知(Advice)、類型間聲明(Inter-type declaration)和方面(Aspect)。切點和通知動態地影響程序流程,類型間聲明則是靜態的影響程序的類等級結構,而方面則是對所有這些新結構的封裝。

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

    動態連接點模型

          任何面向方面編程的關鍵元素就是連接點模型。AspectJ提供了許多種類的連接點集合,但是本篇只介紹它們中的一個:方法調用連接點集(method call join points)。一個方法調用連接點捕捉對象的方法調用。每一個運行時方法調用都是一個不同的連接點,許多其他的連接點集合可能在方法調用連接點執行時運,包括方法執行時的所有連接點集合以及在方法中其他方法的調用。我們說這些連接點集合在原來調用的連接點的動態環境中執行。

     

    切點

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

                  call(void Point.setX(int))

    捕捉每一個簽名為void Point.setX(int)的方法調用的連接點,也就是說,調用Point對象的有一個整型參數的void setX方法。切點能與其他切點通過或(||)、與(&&)以及非(!)操作符聯合。例如 call(void Point.setX(int)) || call(void Point.setY(int)) 捕捉setX或setY調用的連接點。切點還可以捕捉不同類型的連接點集合,換句話說,它們能橫切類型。例如

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

    捕捉上述五個方法調用的任意一個的連接點集合。它在本文的例子中捕捉當FigureElement移動時的所有連接點集合。AspectJ使程序員可以命名一個切點集合,以便通知的使用。例如可以為上面的那些切點命名

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

    無論什么時候,程序員都可以使用move()代替捕捉這些復雜的切點。

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

     

    通知

           雖然切點用來捕捉連接點集合,但是它們沒有做任何事。要真正實現橫切行為,我們需要使用通知機制。通知包含了切點和要在每個連連接點處執行的代碼段。AspectJ有幾種通知。

    ·前通知(Before Advice)      當到達一個連接點但是在程序進程運行之前執行。例如,前通知在方法實際調用之前運行,剛剛在方法的參數被分析之后。

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

    ·后通知(After Advice) 當特定連接點處的程序進程執行之后運行。例如,一個方法調用的后通知在方法體運行之后,剛好在控制返回調用者之前執行。因為Java程序有兩種退出連接點的形式,正常的和拋出異常。相對的就有三種后通知:返回后通知(after returning)、拋出異常后通知(after throwing)和清楚的后通知(after),所謂清楚后通知就是指無論是正常還是異常都執行的后通知,就像Java中的finally語句。

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

    ·在周圍通知(Around Advice)    在連接點到達后,顯示的控制程序進程是否執行(暫不討論)

     

    暴露切點環境

           切點不僅僅捕捉連接點,它還能暴露連接點處的部分執行環境。切點中暴露的值可以在通知體中聲明以后使用。通知聲明有一個參數列表(和方法相同)用來描述它所使用的環境的名稱。例如后通知

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

    使用了三個暴露的環境,一個名為fe的FigureElement對象,兩個整型變量x,y。通知體可以像使用方法的參數那樣使用這些變量,例如

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

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

    }

    通知的切點發布了通知參數的值,三個原生切點this、target和args被用來發布這些值/所以上述例子的完整代碼為

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

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

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

    }

    目標對象是FigureElement所以fe是after的第一個參數,調用的方法包含兩個整型參數所以x和y為after的第二和第三個參數。所以通知打印出方法setXY調用返回后對象移動到的點x和y。當然還可以使用命名切點完成同樣的工作,例如

           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+”移動到(”+x+”,”+y+”)”);

           }

     

    類型間聲明

           AspectJ的類型間聲明指的是那些跨越類和它們的等級結構的聲明。這些可能是橫跨多個類的成員聲明或者是類之間繼承關系的改變。不像通知是動態地操作,類型間聲明編譯時的靜態操作。考慮一下,Java語言中如何向一個一些的類中加入新方法,這需要實現一個特定接口,所有類都必須在各自內部實現接口聲明的方法,而使用AspectJ則可以將這些工作利用類型間聲明放在一個方面中。這個方面聲明方法和字段,然后將它們與需要的類聯系。

    假設我們想有一個Sreen對象觀察Point對象的變化,當Point是一個存在的類。我們可以通過書寫一個方面,由這個方面聲明Point對象有一個實例字段observers,用來保存所有觀察Point對象的Screen對象的引用,從而實現這個功能。

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

                  }

                  ……

    }

    然后我們可以定義一個切點stateChanges決定我們想要觀察什么并且提供一個after通知定義當觀察到變化時我們想要做什么。

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

                  }

    }

    注意無論是Sreen還是Point的代碼都沒有被修改,所有的新功能的加入都在方面中實現了,很酷吧!

     

    方面

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

    例如

           aspect Tracing {

                  OutputStream trace=System.out;

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


    二、AspectJ應用范圍

           如前所述,AspectJ可以用于應用開發的不同階段。下面討論不同階段的AspectJ的具體應用情況。

    開發型方面(Development Aspects)

           開發方面可以很容易的從真正的產品中刪除。而產品方面則被可用于開發過程和生產過程,但是僅僅影響某幾個類。

           這一部分將通過幾個例子說明方面在Java應用的開發階段是如何使用的。這些方面包括調試、測試和性能檢測等工作。方面定義的行為范圍包括簡單的代碼跟蹤、測試應用的內在聯系等等。使用AspectJ不僅使得模塊化這些功能變為可能,同時也使得根據需要打開和關閉這些功能變成可能。

     

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

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

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

    通常我們在調式程序時,會在特定的地方放置幾條輸出語句,而當調試結束時還需要找到這些代碼段將它們刪除,這樣做不但使我們的代碼很難看而且很費時間。而使用AspectJ我們可以克服以上的兩個問題,我們可以通過定義切點捕捉任何想要觀察的代碼段,利用通知可以在方面內部書寫輸出語句,而不需要修改源代碼,當不在需要跟蹤語句的時候還可以很輕松的將方面從應用中刪除并重新編譯代碼即可。

     

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

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

        }

    }

    它實現了邊界檢測功能。當FigureElement對象移動時,如果x或y的值超過了定義的邊界,程序將會拋出IllegalArgumentException異常。

     

    合同實施(Contract Enforcement)
           基于屬性的橫切機制在定義更加復雜的合同實施上非常有用。一個十分強大的功能是它可以強制特定的方法調用只出現在對應的程序中,而在其他程序中不出現。例如,下面的方面實施了一個限制,使得只有在知名的工廠方法中才能向注冊并添加FigureElement對象。實施這個限制的目的是為了確保沒有任何一個FigureElement對象被注冊多次。

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

        }

    }

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

    static aspect RegistrationProtection {

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

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

     

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

    }

    當使用這個方面后,如果代碼中存在定義的這些非法調用我們將無法通過編譯。這種情況只出現在我們只需要靜態信息的時候,如果我們需要動態信息,像上面提到的前提條件實施時,就可以利用在通知中拋出帶參數的異常來實現。

     

    配置管理(Configuration Management)
           AspectJ的配置管理可以使用類似于make-file等技術進行處理。程序員可以簡單的包括他們想要的方面進行編譯。不想要任何方面出現在產品階段的開發者也可以通過配置他們的make-file使用傳統的Java編譯器編譯整個應用。

     

    產品型方面(Production Aspects)

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

    改變監視(Change Monitoring)
           在第一個例子,方面的角色是用于維護一位數據標志,由它說明對象從最后一次顯示刷新開始是否移動過。在方面中實現這樣的功能是十分直接的,testAndClear方法被顯示代碼調用以便找到一個圖形元素是否在最近移動過。這個方法返回標志的狀態并將它設置為假。切點move捕捉所有能夠是圖形移動的方法調用。After通知截獲move切點并設置標志位。

    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;

        }

    }
     

    這個簡單例子同樣說明了在產品代碼中使用AspectJ的一些好處。考慮使用普通的Java代碼實現這個功能:將有可能需要包含標志位,testAndClear以及setFlag方法的輔助類。這些方法需要每個移動的圖形元素包含一個對setFlag方法的調用。這些方法的調用就是這個例子中的橫切關注點。

    ·顯示的捕捉了橫切關注點的結構

    ·功能容易拔插

    ·實現更加穩定

     

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

    使用AspectJ,這種上下文的傳遞可以使用模塊化的方式實現。下面代碼中的after通知僅當一個圖形對象的工廠方法在客戶ColorControllingClient的某個方法控制流程中調用時才運行。

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

        }

    }

    這個方面僅僅影響一小部分的方法,但是注意該功能的非AOP實現可能 需要編輯更多的方法。

     

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

    aspect PublicErrorLogging {

        Log log = new Log();

     

        pointcut publicMethodCall():

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

     

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

            log.write(e);

        }

    }

    在一些情況中,這個方面可以記錄一個異常兩次。這在com.bigboxco包內部的代碼自己調用本包中的公共方法時發生。為解決這個問題,我們可以使用cflow初始切點將這些內部調用排除:

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

        log.write(e);

    }

     

    結論

           AspectJ是對Java語言的簡單而且實際的面向方面的擴展。僅通過加入幾個新結構,AspectJ提供了對模塊化實現各種橫切關注點的有力支持。向以有的Java開發項目中加入AspectJ是一個直接而且漸增的任務。一條路徑就是通過從使用開發方面開始再到產品方面當擁有了AspectJ的經驗后就使用開發可重用方面。當然可以選取其他的開發路徑。例如,一些開發者將從使用產品方面馬上得到好處,另外的人員可能馬上編寫可重用的方面。

           AspectJ可以使用基于名字和基于屬性這兩種橫切點。使用基于名字橫切點的方面僅影響少數幾個類,雖然它們是小范圍的,但是比起普通的Java實現來說它們能夠減少大量的復雜度。使用基于屬性橫切點的方面可以有小范圍或著大范圍。使用AspectJ導致了橫切關注點的干凈、模塊化的實現。當編寫AspectJ方面時,橫切關注點的結構變得十分明顯和易懂。方面也是高度模塊化的,使得開發可拔插的橫切功能變成現實。

           AspectJ提供了比這兩部分簡短介紹更多的功能。本系列的下一章內容,The AspectJ Language,將介紹 AspectJ語言的更多細節和特征。系列的第三章,Examples將通過一些完整的例子說明如何使用AspectJ。建議大家在仔細閱讀了接下來的兩章后再決定是否在項目中加入AspectJ。


    三、AspectJ的高級特性

    (一)、The reflection API

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

    (二)、Aspect precedence

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

    AspectJ早已為我們考慮到了這個問題,它提供了一種設置aspect precedence的方法。對三種不同的advice來說:

    1、before advice是先執行higher-precedence,后執行lower-precedence;

    2、around advice是higher-precedence包含lower-precedence,當higher-precedence around advice沒有調用proceed()方法時,lower-precedence不會被執行;

    3、after advice與before advice正好相反,先執行執行lower-precedence,然后執行higher-precedence。

    那應該如何來聲明aspect precedence?非常簡單,只要在aspect中使用如下的語法即可:

    declare precedence : TypePattern1, TypePattern2, ..;

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

    (三)、Aspect association

    在Java中,為了節省對象每次構建的耗費,增加效率,很多人會考慮使用Singleton模式,讓jvm中只有一個實例存在。AspectJ當然為我們考慮到這個問題,Aspect association實際上就是aspect與advised join point object的一種關聯關系,這很類似于OO中association,譬如1:1,1:m等。Aspect association能讓我們能更好地控制aspect的狀態信息。

    在AspectJ中可以把Aspect association大致分為三類:

    1、Per virtual machine (default)

    一個jvm中只有一個aspect instance,AspectJ默認association。

    2、Per object

    每一個advised join point object都會產生一個aspect instance,不過同一個object instance只會產生一個aspect instance。

    3、Per control-flow association

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

    在aspect中聲明這三種association非常簡單,它的主要語法如下:

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

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

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

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

    維護aspect的狀態信息還有一種方法,就是使用introduce。可以在aspect中introduce member fields,通過fields來保存狀態信息。

     

    四、AspectJ實例

     

    使用方面的Tracing程序

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

           AspectJ可以更加方便的實現跟蹤功能并克服這些缺點。Tracing可以看作是面向整個系統的關注點,因此,Tracing方面可以完全獨立在系統之外并且在不影響系統基本功能的情況下嵌入系統。

     

    應用實例

    整個例子只有四個類。應用是關于Shape的。TwoShape類是Shape類等級的基類。

    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類有兩個子類,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版本一

    首先我們直接實現一個Trace類并不使用方面。公共接口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) {...}

    }

    如果我們沒有AspectJ,我們需要在所有需要跟蹤的方法或構造子中直接調用traceEntry和traceExit方法并且初試化TRACELEVEL和輸出流。以上面的例子來說,如果我們要跟蹤所有的方法調用(包括構造子)則需要40次的方法調用并且還要時刻注意沒有漏掉什么方法,但是使用方面我們可以一致而可靠的完成。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());

        }

    }

    這個方面在合適的時候調用了跟蹤方法。根據此方面,跟蹤方法在Shape等級中每個方法或構造子的入口和出口處調用,輸出的是各個方法的簽名。因為方法簽名是靜態信息,我們可以利用thisJoinPointStaticPart對象獲得。運行這個方面的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版本二

           版本二實現了可重用的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();

     

    }

    為了使用它,我們需要定義我們自己的子類。

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

        }

    }

    注意我們僅僅在類中聲明了一個切點,它是超類中聲明的抽象切點的具體實現。版本二的Trace類的完整實現如下

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

        }

    }

    它與版本一的不同包括幾個部分。首先在版本一中Trace用單獨的類來實現而方面是針對特定應用實現的,而版本二則將Trace所需的方法和切點定義融合在一個抽象方面中。這樣做的結果是traceEntry和traceExit方法不需要看作是公共方法,它們將由方面內部的通知調用,客戶完全不需要知道它們的存在。這個方面的一個關鍵點是使用了抽象切點,它其實與抽象方法類似,它并不提供具體實現而是由子方面實現它。

     

    Tracing版本三

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

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

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

    另一種方法是增強方法的功能,添加一個參數表示對象,例如

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

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

    然而客戶仍然有責任傳遞正確的對象,調用代碼如下

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

    這兩種方法都需要動態改變其余代碼,每個對traceEntry和traceExit方法的調用都需要改變。

           這里體現了方面實現的另一個好處,在版本二的實現中,我們只需要改變Trace方面內部的一小部分代碼,下面是版本三的Trace方面實現

    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切點排除toString方法的執行。問題是toString方法在通知內部調用,因此如果我們跟蹤它,我們將陷入無限循環中。這一點不明顯,所以必須在寫通知時格外注意。如果通知回調對象,通常都回存在循環的可能性。

           事實上,簡單的排除連接點的執行并不夠,如果在這之中調用了其他跟蹤方法,那么就必須提供以下限制

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

    排除toString方法的執行以及在這之下的所有連接點。

           總之,為了實現需求的改變我們必須在Trace方面中做一些改變,包括切點說明。但是實現的改變只局限于Trace方面內部,而如果沒有方面,則需要更改每個應用類的實現。
    (來源: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) | 評論 (2)編輯 收藏

    2008年4月12日 #

    .概述

    1.1 JMS與ActiveMQ特性  

       JMS始終在JavaEE五花八門的協議里,WebService滿天飛的時候占一位置,是因為:

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

        ActiveMQ的特性:

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

    1.2 SpringSide 的完全POJO的JMS方案   

      SpringSide 2.0在BookStore示例中,演示了用戶下訂單時,將發通知信到用戶郵箱的動作,通過JMS交給JMS服務端異步完成,避免了郵件服務器的堵塞而影響用戶的下訂。

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

      一個JMS場景通常需要三者參與:

    • 一個POJO的的Message Producer,負責使用Spring的JMS Template發送消息。
    • 一個Message Converter,負責把Java對象如訂單(Order)轉化為消息,使得Producer能夠直接發送POJO。
    • 一個MDP Message Consumer,負責接收并處理消息。

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

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

    2.引入ActiveMQ的XSD

      ActiveMQ4.1 響應Spring 2.0號召,支持了引入XML Schema namespace的簡單配置語法,簡化了配置的語句。 

      在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的那個XSD有部分錯誤,因此使用的是自行修改過的XSD。

    先在ClassPath根目錄放一個修改過的activemq-core-4.1-incubator-SNAPSHOT.xsd。

    在ClassPath 下面建立META-INF\spring.schemas 內容如下。這個spring.schemas是spring自定義scheam的配置文件,請注意"http:\://"部分寫法

    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 基礎零件 

    1. 配置ActiveMQ Broker  

       暫時采用在JVM中嵌入這種最簡單的模式,  當spring初始化時候,ActiveMQ embedded Broker 就會啟動了。

    <!--  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能夠直接發送Order對象,而不是JMS的Message對象。

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

    3.2  發送端 

    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

       消息發送者,使用JmsTemplate發送消息,綁定了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,指定負責處理消息的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

        負責調度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>

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

    4. 下篇


    1. 說明

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

       本篇將補充說明了:

       1) 使用數據庫持久化消息,保證服務器重啟時消息不會丟失
       2) 使用Jencks作正宗的JCA Container。

    2.持久化消息

    2.1 給Broker加入Persistence 配置

    在配置文件applicationContext-activemq-embedded-persitence.xml中的<amq:broker>節點加入  

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

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

    2.2 配置多種數據源

    配置多種數據源,給jdbcPersistenceAdapter使用,SpringSide 中使用的內嵌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 說明

       筆者僅僅使用了jdbcPersistenceAdapter,其實在ActiveMQ的XSD已經描述了多種PersistenceAdapter,可以參考對應的XSD文件.

      另外對于數據庫的差異主要表現在設置了userDatabaseLock="true"之后,ActiveMQ使用的[SELECT * ACTIVEMQ_LOCK FOR UPDATE] 上面,會導致一些數據庫出錯(測試中MSSQL2000/2005,HSQL都會導致出錯)。另外HSQL的腳本請參見activemq.script。

    3. Jenck(JCA Container)  

       Spring 2.0本身使用DefaultMessageListenerContainer 可以充當MDP中的Container角色,但是鑒于Jencks是JCA標準的,它不僅僅能夠提供jms的jca整合,包括其他資源比如jdbc都可以做到jca管理

    所以,同時完成了這個ActiveMQ+Spring+Jencks 配置演示,更多的針對生產系統的JCA特性展示,會在稍后的開發計劃討論中確定。

         此文檔適用于說明使用 Jecncks 和 使用Spring 2.0(DefaultMessageListenerContainer)  充當MDP Container時的區別,同時演示Jecnks 的Spring 2.0 新配置實例。

    3.1 引入ActiveMQ ResourceAdapter 和Jencks 的XSD

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

       ActiveMQ4.1 響應Spring 2.0號召,支持了引入XML Schema namespace的簡單配置語法,簡化了配置的語句。 

      在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 那個XSD 仍然有部分錯誤,因此使用的是自行修改過的XSD。(是xs:any元素引起的錯誤)

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

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

    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 基礎零件 

    1. 配置ActiveMQ Broker  參見 ActiveMQ+Spring

    2. 配置ActiveMQ Resource Adapter

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

    3. 配置Jencks 基礎配置

       具體的配置可以參見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 (主要是生成者/發送者 使用)

       這里注意下,在配置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,保證不需要用戶實現MessageListener

      ActiveMQ+Spring

     6.配置Jecnks 充當MDP的Container

      就是把上面的MessageListenerAdapter配置到Jencks里面,完成整個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) | 評論 (0)編輯 收藏

    2008年3月8日 #

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

    直板反膠正手拉球的基本原理和訓練方法

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

    正手拉球的方法與技巧
    正手拉球是一門很復雜的技術,有近臺拉球、中近臺拉球、遠臺拉球,有拉上旋球、下旋球,有近臺快帶、反拉弧圈球,拉半出臺球等等。不管拉球有多么復雜,但有一點是最重要的基礎,就是步法。步法的好壞,直接關系到正手拉球的命中率、力量的大小和拉球時的調節能力。要想練好正手拉球,就必須先練好步法。而在這一點上,是專業運動員和業余運動員最大的區別所在,業余運動員不可能像專業運動員那樣進行大量的高強度的步法訓練。但有一點是相同的,那就是擊球的技巧。只要能夠做到因勢利導,充分發揮現有的條件,也會收到一定的效果。下面,我給大家介紹一些比較實用的訓練方法和技巧:
    1、拉好定點下旋球:拉沖下旋球是直板反膠最基礎的基本功,在拉下旋球時除了注意前面提到的基本動作要領以外,要特別注意手腕的用力方法。在擊球的瞬間是用手腕去摩擦球,擊球點在來球的中上部,在用手腕摩擦球時還要根據來球旋轉的強弱再加上一定的撞擊。就是人們常說的又摩又打。拉沖下旋球旋轉弱的來球要連摩擦帶撞擊,撞擊可稍大于摩擦。拉沖下旋球旋轉強的來球必須用力摩擦擊球,用自己拉球的力量抵消來球的旋轉。在擊球的瞬間要特別注意擊球時一定要把球往前送,不能靠力量去硬碰球。這就是我們常說的“吃球”,盡量讓球在球板上停留的時間長一些。經常這樣訓練拉球,你對球的感覺就會越來越好,拉球就會越來越有數,慢慢達到運用自如。訓練的方法,在沒有多球條件的情況下可采用拉球一方發下旋球到對方的反手位讓對方搓長球到側身位,然后發力拉沖這個球。拉球時一定要注意用全力拉沖,不要考慮下一板球對方是否能夠防過來。要的就是讓你防不過來。經常這樣訓練,你的拉球力量一定會提高。在有多球的條件下,可讓對方發下旋球到你的側身位,定點發力拉沖這種球。拉球時要掌握好擊球時間,在對方來球跳到最高點或下降前期擊球最好。擊球時間一定要相對固定,這樣容易掌握拉球的命中率,好調節。出界多就向前送一點,下網多就多摩擦一點。在定點拉沖下旋球比較有數的情況下,再把來球的落點擴大到全臺的定點拉沖,這樣不斷加大拉球的難度,拉球的水平就會不斷提高。
    2、拉好定點上旋球:拉上旋球和下旋球不同的是,拉上旋球擊球點在來球的上部,摩擦球要大于撞擊球,擊球的瞬間一定要往前送。訓練的方法基本和搶拉下旋球一樣,只是來球的旋轉不一樣,是上旋球。在推擋后側身發力拉沖這板球,或對方變你正手位后發力拉沖,反復練習。有多球訓練的條件,可以由對方直接發上旋球到你的正手位和側身位搶沖,落點可以從定點到不定點,逐步提高擊球的難度。
    3、練好反拉弧圈球:反拉弧圈球是一種高級技術,尤其是業余運動員掌握了這項技術就像如魚得水,你就掌握了比賽的主動權。因為一般的業余運動員在拉弧圈球時拉高吊弧圈球的時候多,你掌握了反拉弧圈球的技術,你就站在了比對方高一擋的層次上。反拉弧圈球的要領,首先要自己發力,盡量少借對方的旋轉,用自己拉球的力量去抵消對方來球的旋轉。其次是在反拉時摩擦球一定要薄,摩擦球的上部甚至頂部,既要借對方來球的旋轉的力,還要自己發力摩擦球。越是自己發力反拉,命中率越高。越是怕對方的旋轉去碰球,越是容易吃對方的旋轉。訓練的方法,對方發下旋球到你的反手位,你搓球到對方側身位,對方拉高吊弧圈球到你反手位,你側身反拉,這樣反復練習,等基本掌握了反拉弧圈球的規律以后,再把反拉擴大到全臺和不定點反拉。
    4、近臺正手快帶弧圈球:這項技術是防守中很先進的技術,也是很難掌握的技術,是90年代后期才逐漸被采用的技術。在這之前人們在正手位的防守都是平擋,借對方來球的旋轉把球擋過去,因而在比賽關鍵的時刻就很容易因緊張而造成失誤,即使不失誤,防過去的球也沒有威脅,很容易被對方連續進攻。到90年代后期,中國的運動員把反拉的技術運用在近臺的防守上,特別是直板反膠打法的運動員運用更多,加快了攻防轉換的節奏,收到了很好的效果,馬林在這項技術的運用上是非常突出的。這項技術要求運動員的對來球的判斷要非常快、準確,手上對球的感覺要求很高,因為有很多球是在失去身體重心或不到位的情況下,完全靠運動員手上的功夫去完成技術動作。我想雖然目前在業余運動員中能真正掌握這項技術的不多,但已經具備了一定水平的運動員可以去嘗試一下,也許你會有意外的收獲。
    這項技術的技巧主要在于掌握好擊球時間和手腕的用力,擊球時間盡量在球的起跳前期(上升期),當步法實在到不了位的情況下,還可以在球剛一跳起時就擊球。擊球時靠腰和手腕發力,接觸球的頂部。接觸球時既要借對方來球旋轉的力,同時自己一定要發力去摩擦球,盡量摩擦薄一點,摩擦厚就容易下網,在摩擦球的瞬間一定要把球往前頂。訓練方法可采用搓下旋球到對方正手位讓對方拉弧圈球到自己的正手位,然后正手近臺快帶。這樣反復練習就會逐漸掌握擊球的基本方法,在快帶對方從下旋球拉起來的弧圈球比較熟練的情況下,再進行推直線讓對方拉弧圈球到自己的正手位快帶上旋弧圈球的訓練。這樣,你就會慢慢掌握在防守中正手近臺快帶弧圈球的技術。這項技術的關鍵點是在擊球時一定摩擦球要薄,而且自己一定要主動發力去帶球。

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


    從弧圈球的風格上來講,目前主要分為歐洲派和亞洲派。歐洲選手拉弧圈球的時候,撞擊的成分比較多,因此球在飛行的過程中速度快,力量大,弧線低;亞洲選手拉弧圈球的時候,摩擦的成分比較多,因此球在彈起后的過程中速度快,旋轉強,弧線低。隨著弧圈球技術的發展,目前各國選手都在相互學習,相互借鑒,因此并沒有十分明顯的風格區別,而是根據不同的球運用不同的技術。

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

    一、技術動作分解

    1.準備動作:

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

    2.拉球:

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


    關于擊球部位,對于以拉打為主和摩擦為主是有區別的。 以拉打為主的選手,擊球的部位一般為B點或B、C點之間。以摩擦為主的選手,擊球部位一般為C點。
    拉下旋球的動作要領與拉上旋球基本一致。只是拉下旋球時,右肩沉的更低一些,擊球的部位一般為B點,且用力的方向向上多一些。

    3.步法

    拉球時,要根據來球的位置,時刻跑動來調節擊球的最佳位置。跑動時要保證重心盡量平穩,身體不要亂晃。

    二、高吊弧圈與前沖弧圈

    高吊弧圈一般是針對拉下旋球而言的。高吊弧圈以旋轉見長,但是弧線略高,速度較慢。高吊弧圈的擊球部位一般為B點,甚至是A、B點之間,這要根據來球的旋轉而定。拉高吊弧圈,右肩下沉的較低,用力方向向上的比較多,先要制造一個高過球網的弧線,然后用力方向向前,再制造一個向前的弧線。如果一味的向上硬拉,則球很容易出界。

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

    三、臺內弧圈球技術

    臺內弧圈球的技術難度比較大。首先要判斷來球的位置和高度,根據來球的高度來決定引拍的高度。拉臺內弧圈球,一般引拍的高度較高,往往與臺面高度持平,甚至高于臺面。擊球部位一般為D點。由于摩擦球的部位很薄,因為對于下旋非常強的臺內球,處理起來難度很大。而對于不太轉的下旋球來說,臺內弧圈球給對方造成的威脅還是很大的。拉臺內弧圈球,要注意用力方向向上多一些,繼而向前,要把弧線拉短。

    四、套膠與弧圈球

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

    亞洲人拉球多摩擦,因為國產的套膠,如狂飆系列套膠,膠皮黏性強,海綿彈性非常實在,非常大。在球接觸拍子的時候,膠皮給了來球很大的阻力,而海綿被壓縮的程度也不大,這樣就造成的脫板速度很快。因此只有多摩擦,以旋轉克旋轉才能拉出高質量的弧圈球。所以使用國產套膠對拉球的技術要求較高。

    隨著乒乓器材的發展,國內已經生產出很多新產品,兼具了國產與進口的很多優點,對于眾多的乒乓球愛好者來說,又多了很多的選擇。

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


    拉弧圈最重要的環節是什么?是吃球。

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

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

    如果拉上旋來球,就是逆旋轉擊球,球和膠皮接觸的瞬間,球和膠皮的相對速度大,來球減轉的過程就是個緩沖過程,球不會很快脫板,一般不會有吃不住球的感覺。

    如果拉下旋來球,則是順旋轉擊球,如果揮拍向上的速度低于旋轉, 便無法吃得住球。就好比玩陀螺,抓住轉動的陀螺容易,因為是逆旋轉,而給陀螺加轉就很困難,要用比陀螺更快速度的鞭子去抽。這一點對著削球手感覺最為明顯,力量還沒作用到球上,球就脫板了,常會有吃不住球的情況發生。如果仔細觀察錄像,國手們拉削球時揮拍摩擦都極快,揮拍之所以快的就是靠發力抵消來球的旋轉。對下旋來球, 揮拍速度是不能低于旋轉的。

    拉下旋球為保證能吃住球需掌握三個要點:

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

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

    三是掌握擊球時間。一般是在下降期拉,一方面下旋球在空中飛行時會逐漸減轉,另一方面,球在下落時由于下落速度和球的旋轉方向相反,兩個速度相抵,揮拍相對速度就體現的更快一些。

    第一板從下旋拉起的弧圈很難防守,也是因為具有其“順旋”的加成效果。一旦練成,對對手威懾極大。





    前言:都說下旋球下降期好拉,為甚么?什么出轉沒出轉,都是憑感覺。請看物理學的精確分析。我們對事物不僅要知其然,也要知其所以然。

    如圖:球為下旋,設球拍垂直向上摩擦,與球在a點接觸。假設球旋轉每秒30轉,直徑40MM,則a點線速度為:

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

    1、拉上升期,a點速度為轉速加球速。

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

    2、拉下降期,a點速度為轉速減球速。

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

    可見拉上升期比下降期需要三倍的速度!

     

     

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

    2008年3月5日 #

       我們在單位里調試用戶系統時,單位的網絡地址一般和用戶的網絡地址不在一個網段上,如果沒有路由器則兩網不能互通,那對工作會很有影響。硬路由器價格昂貴也沒有必要去配,因為SOLARIS可以很容易地設成軟件路由器,而不需另外花費。

      1、編輯文件/etc/hosts,為該工作站加另一個網段地址:

       #vi/etc/hosts

       127.0.0.1localhost

       192.9.200.1serverloghost;本例的主機名及地址

       192.9.201.1 anoserver;另一個對應的名稱及地址

      2、編輯文件/etc/nerworks,將兩個網絡的地址加入:

       #vi /etc/networks

       loc 192.9.200;本網網址

       ano 192.9.201;另一個網的網址

      3、新建文件/etc/gateways,該文件只要存在沒有內容也可,以使SOLARIS在啟動時運行路由器服務進程。

       #cat/dev/null>/etc/gateways

      4、查詢主網卡的名稱:

       #ifconfig-a;列出系統中的所有網絡接口

       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網卡名,如果你所用的是10M網卡則名為le。

      5、新建文件/etc/hostname.hme0:1,將/etc/josts中的另一個主機名填入,以使SOLARIS啟動時在物理接口hme0上建立一個邏輯接口。

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

      7、效果:

       在工作站啟動中,可以看到“machine is a router.”的噗顯示。表明本機已成為一個路由器,會向網絡上發RIP包,用接口查詢命令可見:

       #ifcofig -a ;列出系統中的所有網絡接口

       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

      以上表明已啟動了hme0上的一個邏輯接口,地址為192.9.201.1。

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

    2008年2月28日 #

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

      判斷意識。對付不同的來球,應用不同的打法。若想打好球,首先應對來球作出及時、準確的判斷。這是正確還擊來球的前提。
      盯球意識。盯球,是正確判斷的基礎。不少人對來球判斷不及時或錯誤,都是因為盯球不夠。運動員每打完一板球后,都應隨球密切注視對方擊球的動作(尤其是擊球瞬間的動作),并緊盯對方擊出球的弧線。
      移步意識。對方來球落點和節奏不定,為確保在最佳的位置和時間擊球,或最大限度地發揮個人的特長技術(如反手位用側身攻),必須移步擊球。應明確,打乒乓球絕不是單純的手法問題,隨技術水平的提高,腳步移動的重要性將越來越明;顯、它是爭取主動、搶先進攻的有力保證。

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

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

    僅列出標題  下一頁
    主站蜘蛛池模板: 在线观看免费无码专区| 日本成人在线免费观看| 亚洲综合激情五月色一区| 热99re久久免费视精品频软件| 老妇激情毛片免费| 亚洲一区二区在线视频| 午夜毛片不卡免费观看视频| 一级毛片一级毛片免费毛片| 亚洲男人的天堂在线播放| 四虎成人免费大片在线| 一区二区视频免费观看| 亚洲av永久无码嘿嘿嘿| 国产成人免费永久播放视频平台| 日韩a级无码免费视频| 亚洲综合中文字幕无线码| 亚洲热线99精品视频| 日本高清免费不卡在线| 四虎成年永久免费网站 | 黄色一级视频免费| 亚洲国产成人综合| 国精无码欧精品亚洲一区| 全免费a级毛片免费看无码| 久久99热精品免费观看动漫| 免费人成视频在线播放| 亚洲AV无码一区二区乱子仑 | a级黄色毛片免费播放视频| 亚洲一卡2卡3卡4卡乱码 在线| 亚洲av一综合av一区| 国内精品久久久久久久亚洲| 处破痛哭A√18成年片免费| 亚洲三级在线免费观看| 黄色网站软件app在线观看免费| 久久亚洲欧美国产精品| 亚洲AV无码一区二区三区网址| 亚洲一级毛片免费观看| 亚洲国产成人精品无码区在线网站| 亚洲日韩v无码中文字幕| 亚洲欧洲自拍拍偷午夜色无码| 亚洲国产精品自产在线播放| 国产精品公开免费视频| 四虎永久免费地址在线观看|