使用 AXIS( 版本 1.4) 的方法 邢飞 先配置好 JDK 和 Tomcat 本文中使用的是 JDK 5.0( 源代码是 1.4 的 ) Tomcat 是 5.0.28 下载 AXIS 1.4, 地址 http://ws.apache.org/ 安装 AXIS 解压开 axis 1_4.zip, 将 axis 1_4/webapps/axis 拷贝到 $TOMCAT_HOME/webapps/ 下启动 tomcat, 打开页面 http://localhost:8080/axi s 页面正常, 表示 axis 安装正确 I
编写服务代码, 如 /* * Account.java */ package com.hcycom.n7; public class Account { public static int balance = 5000; // 存款 public String deposit (int amount) { String ret = "success"; if(amount > 0){ balance += amount; ret = "success"; else { return ret = "failure"; ret; // 取款 public String withdraw (int amount) { String ret = "success"; if(amount > 0 && amount <= getbalance()){ balance = amount; ret = "success"; else { ret = "failure"; return ret; // 获取余额 public int getbalance () { return balance; 需要发布的方法为 deposit, withdraw, getbalance 将编译好的 Account.class 文件放在文件夹 $TOMCAT_HOME/webapps/axis/WEB INF/classes/com/hcycom/n7 如果使用 IDE, 可以将输出文件夹设为 $TOMCAT_HOME/webapps/axis/WEB INF/classes 中 II
编写部署描述符 /home/xingfei/axis/ deploy.wsdd 内容如下 <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <service scope="session" name="account " provider=" java:rpc "> <parameter name="classname" value=" com.hcycom.n7.account "/> <parameter name="allowedmethods" value=" *"/> </service> </deployment> 注意红色部分, account 表示服务的名称 java:rpc 表示调用服务的方法 com.hcycom.n7.account 表示服务要调用的类的名称 * 表示要公布的方法, 也可以公布一个类的某几个方法, 使用逗号隔 III
部署服务 account, 将 axis 所需 jar 文件全部加到 classpath 中, 执行以下命令 得到结果 <Admin>Done 表示部署成功 processing</admin> 也可以在 IDE 中使用如下方法部署 public class Deploy { % java org.apache.axis.client.adminclient \ public static void main(string[] args) { String[] arguments = { /home/xingfei/axis/ deploy.wsdd" ; /home/xingfei/axis/ deploy.wsdd org.apache.axis.client. AdminClient.main(arguments); IV
编写客户端调用代码, 如下 package com.hcycom.n7; import javax.xml.namespace.qname; import org.apache.axis.client.call; import org.apache.axis.client.service; public class AccountClient { public static void main(string[] args) { String endpoint = "http://localhost:8080/axis/services/account"; try { Service service = new Service(); Call call = (Call) service.createcall(); call.settargetendpointaddress( new java.net.url(endpoint) ); call.setoperationname(new QName("http://soapinterop.org/", "deposit ")); // 上面设置了操作为 deposit, 这里意思是存 300 String ret = (String)call.invoke(new Object[]{new Integer(300)); if(ret.equals("success")) { System.out.println("Deposit operation has been successfully done"); call.setoperationname(new QName("http://soapinterop.org/", "getbalance ")); // 操作为获取余额, 这里得到整型的余额数 Integer balance = (Integer)call.invoke(new Object[0]); System.out.println("Now the balance is " + balance.intvalue()); else if(ret.equals("failure")) { System.out.println("Deposit operation has done something wrong"); catch (Exception e) { System.err.println(e.toString()); 其中红色部分为实际调用 account 服务的代码 V
以上所写的, 只适用于简单情况 : 输入参数和输出结果都是基本类型 但是如果输入参数和输出结果中有我们自定义的复杂类型的话, 就会出错 因为客户端和服务器端使用的是 SOAP 消息进行通信 发送方将 Java 对象序列化为 XML, 而接收方又将 XML 反序列化成 Java 对象 双方应该知道 Java 对象和 XML 之间是如何对应的, 才能 正确的工作 AXIS 可以在配置文件中使用 <beanmapping>,<typemapping> 和反序列化 下面是一个例子 : <typemapping deserializer =" /> org.apache.axis.encoding.ser.beandeserializerfactory" encodingstyle=" http://schemas.xmlsoap.org/soap/encoding/" qname ="ns5:contact" serializer =" org.apache.axis.encoding.ser.beanserializerfactory" type ="java:com.hcycom.user.contact" xmlns:ns5="http://www.hcycom.com/n7/user" 来说明 Java 对象如何序列化 其中 type 指我们要处理的 Java 对象 serializer,deserializer 指将这个 Java 对象进行序列化和反序列化的 工具 类 qname 指的是这个 Java 对象的命名空间 这些可以自己写, 也可以由 AXIS 自动帮我们完成, 下面介绍一个综合的例子, 看看 AXIS 如何 为我们自动化的生成这些代码的 例子是一个非常简单的用户管理服务 可以添加, 删除和查看用户的信息 用户有 name,age,sex 和 contact 字段, 而 contact 又是一个 bean, 由 phone 和 email 两个字段 UserManager 是服务的定义, 它定义了对 User 的操作 以下将详细说明 VI
服务定义你要向外提供什么样的服务, 可以先写一个 interface 来描述它 这个 interface 必需继承自 java.rmi.remote, 而且每个方法必需抛出 java.rmi.remoteexception 本例中的服务定义为 : package com.hcycom.user; import import java.rmi.remote; java.rmi.remoteexception; public interface UserManager extends Remote { /** * add a user * 输入部分有复杂类型 */ public int adduser(user user) throws RemoteException ; /** * delete a user */ public int deleteuser(string name) throws RemoteException ; /** * get all the users * 输出为复杂类型 */ public User[] getallusers() throws RemoteException ; /** * get a user by name * 输出为复杂类型 */ public User getuser( String name) throws RemoteException ; 注意, 红色部分是必需的, 不然 AXIS 将不认识这个服务 VII
其中 User 的定义为, 一个标准的 JavaBean package com.hcycom.user; public class User { private String name; private int age; private int sex; private Contact contact; public User() { super();... getters and setters Contact 定义, 也是一个标准的 JavaBean package com.hcycom.user; public class Contact { private String phone; private String email; public Contact() { super();... getters and settters 这两个类都是标准的 JavaBean: 都有默认的构造方法和符合命名规范的 getters 和 setters VIII
生成 WSDL 文件 WSDL 是对服务的描述, 比如服务的名称, 能够进行的操作, 和输入 / 输出的参数 axis 所需的 jar 文件全都包含到 classpath 里边, 然后执行如下命令 java cp.:$axis_classpath o UserManager.wsdl P UserManagerPortType b UserManagerSoapBinding org.apache.axis.wsdl.java2wsdl l http://localhost:8080/axis/services/usermanager n http://www.hcycom.com/n7/user pcom.hcycom.user=http://www.hcycom.com/n7/user y rpc com.hcycom.user.usermanager 参数含义如下 o, output <WSDL file> 指定输出的 WSDL 文件名 若没有指定, 一个适当的默认 WSDL 文件名将被写到当前目录 l, location <location> 指定 Web 服务位置的 URL 最后的斜线或反斜线后的名字是服务端口(service ( 除非被 s 选项覆盖 ) 服务端口的地址位置属性被分配为该指定值 port) 的名字 P, porttypename 指定 porttype 元素的名字 如果未指定, 则使用 class of porttype 的名字 b, bindingname <name> 指定 binding 元素的名字 若未指定, 使用值 serviceportname + "SOAPBinding" n, namespace <target namespace> 指定生成的 WSDL 的目标名称空间 p, PkgtoNS <package> <namespace> 指定包结构到命名空间的映射 若遇到一个没有指定命名空间映射的包, 则 Java2WSDL 将生成 一个适当的命名空间名 这个选项可以指定多次 y, style <argument> 指示 WSDL 文档的风格, 有 DOCUMENT, RPC 和 WRAPPED 三种 默认为 RPC com.hcycom.user.usermanager 就是服务定义接口 执行完上述命令之后会生成一个 UserManager.wsdl 文件 ( 注意 : p 和后边的参数之间不能有空格, 这是本人的经验 ) IX
生成服务器端和客户端代码有了 WSDL 文件我们就可以生成服务器端和客户端的代码框架, 然后再将我们具体的业务逻辑填进去就可以了 axis 所需的 jar 文件全都包含到 classpath 里边, 执行如下命令 java cp.:$axis_classpath org.apache.axis.wsdl.wsdl2java s p com.hcycom.user c com.hcycom.user.usermanagerimpl UserManager.wsdl 参数含义如下 s, server side emit server side bindings for web service p, package <argument> override all namespace to package mappings, use this package name instead c, implementationclassname <argument> custom name of web service implementation UserManager.wsdl 上一步产生的 wsdl 文件 执行完上述命令将产生如下文件 ( 位于 /home/xingfei/axis/com/hcycom/user) deploy.wsdd undeploy.wsdd 部署 ( 反部署 ) 描述符文件 Contact.java User.java 和前面自己写的一样, 用哪个都可以 UserManagerPortType.java 和接口 UserManager 一样, 就是名字变了 本文中用这个类 UserManagerImpl.java 实现 UserManagerPortType 的类 具体的逻辑还得自己去填充 UserManagerPortTypeService.java 用于获取 UserManagerPortType, 是一个接口 UserManagerPortTypeServiceLocator.java UserManagerPortTypeService 的实现 UserManagerSoapBindingStub.java 方便客户端调用服务的 Stub 类 X
实现服务前面只是定义了服务, 没有具体实现, 下面要将具体实现, 填入到 UserManagerImpl.java 中 因为这只是个例子, 所以简单的使用一个 HashMap 来保存用户信息 具体情况下可能需要将用户信息保存到数据库等等持久设备中 代码如下 : package com.hcycom.user; import java.rmi.remoteexception; import java.util.hashmap; import java.util.iterator; public class UserManagerImpl private static HashMap users = new HashMap(); implements com.hcycom.user.usermanagerporttype{ public synchronized int adduser(com.hcycom.user.user in0) throws RemoteException { if(users.containskey(in0.getname())) { throw new RemoteException("user users.put(in0.getname(), in0); int ret = users.size(); return ret; already exists"); public synchronized int deleteuser(java.lang.string in0) throws RemoteException { if(!users.containskey(in0)) { throw new RemoteException("user users.remove(in0); int ret = users.size(); return ret; does not exist"); public synchronized User[] getallusers() throws RemoteException { int num = users.size(); User[] userlist = new User[num]; int idx = 0; for(iterator it = users.values().iterator();it.hasnext();) userlist[idx++] = (User)it.next(); return userlist; XI
public synchronized User getuser(java.lang.string in0) throws RemoteException { if(!users.containskey(in0)) { throw new RemoteException("user doen not exist"); User user = (User)users.get(in0); return user; 这里只是示例, 写的很简单 因为 web 服务是运行在多线程的情况下, 实际中要注意线程安全 部署服务 UserManager 先打开 Tomcat, 然后 axis 所需的 jar 文件全都包含到 classpath 里边, 执行如下命令 java cp.:$axis_classpath /path/to/deploy.wsdd org.apache.axis.client.adminclient 得到结果 <Admin>Done 表示部署成功 processing</admin> 这个和前面部署是一样的, 打开浏览器, 访问 http://localhost:8080/axis/servlet/axisservlet 可以看到 UserManager 服务已经成功部署, 它有 4 个可以调用的方法 XII
编写客户端客户端可以用两种方式, 第一是使用 AXIS 刚刚生成的 Stub, 第二是使用动态调用 下面用代码来说明 1. 使用 Stub, 代码如下 : import java.rmi.remoteexception; import javax.xml.rpc.serviceexception; import com.hcycom.user. *; public class UserClientUsingStub { static UserManagerPortTypeService getusermanagerporttypeservice() { return new UserManagerPortTypeServiceLocator(); public static void main(string[] args) { UserManagerPortTypeService service = getusermanagerporttypeservice(); try { UserManagerPortType usermanager = service.getusermanager(); User user = new User(); user.setage(20); user.setname("james Gosling"); user.setsex(0); Contact contact = new Contact(); contact.setemail("james@sun.com"); contact.setphone("123456789"); user.setcontact(contact); usermanager.adduser(user); user.setage(30); user.setname("scott McNealy"); user.setsex(0); contact.setemail("scott@sun.com"); contact.setphone("987654321"); user.setcontact(contact); usermanager.adduser(user); User[] users = usermanager.getallusers(); for(int i=0;i<users.length;i++) { System.out.println(users[i]); System.out.println(); catch (ServiceException e) { e.printstacktrace(); catch (RemoteException e) { e.printstacktrace(); XIII
执行的结果如下 : Name:Scott Age :30 Sex :Male Contact : McNealy Phone:987654321 Email:scott@sun.com Name:James Age :20 Sex :Male Contact : Gosling Phone:123456789 Email:james@sun.com 2. 使用动态调用, 和前面 Account 例子的调用代码一样, 代码如下 : import java.net.malformedurlexception; import java.rmi.remoteexception; import javax.xml.rpc.serviceexception; import org.apache.axis.client.call; import org.apache.axis.client.service; XIV
public class UserClientUsingDynamicInvocation { public static void main(string[] args) { String endpoint = "http://localhost:8080/axis/services/usermanager"; Service service = new Service(); try { Call call = (Call)service.createCall(); call.settargetendpointaddress(new java.net.url(endpoint)); call.setoperationname(new javax.xml.namespace.qname("http://soapinterop.org", "deleteuser")); Integer num = (Integer)call.invoke(new Object[]{"James Gosling"); System.out.println(num); num = (Integer)call.invoke(new Object[]{"Scott McNealy"); System.out.println(num); catch (ServiceException e) { e.printstacktrace(); catch (MalformedURLException e) { e.printstacktrace(); catch (RemoteException e) { e.printstacktrace(); 执行结果 1 0 两种方法各有各的好处 使用 Stub 很方便, 但是生成的文件很多, 不利于管理 使用 DII, 可 以将调用代码包装, 做到重用, 但是对于比较复杂的情况, 难以适用 实际中也可将两种情况结合起来 仔细看 Stub 的代码的话, 其实它也是适用了 DII, 只不过它的包装只限于对应的服务, 不能用于所有的服务 XV
AXIS 的服务定义都在 $TOMCAT_HOME/webapps/ axis/web INF/server config.wsdd 里边 我们用 AdminClient 进行部署的时候, 也不过是将 deploy.wsdd 的内容添加到这个文件里罢了 其实, 自己编辑这个文件也是可以的, 拷贝, 粘贴即可 还有, 这个文件大部分语法都是很简单的, 非常容易看懂 只要明白了那些标签都代表了什么含义, 就可以自己编辑它, 而不用再 java2wsdl,wsdl2java 了 部署服务还可以使用 JWS 的方式, 但是这种方式有很多缺点, 也不灵活, 这里就不介绍了 对于输入参数和返回值中的非标准 JavaBean, 要自己写 serializer,deserializer 另外 AXIS 还提供类似 Servlet 中 Filter 作用的 Handler, 可以对服务请求进行过滤 ( 日志, 访 问控制, 加密解密等 ) 关于 WSDL,SOAP,Web Service 可以参看 http://www.w3schools.com/wsdl/default.asp http://www.w3schools.com/soap/default.asp http://www.w3schools.com/webservices/default.asp XVI