契约最先式方法和契约最后式方法的比较
在实现 Web 服务时,我们通常有两种方法,即契约最先式和契约最后式方法。在契约最先式方法中,我们先开始编写 Web 服务的契约,即先开始设计 WSDL 文件;然后,我们使用一些工具根据 WSDL 文件生成 Java 代码,生成的 Java 代码包括 Java 接口及其实现类,另外,还包括一些与 Web 服务管道相关的代码。而在契约最后式的方法中,您先开始编写 Java 代码,然后,从这些代码中生成 WSDL 文件。
在定义和实现 Web 服务时,虽然我们需要考虑多种因素,如您要定义的 Web 服务上下文等,但是,在通常情况下,人们更偏爱使用契约最先式方法。但契约最先式方法的一个实际困难是,创建 WSDL 的过程并不是非常简单。因此,在没有特别工具支持的情况下,我们也许并不能轻易创建一个 WSDL 文件。为了解决这个难题,我们可以采用一种混合的方法,其具体步骤如下:
1) 先创建 Web 服务接口 ( 即 Java 接口 )
2) 根据该接口生成 WSDL
3) 然后,您就可以按照契约最先式方法的正常步骤,继续我们的开发过程
在我们本节的例子中,我们将采用上面这种混合方法,因此,您只需要对本章中例子中的编译脚本稍加修改,您就可以将本例改为契约最先式或契约最后式方法实现的 Web 服务。
使用Apache Axis来实现Web服务
我们将使用 Axis1.3 来实现 Web 服务,您可以在 http://ws.apache.org/axis/java/releases.html 处免费下载,下载后,请将安装包解压到本地硬盘的合适目录,然后修改源代码 examples.PROPERTIES 文件中的 axis.home 属性,让它指向 Axis 的安装目录。
与本章前面的两个例子不同的是,我们将编译并对 Web 服务文件进行打包,让它成为一个标准的 Web 文档文件 (.war 文件 ) 。然后,我们需要 Web 服务器来部署该 war 文件,这里我们将使用 Apache Tomcat6.x ,您可以从 http://tomcat.apache.org/ 处下载 Tomcat6.x 。
服务器端和客户端端代码
我们将使用契约最先式方法实现服务器端,但由于我们不想手动编写 WSDL ,所以咱们就先来写一个 Java 接口。本例中所有的文件都位于 ch03/02_Axis/src 目录下 ( 译者注:本书代码请从 http://www.packtpub.com/files/code/3216_Code.zip 处下载 ) 。我们现在来逐个看看这些文件。
IHelloWeb.java
IHelloWeb是一个简单的Java接口,它只定义了一个业务方法:
- public interface IHelloWeb{
- public String hello(String param);
- }
在契约最先式的实现方法中,我们先从WSDL开始,因为WSDL是一种平台中立的语言,这可以保证,使用契约最先式方法实现的客户端和服务器端可以相互交互。但本例中,我们先从Java接口开始,因此,为了保证生成的WSDL也满足可互操作性的标准。您需要注意,在您产生WSDL前,Java接口方法声明中包含的参数及返回类型,要能够和标准的可移植的WSDL相兼容。
HelloWebService.java
接下来我们需要实现Web服务,HelloWebService类的源码非常简单:
- public class HelloWebService implements IHelloWeb{
- private static int times;
- public HelloWebService(){
- System.out.println("Inside HelloWebService.HelloWebService...");
- }
- public String hello(String param){
- System.out.println("Inside HelloWebService.hello... - " +
- (++times));
- return "Return From Server";
- }
- }
其实,我们可以生成一个Web服务实现类的模板,而不用从头开始实现它。在实现类模板中,您可以手动添加您的业务逻辑。您可以使用这种方法在真实的生产环境中部署Web服务,但在本例中,我们不用手动添加业务逻辑,而是只使用一个小巧的ant脚本自动实现所有步骤。
build.xml
build.xml文件非常重要,它将引导您一步一步地实现web服务,从Java接口开始,实现业务逻辑,然后打包为一个标准war文件。因此,我们将整个的编译脚本拷贝如下:
- <?xml version="1.0" ?>
- <project default="all">
- <property file="../examples.properties" />
- <property name="build" value="build" />
- <property name="dist" value="dist" />
- <property name="lib" value="lib" />
- <property name="src" value="src" />
- <property name="gensrc" value="gensrc" />
- <property name="config" value="config" />
- <property name="webapp.name" value="AxisEndToEnd" />
- <property name="service.name" value="HelloWebService" />
- <property name="wsdl" value="HelloWebService.wsdl" />
- <property name="interface.package"
- value="com.binildas.apache.axis.AxisEndToEnd" />
- <property name="interface.path"
- value="com/binildas/apache/axis/AxisEndToEnd" />
- <property name="interface.class" value="IHelloWeb" />
- <property name="implement.package"
- value="com.binildas.apache.axis.AxisEndToEnd" />
- <property name="implement.path"
- value="com/binildas/apache/axis/AxisEndToEnd" />
- <property name="implement.class" value="HelloWebService" />
- <path id="classpath">
- <pathelement path="./build" />
- <fileset dir="${axis.home}/lib">
- <include name="*.jar" />
- </fileset>
- </path>
- <target name="all" depends=" deploy, compileclient"></target>
- <target name="clean">
- <delete dir="${build}" />
- <delete dir="${dist}" />
- <delete dir="${lib}" />
- <delete dir="${gensrc}" />
- </target>
- <target name="init">
- <mkdir dir="${build}" />
- <mkdir dir="${dist}" />
- <mkdir dir="${lib}" />
- <mkdir dir="${gensrc}" />
- </target>
- <target name="copy">
- <copy todir="${lib}">
- <fileset dir="${axis.home}/lib">
- <include name="*.jar" />
- </fileset>
- </copy>
- </target>
- <target name="precompile" depends="clean, init">
- <javac srcdir="${src}" destdir="build"
- classpathref="classpath">
- <exclude name="**/*Client*.java" />
- </javac>
- </target>
- <target name="java2wsdl" depends="precompile">
- <java classname="org.apache.axis.wsdl.Java2WSDL" fork="true"
- failοnerrοr="true">
- <arg value="-o" />
- <arg value="${wsdl}" />
- <arg
- value="-lhttp://localhost:8080/${webapp.name}/
- services/${service.name}" />
- <arg value="${interface.package}.${interface.class}" />
- <classpath>
- <path refid="classpath" />
- <pathelement location="${build}" />
- </classpath>
- </java>
- </target>
- <target name="wsdl2java" depends="java2wsdl">
- <java classname="org.apache.axis.wsdl.WSDL2Java" fork="true"
- failοnerrοr="true">
- <arg value="-o" />
- <arg value="${gensrc}" />
- <arg value="-s" />
- <arg value="-S" />
- <arg value="no" />
- <arg value="-c" />
- <arg value="${implement.package}.${implement.class}" />
- <arg value="${wsdl}" />
- <classpath>
- <path refid="classpath" />
- <pathelement location="${build}" />
- </classpath>
- </java>
- </target>
- <target name="implement" depends="wsdl2java">
- <delete>
- <fileset dir="${gensrc}/${implement.path}"
- includes="${implement.class}.java" />
- </delete>
- <copy todir="${gensrc}/${implement.path}" overwrite="ture">
- <fileset dir="${src}/${implement.path}">
- <include name="${implement.class}.java" />
- </fileset>
- </copy>
- </target>
- <target name="compile" depends="implement">
- <javac srcdir="${gensrc}" destdir="build"
- classpathref="classpath" />
- </target>
- <target name="compileclient">
- <javac srcdir="${src}" destdir="build"
- classpathref="classpath">
- <include name="**/*Client*.java" />
- </javac>
- </target>
- <target name="deploy" depends="compile, copy">
- <move todir="${config}" flatten="yes">
- <fileset dir="${gensrc}">
- <include name="**/*.wsdd" />
- </fileset>
- </move>
- <java classname="org.apache.axis.utils.Admin" fork="true"
- failοnerrοr="true" dir="config">
- <arg value="server" />
- <arg file="config/deploy.wsdd" />
- <classpath>
- <path refid="classpath" />
- <pathelement location="build" />
- </classpath>
- </java>
- <war destfile="dist/${webapp.name}.war"
- webxml="config/web.xml">
- <webinf dir="config">
- <include name="server-config.wsdd" />
- </webinf>
- <lib dir="lib" />
- <classes dir="build" />
- </war>
- <delete dir="${lib}" />
- </target>
- </project>
下面,我们来逐步理解一下上面这个Web服务的实现过程。按照上面脚本的顺序,我们执行以下ant任务。
clean任务:这个ant任务将删除所有临时目录及上次编译生成的所有文件;
init任务:这个ant任务将创建几个新的目录;
precompile任务:这个步骤将编译Java接口类;
java2wsdl任务:java2wsdl将从预先编译过的Java接口中生成WSDL,您可以浏览Apache的http://ws.apache.org/axis/java/reference.html页面,了解上面脚本中java2wsdl任务中的一些选项的意义;
wsdl2java任务:我们现在开始实施契约最先式方法。一旦有了WSDL,我们就可以通过wsdl2java这个工具生成Web服务代码,包括Web服务的实现模板类,这些生成的文件都位于同一个目录中(如本例中的gensrc目录)。同时,本任务执行后也将产生一个用于部署Web服务的deploy.wsdd文件,和一个用于取消部署的undeploy.wsdd文件。在后面我们将通过这两个wsdd文件生成服务器端的配置;
implement任务:前面已经讲过,为了避免向生成的Web服务实现模板中手动添加代码,我们可以利用前面已经写好的一个与实现模板类同名的Java文件,这个文件中包含的类名与模板类也相同(即HelloWebService),并且它实现了业务逻辑。因此,我们可以将前面准备好的实现类替换生成的模板类,这样做的效果就相当于向生成的模板类中添加了业务代码;
compile任务:我们将通过该任务编译所有生成的类,其中包含业务逻辑的Web服务实现类;
copy任务:该任务将所有需要的Axis库文件拷贝到一个临时目录(如本例中的lib)中,以便于打包只用;
deploy任务:在本任务中,我们使用org.apache.axis.utils.Admin类产生部署描述文件server.config.wsdd,这个类的输入为前面wsdl2java任务生成的deploy.wsdd。然后,我们接下来创建一个标准的Web归档文件(.war),这样,我们可以非常容易地将它部署到我们钟爱的Web服务器中;
complieclient:这是最后一个步骤,编译客户端代码。
RpcClient.java
RpcClient利用自动生成的客户端存根文件,按RPC风格调用远程Web服务。
- public class RpcClient {
- private static String wsdlUrl = "http://localhost:8080/AxisEndToEnd/services/HelloWebService?WSDL";
- private static String namespaceURI = "http://AxisEndToEnd.axis.apache.binildas.com";
- private static String localPart = "IHelloWebService";
- protected void executeClient(String[] args) throws Exception {
- IHelloWebService iHelloWebService = null;
- IHelloWeb iHelloWeb = null;
- if (args.length == 3) {
- iHelloWebService = new IHelloWebServiceLocator(args[0], new QName(
- args[1], args[2]));
- } else {
- iHelloWebService = new IHelloWebServiceLocator(wsdlUrl, new QName(
- namespaceURI, localPart));
- }
- iHelloWeb = iHelloWebService.getHelloWebService();
- log("Response From Server : " + iHelloWeb.hello("Binil"));
- }
- public static void main(String[] args) throws Exception {
- RpcClient client = new RpcClient();
- client.executeClient(args);
- }
- }
CallClient.java
这里,我们还提供了一段叫做CallClient的Web服务客户端代码,其中使用了Axis和SOAP API,以面向文档的方式调用该Web服务。
- public class CallClient {
- public static String wsURL = "http://localhost:8080/AxisEndToEnd/services/HelloWebService?WSDL";
- public static String action = "HelloWebService";
- // SOAP Request - Not shown fully
- public static String msg = "<?xml version=/"1.0/" encoding=/"UTF-8/"?><soapenv:Envelope ...>";
- public static void test() throws Exception {
- InputStream input = new ByteArrayInputStream(msg.getBytes());
- Service service = new Service();
- Call call = (Call) service.createCall();
- SOAPEnvelope soapEnvelope = new SOAPEnvelope(input);
- call.setTargetEndpointAddress(new URL(wsURL));
- if (action != null) {
- call.setUseSOAPAction(true);
- call.setSOAPActionURI(action);
- }
- soapEnvelope = call.invoke(soapEnvelope);
- System.out.println("Response:/n" + soapEnvelope.toString());
- }
- public static void main(String args[]) throws Exception {
- CallClient callClient = new CallClient();
- if (args.length > 0) {
- wsURL = args[0];
- }
- if (args.length > 1) {
- action = args[1];
- }
- callClient.test();
- }
- }
运行服务器和客户端
前面的build.xml文件有点长,这对于新手而言,执行前面的10个步骤来实现Web服务,并不是一件简单的事。但是,我们可以使用一个简单的Ant命令来完成上面所有步骤,因此,您可以把上面的build.xml文件保存下来,以备以后在您的项目中重用它们。要编译服务器端代码,请执行下列命令:
cd ch03/02_Axis
ant
下图显示了每一步编译执行的过程:
编译完成后,我们会在下面的文件夹中找到可部署的Web归档文件(AxisEndToEnd.war):
Ch03/02_Axis/dist
现在,您就可以把这个war文件拷贝到您的Web服务器的Webapps目录,然后,重启服务器。如果部署没有问题,您就可以访问http://localhost:8080/AxisEndToEnd/services/HelloWebService?WSDL获取该Web服务的WSDL。
接下来您就可以执行客户端代码测试您的Web服务,因为我们提供了两个版本的客户端代码,因此,您测试Web服务时会有两个选择。要执行RpcClient,请使用下列命令:
cd ch03/02_Axis
ant runrpc
下图显示了RpcClient的执行过程,您会看到控制台上会打印出从服务器端接收到的应答。
要执行CallClient,请使用下列命令:
cd ch03/02_Axis
ant runcall
下图显示了CallClient的执行过程,您会看到控制台上会打印出从服务器端接收到的应答。