本文共 12795 字,大约阅读时间需要 42 分钟。
错误处理
调用服务方法的客户端会遇到业务逻辑的异常或SCA的运行期异常。
业务逻辑异常是被调用的服务方法的实现抛出的,是在服务对应的接口签名中定义了的checked exception。
SCA运行期异常是SCA在运行期抛出的, 代表在管理组件执行时以及跟远程服务交互时所遇到的问题。目前以定义的SCA运行时异常包括:
ServiceRuntimeException – 代表在管理SCA组件执行的时候遇到的问题。
ServiceUnavailableException – 代表在与远程服务交互期间遇到的问题。它继承自ServiceRuntimeException. 这些异常可能是暂时的,所以可以重试。任何ServiceRuntimeException但不是
ServiceUnavailableException的异常都不大可能通过重试来解决,因为这一般都需要人工干预才能解决。
异步编程
异步编程指的是客户端调用了一个服务后继续执行而不必等待服务执行完毕。一般地,被调用的服务在某段时间后才执行。如果被调用的服务有输出的话,那么必须通过一个独立的机制来返回,因为在调用的时间点上是没有任何可用的输出的。这于“调用-返回”的同步编程方式不同,那种方式在客户端继续执行前前会执行服务并返回所有的输出给客户端。SCA异步编程模型由非阻塞调用,会话服务、回调这几个支撑服务组成。以下将分别讨论这几个主题。
非阻塞调用
非阻塞调用是最简单的异步编程的形式。服务的客户端在调用服务后立刻继续执行而不必等待服务的执行。任何返回void并且没有声明任何异常的方法可以用@OneWay标签来标记。这表示该方法是非阻塞的,并且与服务提供者的通讯的绑定实现可以对请求进行缓冲然后在以后某个时间才发送。
SCA目前对于有返回值或者声明抛出异常的方法没有定义相应的机制。建议服务的设计者尽可能的将设计单向的方法,以给部署人员最大程度的绑定的灵活性。
会话服务
在远程服务的执行期间,一个最频繁的模式就是在服务的客户端和服务的提供者间启动一个会话。会话是由一系列跟某个共同的主题相关的方法的调用组成。 例如,一个对话可能是一次银行贷款请求所需的一系列的服务调用。
传统上,程序员一般需要编写大量的代码来支持这种模式。他们必须使用一个唯一的键值做为会话ID来标记本次会话,例如贷款ID。他们必须在会话中的任何消息中传递这个会话ID。这种服务实现方式必须保证在每次操作结束的时候都保存会话的状态、使用会话ID作为键值、在下一个操作开始的时候查找本次会话的状态。如果开发人员开发的是群集环境下应用,那么为了应用的性能表现良好,则他们必须知道如何将会话请求路由到的会话状态已经存在于内存中的那个群集节点。
SCA则使得设计会话服务成为可能,将ID生成、状态管理和路由等细节留给SCA容器来实现。在SCA中,用一个session来维护客户端和远程服务间的一个会话的信息。如果一个服务的接口的范围的值为session, 那么会话的ID(session ID)不需要显式地生成或作为消息体的一部分传递,实现该服务的组件会在它的字段中存储会话的信息。
注意,有一个session标识的一次会话总是一个远程的服务。当一个远程服务有一个session 的范围时,则一个session代表与之交互的客户端的一个会话。但是如果一个本地服务有一个session的范围,那么它表示的则是该本地服务与调用栈中的最后一个远程服务的客户端,而不是与这个本地服务的客户端之间的会话。
RService1注入了对LService1.1和LService1.2的引用。LService1.2对LService1.1的引用和Rservice1对LService1.1的引用会共享一个实例。LService1.2对RService2的引用则会启动一个新的会话,LService1.1 对RService3的引用也是如此。
某些时候,在调用本地服务的时候,调用栈中却没有远程服务,比如说从JSP中调用本地服务。在这些情况下,会话总是存在的,不过它的语义由运行期环境来决定。运行期环境如何启动和结束一个会话将会在未来的版本中进行标准化。在JSP情况下,一个会话会绑定到一个HTTP会话。在同场的情况下,一个会话将一直持续知道客户端调用了一个标记由@EndSession 标签的操作(或带有@EndSession 标签的回调方法)。客户端可以将服务的引用对象保存在数据库中,在以后取出这个对象继续使用它。通常,可以通过将服务的引用放在应用架构的持久层对象的字段中来进行保存。 (例如 EJB3, JDO 或 Hibernate)。不过,会话可能有超时设置,超时后使用该会话会导致一个SessionEndedException异常。一个会话也可以调用服务引用的endSession() 方法来结束。
以下为标记成会话的接口的例子
conversational:package com.bigbank;import org.osoa.sca.annotations.Scope;import org.osoa.sca.annotations.Remotable;@Scope("session")@Remotablepublic interface LoanService { public void apply(LoanApplication application);public void lockCurrentRate(int termInYears);public void cancelApplication();public String getLoanStatus();} |
有会话客户端
在有会话的服务的客户端,无需特别的编码。客户端开发人员通过接口的session 范围值能知道这是一个有会话的服务。以下上述有会话服务的一个客户端的例子:
package com.independent;import com.bigbank.LoanService;import org.osoa.sca.annotations.Remotable;@Remotablepublic class BrokerImpl implements MortgageBroker { @ReferenceLoanService loanService;// Known to be conversational because the interface is conversational.public void applyForMortgage(Customer customer, HouseInfo houseInfo, int term){ LoanApplication loanApp;loanApp = createApplication(customer, houseInfo);loanService.apply(loanApp);loanService.lockCurrentRate(term);}public boolean isApproved() { return loanService.getLoanStatus().equals("approved");}public LoanApplication createApplication(Customer customer, HouseInfohouseInfo) { return …;}} |
有会话服务的提供者也不必编写任何特殊的代码来是服务成为有会话的。一个服务是否是有会话的是通过让服务实现一个session范围的远程接口来决定的。服务的开发者知道他们可以将跟会话有关的数据保存在字段中,并且这些数据在整个会话期间都是有效的。
对于服务的实现,要求具备有几种能力,但不是必须的。如果字段有@SessionID标签, 那么会话ID应保存在这个字段。
@SessionID
private String mySessionID;该字段的类型不一定非要是String。系统生成的会话ID总是字符串,但是应用生成的会话ID可以是其他复杂类型,这在"应用指定的会话ID "一节中有说明。
服务的实现类也可以有一个以下形式的@Session标签:
@Session(maxIdleTime="10 minutes",
maxAge="1 day", singlePrincipal=false)[注意: 每种范围也可以使用自定义的标签来取代普通的@Scope标签,比如将@Scope("session") 换成@SessionScope(maxAge="1 day")。属性的值也可以通过policy机制进行重载。]
该标签的属性有如下含义:
两个以时间为值的属性的取值为,一个整数接一个空格和"seconds", "minutes", "hours", "days" 或 "years"中任意一个的字符串。
如果没有设置超时,则超时的实现将完全由SCA运行期的实现来定义。
以下是一个有会话服务的实现的例子,其中剔除了业务逻辑代码:
package com.bigbank;import org.osoa.sca.annotations.Session;import org.osoa.sca.annotations.SessionID;@Session(maxAge="30 days");public class LoanServiceImpl implements LoanService { @SessionID private String loanID;public void apply(LoanApplication application) { … }public void lockCurrentRate(int termInYears) { … }public void cancelApplication() { … }public String getLoanStatus() { … }} |
一个session范围的远程服务的方法可以打上@EndSession标签。这意味这一旦该方法被调用,则不能继续调用该会话中的方法以便客户端和服务能释放与会话相关的各种资源。也可以在回调接口的方法上打上@EndSession标签(后面会有说明), 让服务提供者可以决定是否结束一个会话。如果在会话结束后调用方法,那么将有一个SessionEndedException (继承自ServiceRuntimeException) 异常抛出。如果当客户端和服务提供者分别调用各自带有@EndSession标签的方法时出现了竞争条件,也会抛出这种异常。
将有会话的服务作为参数传递
表示一个会话的服务的引用可以作为参数传递给其他服务,甚至是远程服务。这是为了让一个组件继续另一个组件所启动的会话。目前,服务的引用仅能在同一个模块中服务间传递。
服务提供者也可以创建一个指向自己的服务引用传递给其他服务。服务实现通过调用MethodContext.createServiceReferenceForSession(this)来实现。
interface ModuleContext { …ServiceReference createServiceReferenceForSession(Object self);ServiceReference createServiceReferenceForSession(Object self, StringServiceName);} |
当一个组件实现了多个服务的时候,必须使用第二个额外的参数serviceName。
这是为了支持复杂的回调模式,例如某个回调仅使用一个大的会话的一个子集。简单的回调模式有内建的回调支持。
关于会话声明周期的总结
会话的开始
会话的继续
在以下时刻,一个会话结束并释放所有相关的状态:
服务器的会话超时。
客户端调用ServiceReference.endSession().
如果在带有@EndSession标签的方法调用后又调用了该服务的引用的方法,则会启动一个新的服务。如果在带有@EndSession标签的方法调用后而新的会话尚未启动前调用ServiceReference.getSessionID() 方法,则该方法会返回null。
如果一个服务的引用在服务提供者的会话超时后继续使用将导致会话结束,同时抛出SessionEndedException异常。为了在一个新的会话中使用这个服务的引用,则必须调用它的endSession() 方法。
应用指定的会话ID
在有会话服务的状态管理中也可以使用自定义的会话ID。客户端此时不能使用注入,而要调用API newSession(),将session ID作为参数 。newSession() 方法返回一个如下形式的有会话服务的引用:
public interface ModuleContext { …ServiceReference newSession(String serviceName);ServiceReference newSession(String serviceName, Object sessionID);} |
该方法返回的服务引用的对象可以被强制类型转化成serviceName所指定的服务对应的业务接口。
该方法传入的sessionID参数既可以是一个String 也可是是一个序列化成XML的对象,例如SDO 2.0 [1] DataObject. ID的取值对于客户端组件来说必须在所有时间都是唯一的。如果客户端不是SCA组件,则ID的取值必须是全局唯一的。
并不是所有的有会话服务的绑定都支持应用指定的会话ID或仅支持String类型的应用指定会话ID。有会话服务的WS-Addressing绑定实现将会话ID序列化成XML作为引用的参数,见"会话和回调的绑定"一节。
从客户端取得会话ID
不管会话的ID是由用户自定义的还是由系统生成的,客户端都可以调用服务引用上的ServiceReference.getSessionID()方法来取得某次会话的ID值。一般说来,服务的引用是放在字段里保存,字段的类型为该服务的业务接口的类型。如果是这样,那么就需要将该字段的类型强制类型转换成ServiceReference来访问它的getSessionID 方法。
如果会话ID不是由应用指定的,那么ServiceReference.getSessionID()方法仅保证在第一次调用操作后才返回一个有效的值,否则将返回null。
回调跟同步操作通过返回值来通讯不同,一个回调服务是用于从一个服务提供者到它的客户端的异步通讯。
回调是用在双向服务中, 这类服务要有两个接口:
本地服务和远程服务均可使用回调。回调使用的两个接口必须都是远程或都是本地的,否则则是非法的。回调的形式有两种:无状态的回调和有状态的回调。
回调的接口是通过在远程服务接口上使用@Callback标签来声明的,并且有一个实现了该接口的Java类对象作为参数。该标签也可以用在实现的一个方法或一个字段上,用于注入一个回调,这会在下节中进行说明。
以下是一个@CallBack标签来声明一个回调接口的例子:
package somepackage;import org.osoa.sca.annotations.Callback;import org.osoa.sca.annotations.Remotable;@Remotable@Callback(MyServiceCallback.class)public interface MyService { public void someMethod(String arg);}@Remotablepublic interface MyServiceCallback { public void receiveResult(String result);} |
跟这个例子相关的构件类型定义如下:
callbackInterface="somepackage.MyServiceCallback"/> |
一个有状态的回调代表一个服务的特定的客户端组件实现。这个有状态的接口的范围为session 或 module。如果一个回调接口是有状态的,那么回调接口对应的服务则必须是无状态的或者必须与回调的接口范围一致。这是为了预防在会话中将session范围的服务跟module范围的服务混在一起所可能导致的令人混淆的情况。
以下是使用@Callback标签请求注入有状态回调的的一个服务实现(包括上述服务和回调)的例子。在这里,它只是简单将请求传递给其他组件,实际上就是充当一个中介。由于服务的范围是session,当后端服务发回它的异步应答的时候,回调接口依然是有效。。
package somepackage;import org.osoa.sca.annotations.Callback;import org.osoa.sca.annotations.Reference;public class MyServiceImpl implements MyService, MyServiceCallback { @Callbackprivate MyServiceCallback callback;@Referenceprivate MyService backendService;public void someMethod(String arg) { backendService.someMethod(arg);}public void receiveResult(String result) { callback.receiveResult(result);}} |
该组件实现了两个服务,一个用来为客户端提供服务 (MyService) ,一个用于后端的回调 (MyServiceCallback)。该服务的客户端也应该实现MyServiceCallback。
package somepackage;import org.osoa.sca.annotations.Reference;public class SomeClientImpl implements AnotherService, MyServiceCallback { @Referenceprivate MyService myService;public void aClientMethod() { ...myService.someMethod(arg);}public void receiveResult(String result) { // code to process the result}} |
有状态回调也支持跟其他那些将服务引用作为参数的操作同样的用例。主要的差别在于有状态的回调不需要为服务操作传递额外的参数。这个特性是非常方便的。因为若一个服务有多个操作,而其中的任何一个操作都可以是会话的第一个操作,那么为了让任何一个操作都能开始一次会话,而让每个操作都必须接受一个回调的引用作参数的会非常不方便。让应用开发者显式地去调用一次传递回调对象的操作,这样的方式也显得更自然些。
无状态的回调一个无状态的回调接口是一个接口范围为stateless的回调接口。跟无状态的服务不同,使用无状态回调的客户端所调用的回调方法不会被路由到包含会话相关信息的客户端。所以,该客户端需要自己负责任何持久化状态的管理。客户端唯一需要处理的信息(除了回调方法的参数以外)仅是一个传给服务的回调ID对象,该对象每次回调时都一定会被返回。
以下代码跟上面的客户端代码相似,区别在于假定MyServiceCallback是无状态的。在这个例子中,客户端需要在调用服务前设置回调ID,并在收到应答时去获取该回调ID。
package somepackage;import org.osoa.sca.annotations.Reference;public class SomeClientImpl implements AnotherService, MyServiceCallback { @Referenceprivate MyService myService;public void aClientMethod() { String someKey = "1234";...((ServiceReference)myService).setCallbackID(someKey);myService.someMethod(arg);}public void receiveResult(String result) { Object key = ((ServiceReference)myService).getCallbackID();// Lookup any relevant state based on "key"// code to process the result}} |
跟有状态回调一样,服务的实现也是用@CallBack标签标记一个字段或setter方法来得到对回调对象的访问,例如:
package somepackage;import org.osoa.sca.annotations.Callback;public class MyServiceImpl implements MyService { @Callbackprivate MyServiceCallback callback;…} |
而区别则在于,若该组件处理的请求是来自原有客户端以外的另一个客户端,那么这个存放回调的字段是无效的。所以,上节中所用到的用backendService保存MyService的回调的技巧在这里是不能用的,因为在后端系统发来的消息中存放回调的字段会是null。
实现多个双向接口
单个的class可以实现多个服务,所以它所实现的每个服务都能定义回调。在服务的实现中,可以对每个回调都定义一个用来注入的字段。运行期将根据回调的类型给每个字段注入对应的回调。以下是一个提供了两个服务的组件的例子,每个服务都有一个对应的回调。
package somepackage;import org.osoa.sca.annotations.Callback;public class MyServiceImpl implements MyService1, MyService2 { @Callbackprivate MyService1Callback callback1;@Callbackprivate MyService2Callback callback2;} |
如果一个回调的类型可以兼容多个回调字段的类型声明,那么所有的字段都会被赋值。
在RequestContext上访问回调
除了使用带标签的字段访问回调对象外,一个无状态的服务也可以通过RequestContext对象访问最后一次请求所关联的回调。
RequestContext 不能直接访问回调对象,不过它有个getServiceReference方法。该方法返回的服务引用请求所调用的服务的引用。一个服务实现可以通过调用RequestContext.getServiceReference().getCallback()获取请求所对应的回调对象。服务的实现试图调用该服务引用上的setCallback() 方法则是非法的。
通过这种方法得到的回调可以强制类型转换成双向服务接口的回调接口。在服务提供者一端,回调对象保存有必须回传给客户端的引用参数或其他状态信息以便客户端有足够的上下文信息。getService的定义类似于:
package org.osoa.sca;public interface RequestContext { …ServiceReference getServiceReference();} |
使用请求上下文来取得回调的服务实现类似于: package com.bigbank;
import org.osoa.sca.annotations.Context;public class MyServiceImpl implements MyService { @Context;private ModuleContext moduleContext;public void someMethod() { RequestContext rc = moduleContext.getRequestContext();MyCallback callback = (MyCallback)rc.getServiceReference().getCallback();…callback.receiveResult(theResult);}} |
public class CallbackImpl implements MyCallback { @Context;private ModuleContext moduleContext;void receiveResult(Object theResult) { RequestContext rc = moduleContext.getRequestContext();Object refParams = rc.getServiceReference().getCallbackID();…}} |
客户端调用getServiceReference()所返回的对象是发送此次请求的服务的引用。调用getCallbackID()返回的是此次回调的键值,可能是一个String,也可能是表示引用参数的一个对象。
自定义回调
双向服务的回调服务默认是服务的客户端组件。但是,可以通过ServiceReference.setCallback()来改变回调。作为回调传递的对象应该实现回调所定义的接口,以及接口上的附加SCA语义,如接口的范围和是否是远程的,等等。
由于可以使用除客户端以外的服务来作为回调的实现,所以如果一个客户端没有实现某一个引用的回调接口,SCA并不会在部署期抛出错误。若调用了一个并未通过setCallback()指定的引用上的方法,则客户端将抛出NoRegisteredCallbackException。
有状态接口的回调对象有一个额外的要求,就是它必须是可序列化的。SCA运行期可能序列化并存储该对象。
一个回调对象可以是其他服务的一个服务引用。此时,回调的消息将被直接传递给回调所引用的服务。如果一个回调对象不是一个服务的引用,则回调的消息则先发送至客户端,然后再路由给已注册的特定回调对象实例。然而,对于无状态的回调接口,回调对象必须是一个服务的引用。
自定义回调主键
用来标识一次回调请求的主键默认是由系统生成。不过也可以通过调用ServiceReference.setCallbackID()来为回调设置应用自定义的主键。这对有状态和无状态的回调均适用。该主键会被传给服务提供者,绑定必须保证服务提供者调用任何回调方法时都会传回该ID。
回调的主键有着和会话ID同样的约束。它必须是一个String或者是可以被序列化成XML,比如SDO 2.0 [1] DataObject。当使用WS-Addressing的会话绑定时,回调的ID表示做为reply-to地址的endpoint地址中的引用参数。不过其他绑定可以使用其他的机制。
会话和对象的绑定
不同类型的绑定可以有很多种表示会话ID的方式。举例说,在可靠消息中,可以使用WS-RM主键序列作为会话ID。WS-Eventing使用的则是另外一种技术 (wse:头部主键).。也有专门为此用途创立的一个普遍意义的机制WS-Context OASIS TC。
WS-Addressing可以作为会话ID,不过只能用在回调中。WS-Addressing没有定义客户端如何使用一个主键标识整个会话的方式。
SCA编程模型支持会话,但把如何表示ID留给绑定实现。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/12639375/viewspace-151147/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/12639375/viewspace-151147/