1.
远程服务概念
顾名思义,远程服务是指可以通过远程通讯进行调用的服务。
在
SCA
中,在定义服务的时候可以将服务定义为远程服务,通过
@Remotable
标签加在服务接口的类名上,就将该服务接口定位为了远程服务。
SCA
规范中这样规定远程服务的:
“
Remotable services are services that can be published through entry points. Published services can be
accessed by clients outside of the module that contains the component that provides the service.
Whether a service is remotable is defined by the interface of the service. In the case of Java this is defined
by adding the @Remotable annotation to the Java interface. A service whose interface is defined by a Java
class is not remotable.
"
上面这段话主要意思是说,一个远程服务可以通过
Entry Points
进行发布,并且能够被该服务所在模块以外,但包含了该服务的组件所调用,并且,远程服务只能定义在
Java
接口类上,如果利用
Java
实现类(非
interface java
类)定义的服务进行
@Remotable
注释,这种远程服务是无效的。
可能上面的话有点太含糊其词,我们下面具体讲一下
SCA
远程服务的定义,以及
uxsca
容器实现该标准的简要实现手段。
2.
定义远程服务
根据上面我们所讲的,在一个已经被定义为服务的接口的接口类上加上注释
@Remotable
,这样就表示该
sca
服务将会是一个远程服务。就这么简单!
3
.
UxSCA
容器中远程服务的实现概要
的确,光从
sca
规范上来看,一个远程服务的定义是如此简单,但是具体怎么去使用以及使用场合
,SCA
规范中没有给出详细的示例,所以这里我也只能是根据远程服务定义去做实现了。
既然是远程服务,首先想到的实现手段就是
Web service
,当然,也可以用
Java
远程调用。
既然使用
Web service
,那在
Java
语言里,
Axis
是一个既简单又使用的
Web service
组件了。这里简单说一下
Axis
是如何实现
web service
的:
Axis
其实也是通过
servlet
来实现
web service
的,通过一个
HTTP
的访问,
Axis
的
servelet
会去获得这段
HTTP
的
Request
信息,
HTTP
协议上架着
SOAP
,
Axis
解析出
SOAP
后定位到具体的一个
Java
实现,然后在将该
Java
实现执行后的结果(或者没有返回结果)再构造成对应的
SOAP
返回回去。一般情况下,访问
Axis
的客户端是使用的
Axis
生成的客户端代码。
Axis
的
web service
实现大致如上所述,如有错误请读者指出。
Axis
创建
Web service
的方法一般有
2
种:
1 .
Axis
实现
Web service
可以通过
jws
文件来定位服务,也就是将一个写好的单独的
Java
类(
.java
文件)改后缀名为
.jws
,然后放到
Axis
的
webapp
下。访问的
URL
如下:
Http://webserveraddress/axis/services/SomeJws.jws
当我们通过上面的
url
去访问的时候,
Axis
会去查找编译好的
java
类,然后执行后返回结果。
这种方法很简单,但是很不实用。
2.
Axis
可以通过生成好的
wsdl
,利用
WSDL2Java
工具类产生
Axis
所需要的
wsdd
配置文件,并且生成对应客户端以及一些数据对象(
DO
)代码。然后通过
Axis
工具类生成一个
server-config.wsdd
文件,将原先生成好的
wsdd
文件中的
service
配置加入到其中,再生成编译好的
Java
类连同
server-config.wsdd
配置文件一同放到
Axis
的
webapp
目录下对应的位置,这样可以通过
Axis
生成的客户端去调用这个
web service
了。
UxSCA
是利用第二种方式去构建远程服务的。步骤如下:
UxSCA
本身是一个
Web
应用,它的
web.xml
中加入了
Axis
的
servlet
,这样就可以不去单独将
Axis
作为一个
Web
应用来获得
Web service
的请求了。而
UxSCA
在启动的时候(并不是
Web
服务容器启动的时候),会去解析在自己维护下的模块,从而获得各个模块的服务配置,然后:
1.
首先解析出远程服务接口类,以及其实现类。
2.
然后边历接口类中可以发布的接口方法(可发布的接口方法,必须是参数或者返回值都是简单类型或者是
SDO
类型),生成对应的
WSDL
文件。
3.
利用生成好的
WSDL
文件以及服务接口类再生成
server-config.wsdd
中的
service
元素片段,并加入到该文件中(如果
server-config.wsdd
不存在,
UxSCA
会自动生成)
在作完以上工作后,
UxSCA
会去自动启动
Web
服务容器,这样一来,远程调用就可以通过
uxsca
容器去访问远程服务了。
其中有几个问题存在:
1.
服务的查询是由
SCA
容器去管理的,外部具有远程服务的接口的模块组件怎么去获得非本地模块中的服务定义呢?
2.
Axis
目前只支持
Castor
和
XMLBean
或者
JavaBean
的复杂类型数据结构,如何让
Axis
支持
SDO
以及
JAXB
数据结构呢?
3.
如果外部模块获得了远程服务的接口,
SCA
容器在返回接口实现的时候却不会有一个真正的接口类实现,就是说,外
部模块只有一个
interface
类,没有实现类(废话),如何去返回一个能让其调用的类呢?
对于第一个问题,我本人没有想好,目前也只有一个不成熟的解决办法:
SCA
在部署的时候能够知道各个节点的位置,然后再统一启动,每一个节点启动解析完成后再通过网络通讯将解析出来的服务模型反馈给每一个接点,这样每一个接点上都会有其他接点的服务描述,于是在使用
ModuleContext
定位服务时候也就可以作到一致了。当然这只是我个人的想法,行不行得通还需要再去熟悉
SCA
规范后加上实践才能证明其合理性。
第二个问题相对简单一点,请看我的另一篇文章:让
Axis
支持
EMF
类型
第三个问题如下:
假设我们定义了一个TestInterface接口,并且有一个实现类,TestInterfaceImpl,现在我们的某一个客户端只拥有TestInterface这个接口类,而TestInterfaceImpl是在服务端的。所以在客户端中,是找不到有关
TestInterface
的实现的,所以这里使用了
Java
的动态接口代理。利用
Proxy
,在用户调用的时候,采用
Axis
的客户端代码访问
Web Service
。
4
.远程服务使用示例
先下载UxSCA包。
新的UxSCA是和Tomcat帮定在了一起,它作为一个Tomcat的webapp存在。在Tomcat启动的时候会去查询定义的Module以及定义的服务。当前UxSCA版本并不是完整的版本,只是为了测试远程服务而做的,在以后会完善改进。
下载地址(压缩包分成了三份):
Balto1
Balto2
Balto3
准备环境:Eclipse-WTP 1.0
先将下载好的带有UxSCA的Tomcat释放到某个目录下。
选择Eclipse的首选项(Preferences...),增加一个Server Runtime,路径指定到下栽好的Tomcat:
然后在webapps中找到一个balto.war,并倒入到Eclipse工程中。
如果读者不是在WTP下调试,则不需要倒入。
然后新建立一个Java工程,命名为RemoteProject。
在该工程下建立一个XSD文件:Element.xsd,
这个Schema比较简单,只有一个元素:Element,Element有两个属性:name,age:
<?
xml version="1.0" encoding="UTF-8"
?>
<
schema
xmlns
="http://www.w3.org/2001/XMLSchema"
targetNamespace
="http://www.example.org/Element"
xmlns:tns
="http://www.example.org/Element"
>
<
element
name
="Element"
>
<
complexType
>
<
attribute
name
="name"
type
="string"
></
attribute
>
<
attribute
name
="age"
type
="int"
></
attribute
>
</
complexType
>
</
element
>
</
schema
>
选择这个Schema点击右键,选择新建一个EMF Model:
然后根据向导生成一个Element.genmodel文件,打开这个文件后会出现一个编辑界面,选中根元素后点右键,选择:Set SDO Defaults:
然后选择Generate Model Code,完成后我们发现在src下多出了一堆代码,这就是SDO模型代码:
然后修改一下生成的代码:打开生成的ElementPackageImpl类,找到createExtendedMetaDataAnnotations方法,将
addAnnotation
(elementTypeEClass,
source,
new
String[] {
"
name
"
,
"
Element_._type
"
,
"
kind
"
,
"
empty
"
});
该成:
addAnnotation
(elementTypeEClass,
source,
new
String[] {
"
name
"
,
"
Element
"
,
"
kind
"
,
"
empty
"
});
改这段代码的目的是为了让EMF序列化模型的时候生成的XML片段准确一点,也就是Element_._type元素名要改成Element.
这里需要注意,如果我们生成SDO代码是由Ecore生成的,就不会有上述的问题,如果是以XSD生成的就会出现这种情况。
然后我们还需要改一些其他代码,找到initializePackageContents方法:
initEClass(documentRootEClass, DocumentRoot.
class
,
"
DocumentRoot
"
,
!
IS_ABSTRACT,
!
IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS);
.....
initEClass(elementTypeEClass, ElementType.class, "ElementType", !IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS);
将上面代码修改为:
initEClass(documentRootEClass, DocumentRootImpl.
class
,
"
DocumentRoot
"
,
!
IS_ABSTRACT,
!
IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS);
.....
initEClass(elementTypeEClass, ElementTypeImpl.class, "ElementType", !IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS);
改这段代码的目的是为了让UxSCA能认出我们的复杂类型是一个SDO模型。因为在UxSCA在遇到EMF模型时候,会去查看这个
EClass的instance类是否是一个SDO模型,但是在EMF生成的代码中,EClass的instance类是一个接口类,而且该接口类没有
实现EObject以及DataObject。当然,这是UxSCA的一个局限性,在以后的版本中我会改进的。
现在我们已经生成了我们的数据对象,在该工程下建立一个接口:MyRemotableService,该接口具有两个方法:getElement和
getName,getElement返回的是我们刚才生成的ElementTypeImpl对象,getName返回的是一个String。然后我们将这个类
利用SCA的Annotation定义为一个Remotable:
package
org.uxteam.sca.example;
import
org.example.element.impl.ElementTypeImpl;
import
org.osoa.sca.annotations.Remotable;
@Remotable
public
interface
MyRemotableService {
ElementTypeImpl getElement();
String getName();
}
然后我们创建一个类,让这个类实现MyRemotableService接口,并且将它定义成一个Service:
@Service(MyRemotableService.
class
)
public
class
MyRemotableServiceImpl
implements
MyRemotableService {
/*
(non-Javadoc)
* @see org.uxteam.sca.example.MyRemotableService#getElement()
*/
public
ElementTypeImpl getElement() {
ElementTypeImpl element
=
(ElementTypeImpl) ElementFactory.eINSTANCE.createElementType();
element.setAge(
10
);
element.setName(
"
Element Name
"
);
return
element;
}
/*
(non-Javadoc)
* @see org.uxteam.sca.example.MyRemotableService#getName()
*/
public
String getName() {
return
"
This is a remotable service
"
;
}
}
现在我们完成了远程服务的定义了。
然后我们将RemoteProject中代码进行打包。先选种src文件夹,然后点右键,选择Export,然后选JarFile,根据提示即可完成打包。我们将这个命名为Test.jar
现在打开Tomcat的webapps,会发现一个名为balto的web项目(UxSCA以后的代号就为balto),
打开文件夹后会发现一个名位sca-config.xml文件,打开文件,加入这么一段代码:
<
property name
=
"
entries
"
>
<
values
>
<
value type="jar" name
=
"Test.jar"/ >
</values>
</property>
这段代码的是让UxSCA找到我们需要加入到SCA容器管理的包的入口,value的名是包名。
现在我们通过外部的的应用程序去访问这个服务。
首先,我们需要将这个服务的接口类MyRemotableService以及SDO的数据模型打包,也就是说要给访问端工程数据以及服务的接口(这里我们不要把MyRemotableService的实现类打进去)。当然,可以直接通过访问web 服务的方法访问,这里这么做只是为了测试UxSCA容器定位服务的功能。
然后我们再新建一个web项目,TestProject,将刚打好的包放到其中。并在这个项目下创建一个Servlet
在doGET中写如以下代码:
protected
void
doGet(HttpServletRequest request,
HttpServletResponse response)
throws
ServletException, IOException {
ModuleContext context
=
CurrentModuleContext.getContext();
MyRemotableService service
=
(MyRemotableService) context
.locateService(
"
MyRemotableService
"
);
ElementTypeImpl obj
=
service.getElement();
String name
=
obj.getName();
System.out.println(name);
System.out.println(obj.getName());
System.out.println(obj.getAge());
}
然后我们在Eclipse的Servers视图下建立一个server:并把我们刚才新建的TestProject和倒入的balto加入进去:
然后启动Tomcat,在Console上我们会发现这么一段话:
解析Java Annotation 得到一个Service - org.uxteam.sca.example.MyRemotableServiceImpl
解析Java Annotation得到一个Service的Java实现 - Service : MyRemotableService JavaImplemention : org.uxteam.sca.example.MyRemotableServiceImpl
为远程服务MyRemotableService创建WSDL文件.
远程服务MyRemotableServiceWSDL文件创建完成
注册远程服务到Axis wsdd文件中MyRemotableService
这就说明我们的服务已经被发现,并且UxSCA已经将该服务发布成了远程服务。
现在我们在浏览器中写访问这个服务:
http://localhost:8080/balto/services/MyRemotableService
如果看到上面的这个界面,就说明MyRemotableService已经通过Axis被注册成为一个Web Service了。
现在在浏览器中输入:http://localhost:8080/TestProject/TestServlet
得到以下结果:
5。总结
SCA的远程服务是衔接模块和模块的重要元素,它使得模块和模块之间有了交互。当远程服务配合上Scope以及回掉接口(异步时候的接口),将会发挥巨大的威力。
在该稿发布之时,我的UxSCA中远程服务部分在处理复杂类型数据的时候出现了一些问题,所以目前还只能是定义单个元素的复杂类型(不能使用数组),并且调用方法必须是无参数的。希望不是什么大问题,我会及时改正。