Server Java开发--Born for SOA系列 前言
在这一章里面,我们将用大量的篇幅,阐述ArcGIS Server对SOA的支持,我们在这里所指的SOA,是基于Web Serivces的SOA,其他架构的SOA不在我们这个系列的讨论之列。本章内容包括Web Service基础;如何在J2EE下环境下构建和部署Web Service;如何使用ArcGIS Server 构建 Web Service;如何在SOA系统中利用这些Web Service等。最后会以一个实际的例子为例,如何在利用ArcGIS Server编写Web Service,并且部署到SOA环境中,解决实际中的问题。本章所有的例子都是基于JDK 1.5,AXIS2,ArcGIS Server 9.2 JAVA ADF开发。本章也是CJ编写的ESRI内部教材Server Java开发教程的第十五章。
Server Java开发--Born for SOA系列 Web Service基础
Web Service是描述一些操作(利用标准化的 XML 消息传递机制可以通过网络访问这些操作)的接口。Web 服务是用标准的、规范的XML 概念描述的,称为 Web 服务的服务描述。这一描述囊括了与服务交互需要的全部细节,包括消息格式(详细描述操作)、传输协议和位置。该接口隐藏了实现服务的细节,允许独立于实现服务基于的硬件或软件平台和编写服务所用的编程语言使用服务。这允许并支持基于 Web 服务的应用程序成为松散耦合、面向组件和跨技术实现。Web 服务执行一项特定的任务或一组任务。Web 服务可以单独或同其它 Web服务一起用于实现复杂的功能或商业交易。
听起来很复杂,但是我们用一个简单的例子加以说明。这个例子跟GIS无关,但是可以帮助大家了解在J2EE环境中,整个Web Service的编写,部署过程。并且如何在.Net环境和Java环境中调用这个Web Service。
以下的需求来自于一个我原来导师的项目,这虽然不是一个典型的WebService充满前景的一个例子,但是确实用web service来解决问题的一个好的开始。项目的背景是要帮用户部署多个网站,为了节约成本,两个网站都是使用租用的空间,结果有一个租用的Linux操作系统的空间没有安装XWindows,导致一个使用JAVA写的图像缩放程序无法执行,当然,没有Xwindows,基本上需要访问AWT包的程序都会无法执行。怎么解决呢,刚好另外一台服务器上有Xwindows,可以使用这个功能,所以我们可以在另外一台服务器上编写一个WebService,发送一个图像Resize请求,请求参数是被需要被Resize的图片的URL,以及Resize的比例,而返回的结果是Resize后的图像。压缩图像的代码如下所示:
package edu.zju;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import javax.imageio.ImageIO;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
*
* @author CJ
*
*/
public class CompressLocal {
publicCompressLocal() {
super();
}
/**
*
* @param filename 要被缩放的文件名,如果是URL,请设置isURL为true
* @param newname 缩放后的新的文件名
* @param std_img_w 目标文件的宽度
* @param std_img_h 目标文件的高度
* @param stretch 是否拉伸,如果1,则按照参数比例,如果是2,则按照宽度的比例;如果3,则取小的那个比例尺
* @param isURL 是否是URL
*/
public void doScaleOnImage(String filename,String newname,int std_img_w,intstd_img_h,int stretch,boolean isURL)
{
BufferedImageoldimage=null;
try{
intnew_w=-1;int new_h=-1;
if(isURL){
InputStreamin=(new URL(filename)).openStream();
oldimage= ImageIO.read(in);
in.close();
}else{
oldimage = javax.imageio.ImageIO.read(newjava.io.File(filename));
}
intold_w = oldimage.getWidth(null); //原图像的宽度
intold_h = oldimage.getHeight(null);//原图像的高度
if(stretch==1){
new_w=std_img_w;
new_h=std_img_h;
}elseif(stretch==2){
if((old_w > std_img_w) || (old_h > std_img_h)) {
doubletagSize = (double)old_w / std_img_w; // 按宽度比例确定缩放比例
new_w= (int)(old_w / tagSize); //得到一个新的比例
new_h= (int)(old_h / tagSize);
}
}
else if(stretch==3){
new_w= old_w;
new_h = old_h;
if((old_w > std_img_w) || (old_h > std_img_h)) { //如果原图像高度和宽度都大于目标高度和宽度
floattmpdouble = std_img_w;
floattagSize = old_w / tmpdouble; // 按宽度比例确定缩放比例
tmpdouble= std_img_h;
if(tagSize < (old_h / tmpdouble)) //按高度确定缩放比例
tagSize= old_h / tmpdouble;
new_w= Math.round(old_w / tagSize); //得到一个新的比例
new_h= Math.round(old_h / tagSize);
}
}
BufferedImagetag = new BufferedImage(new_w,new_h,BufferedImage.TYPE_INT_RGB);
tag.getGraphics().drawImage(oldimage,0,0,new_w,new_h,null);
oldimage.flush();
FileOutputStreamnewfile = new FileOutputStream(newname);
JPEGImageEncoderencoder = JPEGCodec.createJPEGEncoder(newfile);
encoder.encode(tag);
tag.flush();
newfile.close();
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param args
*/
public static void main(String[] args) {
CompressLocalcl = new CompressLocal();
cl.doScaleOnImage("D:\\temp\\photo\\0016.jpg","c:\\temp\\cj.jpg", 535, 312,2,false);
}
}
缩放图片的代码已经完成,下面来写一个方法,如何调用图片缩放代码: package edu.zju;
/**
* 作为一个application scope的web service,供大家使用
*
* @author CJ
*
*/
public class CompressWebService {
publicCompressWebService() {
super();
}
/**
*
* @param url 源jpg文件的URL
* @param width 目标jpg文件的宽度
* @param length 目标jpg文件的高度
* @return 返回目标jpg文件的url
*/
publicString doCompress(String url,int width,int height){
StringrltURL="c:/temp/aa.jpg"; //例子程序使用,正式代码根据url生成文件名
CompressLocalcomp=new CompressLocal();
comp.doScaleOnImage(url,rltURL,width,height,1,true);
returnrltURL;
}
/**
* @param args
*/
public static void main(String[] args) {
}
}
代码编写完毕后,可以直接部署了,由于有了AXIS,部署变得非常的简单,我们使用下面的service.xml进行部署,具体的部署方法可以参考AXIS的部署文档:
<service name="CompressService"scope="application">
<description>
ScaleJPEG Service
</description>
<messageReceivers>
<messageReceiver
mep="http://www.w3.org/2004/08/wsdl/in-only"
class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
<messageReceiver
mep="http://www.w3.org/2004/08/wsdl/in-out"
class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
<parameter name="ServiceClass">
edu.zju.CompressWebService
</parameter>
</service>
部署完成后,可以使用下面的URL对WebService进行访问。
http://chunjie:8088/axis2/services/CompressService/doCompress?url=http://chunjie:8088/jpg/10/deans/0037.JPG&width=55&height=55
返回结果为
<ns:doCompressResponse xmlns:ns="http://zju.edu/xsd">
<ns:return>c:/temp/aa.jpg</ns:return>
</ns:doCompressResponse>
如果访问http://chunjie:8088/axis2/services/CompressService?wsdl,可以得到描述当前WebService的wsdl文件。第一个WebService就部署完成了,是不是简单的不可思议?当然,得益于axis的伟大工作,隐藏了大量的细节,从而使工作变得如此简单。
刚才我们直接通过URL已经可以访问webservice了。但是我们一般在javacode里面访问,访问后还需要做一些其他的操作,比如马上把图片文件保存到服务器上的某个目录中。如果需要在Javacode中访问该web service,我们需要根据wsdl文件生成一个本地的Java代理类,通过这个代理类用来访问WebService的方法。Axis提供了wsdl2java的工具可以根据wsdl的描述生成java代理类,可以使用命令行的工具,或者使用Axis为Eclipse提供的工具生成代理类。生成代理后,就可以用下面的方法调用该WebService:
try {
DoCompress dc = newDoCompress();
dc.setUrl("http://chunjie:8088/jspsamples/jpg/thanks.jpg");
dc.setHeight(100);
dc.setWidth(100);
CompressWebServiceCompressWebServiceSOAP11PortStubstub = new CompressWebServiceCompressWebServiceSOAP11PortStub(
null,"http://chunjie:8088/axis2/services/CompressService");
DoCompressResponsedcr=stub.doCompress(dc);
System.out.println(dcr.get_return());
} catch (AxisFault e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
当然,我们也可以用.Net来调用这个WebService,这正是Web Service的妙处所在,在VisualStudio里面新建一个项目,然后添加Web引用,在引用地址里面填入service的URL,入下图所示:
[attach]4122[/attach]
添加引用后,就可以用下面的代码来进行访问:
consumeWS.chunjie.doCompressdc = new consumeWS.chunjie.doCompress();
dc.url = "http://chunjie:8088/jspsamples/jpg/thanks.jpg";
dc.height = 160;
dc.width = 120;
consumeWS.chunjie.CompressServicecs = new consumeWS.chunjie.CompressService();
consumeWS.chunjie.doCompressResponsedr= cs.doCompress(dc);
现在我们已经知道如何编写,部署,在JAVA和.NET中调用WebService。在下一节中,我们来看看可以通过怎样的方式对ArcGIS Server提供的WebService的进行访问。
Server Java开发--Born for SOA系列-- ADF中访问Web Services的类
事实上细心的您肯定已经发现了,在ADF的类库中,很多类在两个Package中都会有,比如我们曾经在前面的例子中提到的类:
MapDescription
在9.2以前的服务中,此类都是老老实实的呆在com.esri.arcgis.carto.MapDescription包中,但是在9.2以后,有了一个新的package,此package就是大名鼎鼎的com.esri.arcgisws。这两个类是不同之处在于:com.esri.arcgis.carto.MapDescription类是指向com的一个代理,你在创建这个类的时候,必须创建在ServerContext里面;而com.esri.arcgisws. MapDescription则是一个标准的Java类,你可以用创建普通类的方法创建一个com.esri.arcgisws.MapDescription实例。
在上面一节中已经知道,MapDescription是通过AXIS的WSDL2JAVA工具转换而来的,事实上,这个包里面所有的类都是这么来的。注意com.esri.arcgisws.MapDescription的父类是Object,创建com.esri.arcgisws. MapDescription类的时候,它在本地的JVM中运行(比如Tomcat的JVM中),所以不消耗ArcGIS Server那一端的资源,而且很容易进行调试。另外,com.esri.arcgisws. MapDescription被称为Value Object,它在向服务器发送请求前,被实例化并且设置一些值,作为参数发送到服务器端;从服务器端返回结果后,该类的属性又被设置成相应的值,从而得到服务器端返回的结果。如果想要看MapDescription的源代码,可以用AXIS根据WSDL生成一个JAVA类来查看。
跟Server进行交互的是8个特别的接口,这些接口的命名都很特殊:…port,比如com.esri.arcgisws.MapServerPort,这个接口唯一的实现类是MapServerBindingStub ,这个接口可以通过com.esri.adf.web.ags.data.AGSMapResource类得到,而AGSMapResource是adf中的类,这样也就把我们所需要的一些功能跟adf连接起来了。如果您使用的是AGSGPResource,那么对应的port就是GPServerPort。而network Analysist比较奇怪,似乎现在ESRI更愿意让我们通过Functionality来使用它,看看它将来的情况吧。所以,所有的参数的创建,设置都在本地JVM中,从服务器端返回的结果也在JVM中,只有在使用MapServerPort进行查询或者其它操作时才跟Server进行交互。所以大大减少了和Server交互的次数和时间,大大提高了性能,同样的情况下,9.2比9.1可以服务更多的用户。这也可以让我们回想起在学习分布式系统编程的时候的一个规则:一次性传输1000 bytes的数据,比传输1000次的1 byte数据又快又好。
所以,adf访问ArcGISServer的时候,推荐使用SOAP协议对Server进行访问,也就是我们提到的com.esri.arcgisws包中的类,这里arcgisws包中ws的含义就是web services。这样效率会更高,速度也会更快。可以查看下面的图,了解协议请求的关系。事实上我们ArcIMS一开始就是使用ArcXML进行数据的交互;Server现在也开始吸取ArcIMS的长处,在本地构建类后,通过SOAP协议,把XML请求发送到服务器端,如图16.3所示:
看看例子里面一个CountFeature的功能吧,在9.1下我们如果要计算某个层中选中的要素,方法是在ServerContext里面建一个,SpatialFilter,设置它的过滤要素和关系。然后对层进行query,然后再得到返回记录集的数目,完成一个空间查询并得到选中要素的过程。在9.2环境下,我们不需要在ServerContext里面建SpatialFilter,而可以使用arcgisws包里面的SpatialFitler,在本地创建空间过滤器,如下面的代码所示:
EnvelopeN env =new EnvelopeN(extent.getMinX(), extent.getMinY(), extent.getMaxX(),
extent.getMaxY(), null, null, null, null, null);
//新建一个空间过滤器
SpatialFilter spatialFilter = new SpatialFilter();
//设置空间相交关系
spatialFilter.setSpatialRel(EsriSpatialRelEnum.esriSpatialRelIntersects);
spatialFilter.setWhereClause("");
spatialFilter.setSearchOrder(EsriSearchOrder.esriSearchOrderSpatial);
spatialFilter.setSpatialRelDescription("");
spatialFilter.setGeometryFieldName("");
//设置和层相交的要素
spatialFilter.setFilterGeometry(env);
int layerId = 1;
this.countedFeatures =
mapServer.queryFeatureCount(mapServer.getDefaultMapName(), layerId,spatialFilter);
在本地创建SpatialFitler,设置好参数,调用mapServerPort的queryFeatureCount方法,在调用这个方法的时候,SpatialFitler,layerId等参数都作为XML封装在SOAP中,发送到服务器端,查询完成后再以XML返回结果。
上面我们说到的一直是ADF这一端的对象,那么如果我们有的时候确实需要使用AO对象,怎么办呢?ESRI的JAVA开发人员事实上早就想到了这些,比如用如下的方法实现对AO对象和ADF中ws包对象中的转换:
ADF对象转向AO对象:
IGeometry igeom =(IGeometry)AGSUtil.createArcObjectFromStub(geom, serverContext);
AO对象转向ADF对象:
com.esri.arcgisws.Polygon wPoly =(com.esri.arcgisws.Polygon)AGSUtil.createStubFromArcObject(bufferedGeom,com.esri.arcgisws.Polygon.class,serverContext);
这就是我们ArcGIS Server中无处不在的Web Services,ArcGIS Server有.net版本和Java版本,事实上你完全可以用Java版本的ADF去访问.net版本Server。如果你要构建大型的SOA,也完全可以用我们server的API,去通过SOAP协议访问我们的Server。