SCA作为一种新的编程模型,自然也需要提供完整的事务支持。所幸的是WPS本身是基于WebSphere
Application Server的,所以底层天然就具有了强大的事务处理能力。因此在SCA层次上只需要考虑事务的语法是什么,然后在合适的阶段把它映射到WAS的事务上就可以了。本文从SCA容器开始说起,然后介绍SCA的事务模型极其和J2EE事务的映射规则,最后给出一个简单的例子来看整个运作是如何运作的。
1. SCA容器
要了解SCA的事务是如何运作的,首先得先知道SCA容器是如何工作的。
和大部分的容器一样,SCA容器的设计也是基于责任链的——在请求被路由到真正的服务实现之前,无论是是在调用端还是服务提供端都有多个handler来先对请求进行处理。同理在产生响应消息后,结果也是经过多个消息处理器后才返回给调用者的。这些消息处理器各司其责,有的处理安全、有的对消息进行验证,有的则处理事务等等。这种设计模式在AXIS架构中得到了充分的体现。
而SCA容器提供的的编程风格和Web服务的动态调用也很类似。例如在调用一个Web服务时,个典型的Web服务的动态调用代码如下:
以下是引用片段:
Service service = factory.createService(new
QName(qnameService));
Call call = service.createCall(port);
String result = (String)call.invoke(params);
而在SCA中,对应的代码如下:
以下是引用片段:
Service service = ServiceManager.INSTANCE.locateService(referencename);
String result = (String) service.invoke("target
method name",parameters);
无论是Web服务调用时候面对的Call还是SCA调用时候的Service,他们都只是服务的一个代理,这个代理通过调用预先配置好的消息处理器对请求消息进行处理,然后把消息传递给本地或者远程的服务实现端的代理,服务实现端的代理执行类似的操作后调用服务实现。。。
在Web服务编程模型中,除了系统预定义的消息处理器,我们还可以加入用户自定义的消息处理器来实现特定的功能。但在目前的WPS的SCA实现中,目前没有提供公开的接口来支持自定义的消息处理器:
2. SCA的事务模型
如下图所示,在SCA的世界中,一个SCA组件是由接口、服务实现和引用(可选)三个部分构成:
其中接口部分表示该组件能提供的业务能力;实现则是实现了该接口的组件;而引用则表示在服务实现中需要调用到的其他服务
在事务方面,SCA通过在三个部分(接口、服务实现和引用)分别提供不同的限定符(qualifier)的方式来完成事务的定制。
接口部分的限定符有:
Join Transaction. 可以取值true或者false。这个限定符表示该组件是否愿意加入到调用方的事务中。
Join Activity Session. 可以取值true或者false。这个限定符表示该组件是否愿意加入到调用方的Activity
session中。
实现部分的限定符有:
Transaction。可以取值Global、Local或者Any。这个限定符表示该组件对运行环境的需求。global表示需要运行在一个全局事务中,这样SCA容器必须保证在调用该服务实现时总是有一个活动的全局事务。lobal则表示要运行在本地事务中。容器必须挂起当前活动的全局事务,并负责启动一个本地事务。any
则表示该实现既可以运行在一个已有的全局事务中也可以运行在本地事务中。如果选择了any,容器如果发现当前没有活动的事务,就会建立一个本地事务。
Activity session:可以取值true、false或者any。
引用部分的限定符有:
Suspend transaction。取值为true或者false。用来表示在调用这个目标服务时是否需要把当前的全局事务挂起(但目标服务是否能加入到这个事务中还取决于目标服务的事务配置)。如果选择true,则表示当前服务的事务不会被传播给所调用的服务。这个限定符只针对同步调用有效。
Suspend activity session。和suspend transaction类似,是否需要挂起当前的activity
session。这个限定符只针对同步调用有效。
Asynchronous invocation。取值为commit或者call。这是用来表示在通过异步的方式调用这个目标服务时,请求的JMS消息是否立即提交还是和当前的事务一起提交。当调用的目标服务时双向的请求时,这儿必须选择为commit,否则会造成死锁。
通过这三个部分的组合就可以实现细粒度的事务控制。可以看到SCA提供的事务描述比EJB的事务粒度更细。这样也就能对各个组件实现更为精确的事务控制。
下面具体给一个例子。
比如一个SCA组件有如下的事务需求:
在运行时候需要有全局事务但不能加入到已有的全局事务中
该组件需要调用到其他的服务,希望其他的服务也能加入到自己的事务中
那么在设计上:
接口的事务限定符应该设置为join transaction=false,这样就防止了该组件加入到上游的事务中。
实现上的事务限定符应该设置为transaction=global。这样SCA容器在调用该实现之前就会启动一个全局事务。
引用上的事务限定符应该设置为suspend transaction=false。这样组件实现的事务就被允许传播到被调用的服务。
3. 映射规则
在开始我们就提到WPS是建立在WAS上的,所以虽然这儿它提出了不同于J2EE的事务的限定符,但最终它还是会被映射到J2EE的事务上。这种映射如下表所示:
那么接下来的问题就是这种映射是如何发生的呢?
这个秘密就在自动生成的EJB部署描述符中。我们可以在WID里面做一个简单的SCA模块,Build后看自动生成的EJB项目,通过查看其EJB部署描述符可以看到在container-transaction部分有如下的一些方法的声明:
以下是引用片段:
|
<container-transaction>
<method>
<ejb-name>Module</ejb-name>
<method-intf>Local</method-intf>
<method-name>transactionNotSupportedActivitySessionNotSupported</method-name>
</method>
<method>
<ejb-name>Module</ejb-name>
<method-intf>Local</method-intf>
<method-name>transactionNotSupportedActivitySessionRequired</method-name>
</method>
<method>
<ejb-name>Module</ejb-name>
<method-intf>Local</method-intf>
<method-name>transactionNotSupportedActivitySessionSupports</method-name>
</method>
<trans-attribute>NotSupported</trans-attribute>
</container-transaction> |
根据SCA模块中事务限定符的配置的不同,你所能看到的方法也不太一样,但总共就只有九种组合。
到这大家基本上都能知道SCA的这些限定符是如何被映射到J2EE的世界里面的了。没错,这儿就是利用了WAS提供的容器管理器的事务(CMT)的方式来把SCA世界的事务和J2EE的事务联系在了一起。在运行的时候,SCA容器中的事务消息处理器首先根据消息所处的阶段,比如是在接口、实现还是在引用,查找到对应的事务限定符,然后发起一次EJB调用,调用的方法就是上述的这些方法。这些方法因为有确定的事务属性,所以EJB容器在接收到调用后就知道如何处理事务。在下面一小节中我们将通过一个实例来验证这儿的推断
4. 一个简单的实例
下面我们通过一个简单例子,通过真正的代码调用的轨迹来看是SCA的事务是如何运作的。
在上面的场景中,component1的事务需求是需要运行在全局事务中并且可以加入到已有的事务;component2则只能运行在自己的全局事务中;而component3则只能运行在本地事务中。具体配置如下:
Stand-alone Reference的引用上面设置的事务限定符Suspend
transaction为false。
Component1接口上的事务限定符Join transaction为true。
Component1实现上的事务限定符Transaction为Global。
Component1到Component2上的事务限定符Suspend
transaction为false。
Component1到Component3上的事务限定符Suspend
transaction为true。
Component2接口上的事务限定符Join transaction为false。
Component2实现上的事务限定符Transaction为Global。
Component3接口上的事务限定符Join transaction为false。
Component3实现上的事务限定符Transaction为Local。
前面通过一个JSP来调用这个stand-alone reference,在各个component的实现中打印当前的Thread.currentThread().getStackTrace()。可以看到如下的输出:
在component1实现中的 输出中我们可以看到如下的EJB方法被调用到.这表示了component1运行在一个全局事务中。
以下是引用片段:
transactionRequiredActivitySessionNotSupported
下面是component2实现中的输出。
以下是引用片段:
transactionRequiredActivitySessionNotSupported
transactionNotSupportedActivitySessionNotSupported
transactionRequiredActivitySessionNotSupported
可以看到有三次EJB调用,第一次是transactionRequiredActivitySessionNotSupported,这对应了对
component2引用上的事务限定符为suspend transaction=false,表示调用时候需要把当前事务传播到下游的服务中。第二次是
transactionNotSupportedActivitySessionNotSupported,表示挂起当前的事务,这对应了
component2接口上面的jointransaction为false,第三次是重新启动一个新的全局事务
transactionRequiredActivitySessionNotSupported,这对应了component2的实现上的事务限定符
transaction为global。
component3实现中的输出。
以下是引用片段:
transactionNotSupportedActivitySessionNotSupported
transactionRequiredActivitySessionNotSupported
这由于component1对component3引用上的事务限定符suspend
transaction为false,所以这儿没有EJB调用(因为WAS默认会自动在当前线程上传播事务)。然后由于接口上声明不参加上游事务,所以产生了一次EJB调用,调用的方法是transactionRequiredActivitySessionNotSupported,从而为该SCA组件建立了一个新的事务;但由于该组件实现需要运行在本地事务中,因此再次发生EJB调用,方法是
transactionNotSupportedActivitySessionNotSupported,在上面的EJB描述符中可以看到该方法对应的事务属性是NotSupported。
小结
至此,我们可以得出如下的结论:
1. WPS的SCA的事务实现时给予WAS的声明式事务。
2. WPS通过一系列的消息处理器来实现不同的事务需求。
由此我们可以得到当执行如下的服务调用时所经历的事务处理步骤为:
以下是引用片段:
Service service = ServiceManager.INSTANCE.locateService(referencename);
String result = (String) service.invoke("target
method name",parameters);
1. 调用端SCA容器调用负责处理事务的消息处理器,该消息处理器根据引用上面定义的事务限定符决定是否需要调用EJB方法来挂起事务。
2. 在结果一系列处理后消息被传递到服务提供端,然后SCA容器调用处理接口事务的消息处理器,消息处理器根据接口上定义的事务限定符决定是否要调用发起一次EJB调用来挂起当前的事务。
3. SCA容器接着调用负责处理服务实现的事务处理器,该处理器根据实现上配置的事务限定符决定是否调用EJB方法来启动一个本地事务。
|