hk2000c技术专栏

技术源于哲学,哲学来源于生活 关心生活,关注健康,关心他人

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  111 随笔 :: 1 文章 :: 28 评论 :: 0 Trackbacks

2008年3月8日 #

posted @ 2014-04-21 21:33 hk2000c 阅读(171) | 评论 (0)编辑 收藏

许多年以来,对于软件项目,企业软件开发的主流实践一直都倾向于在单一的通用编程语言上进行标准化,从而使得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 阅读(295) | 评论 (0)编辑 收藏

     摘要: java 装载 groovy 方法  阅读全文
posted @ 2011-11-10 05:45 hk2000c 阅读(722) | 评论 (0)编辑 收藏

     摘要: java 和groovy 集成

  阅读全文
posted @ 2011-11-10 05:37 hk2000c 阅读(1048) | 评论 (0)编辑 收藏

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 阅读(910) | 评论 (2)编辑 收藏

.概述

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 阅读(732) | 评论 (0)编辑 收藏

请参考一下马琳、王皓的主管教练吴敬平的文章

直板反胶正手拉球的基本原理和训练方法

乒乓球基本上是一项圆周运动,正手和反手拉球都是以运动员的身体重心为轴心、以身体到身体重心的连线为半径进行圆周运动。因此,不管是正手还是反手击球都必须符合这个原理,从这个意义上讲,正手拉球动作的基本原理就是一种力的传递。
正手拉球的基本要点
基本站位:两腿张开与肩的宽度为差不多相同,身体稍微前倾,重心在前脚掌上,拉球时,身体向右转(以右手为例),重心放在右脚上,在转腰的过程中,用腰控制大臂,右肩稍底,小臂自然下垂,用手腕控制板型,板型前倾(拇指用力压住球板,食指稍微放松,中指顶住球板),板型前倾的角度因来球的旋转不同而调整。
击球原理:击球的时候,以右手为例,首先是腿上发力,向左蹬腿,身体重心从右脚向左脚转换,交换重心,身体前迎,身体前迎的方向要和击球的方向一致。然后是腰上发力,用腰带动大臂转动,把力传递到前臂,在击球一瞬间,收缩前臂用力击球。从力学的原理讲,正手拉球前,小臂和大臂之间的角度越小越好,这是加大半径,半径越大,初速度就越大,在击球瞬间突然收缩前臂,使半径变小而获得加速度,使速度加快,力量加大。击球时,小臂和大臂之间角度的变化要根据来球和击球的需要进行变化。很多运动员在进行正手拉球时往往只注意了收前臂而忽略了转腰,用腰来控制手臂的发力,或者是注意了用腰的发力来带动手臂而忽略了收前臂,前臂和大臂之间的角度几乎没有变化或变化很小。总结起来正手拉球应注意四点: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 阅读(701) | 评论 (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 阅读(974) | 评论 (0)编辑 收藏