前言:本文不是专门讲述Web Service技术的,读者在阅读本文之前需要具备一定的SOAP和Web Service知识基础,同时对Weblogic Server的使用也应该熟悉。如果要自己动手实践本文的例子,就需要安装Weblogic Server 81,尽管本文是以weblogic server 81为测试环境,但是针对weblogic server 7下也是差不多的。本文只是起个抛砖引玉的作用,如果想深入研究Web Service的开发,还需要参考、学习相关的资料,包括Weblogic Service的相关文档。
一、概述
在JBuilder中也支持开发基于weblogic的web service,不过实际上在JBuilder下开发web service也是基于ant任务来生成和构造web service的。但是,当初笔者在一个项目中使用JBuilder下自动生成构造ant脚本生成的web service时碰到了一个问题,通过JBuilder生成的web service,如果你的web service调用接口中存在一个或者多个String类型参数的时候,在生成的wsdl文件中对该接口的参数命名不会按照你的后端组件对应方法中参数的名字,而是以string、string0、string1…等形式命名的。而在那个项目中需要在Delphi环境中调用web service,问题就出现了,string在Delphi中是关键词,产生了冲突,不能进行调用。于是笔者决定采用自编写ant脚本的方式来生成和构造web service来解决前面所述Delphi调用的问题。
BEA Weblogic提供了一些Ant任务,用来帮助开发者生成、构造一个Web服务的重要部件,(例如:序列化类、客户端jar支持库、以及web-services.xml描述文件),并且把一个Weblogic Web 服务的所有部分打包成一个可部署的EAR文件。
BEA Weblogic所提供的Web服务Ant任务,支持从实现了Web Service接口的普通JAVA源文件和EJB jar生成Web Service部件,也支持从WSDL描述文件生成,同时支持基于http/https传输协议和JMS传输协议的Web Service。在这一节我们只讲述通过基于一个普通JAVA类作为后端组件来实现的Web Service,传输协议使用http(基于https的方式将在后述关于Web Service安全的部分讲述)。
二、使用Weblogic ant工具生成Web Service
我们先建立D:\wls_ws_demo的工作目录,在此目录下分别建立src、build、ddfiles、webapp、test目录。具体用途后文会涉及到。
首先我们编写一个实现了两个Web Service接口的普通JAVA类:
package com.wnetw.ws.demo;
public class HelloWorldWS{
public String sayHello(){
return "Hello World!";
}
public String welcome(String name){
return "Hello " + name + ",Welcome to WebService!";
}
}
上面两个方法就不需要解释了,很简单。把此类按封装包一致的路径放置在src目录下。
下面是本示例中ant脚本文件内的属性设置:
<property name="build.compiler" value="modern"/>
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="war.file" value="${build.dir}/
applications/HelloWorldWS.war" />
<property name="ear.file" value="${build.dir}/
applications/HelloWorldWS.ear" />
<property name="clients.lib" value="${build.dir}/
clientslib/HelloWorldWS_clients.jar"/>
<property name="bea.home" value="D:/bea"/>
<property name="wls.dir" value="${bea.home}/weblogic81/server"/>
<property name="wlslib.dir" value="${wls.dir}/lib"/>
<property name="wlsext.dir" value="${wls.dir}/ext"/>
<property name="namespace" value="http://www.wnetw.com/demo/"/>
<path id="classpath">
<dirset dir="${build.dir}/classes">
<include name="**"/>
</dirset>
<fileset dir="${wlslib.dir}">
<include name="**/weblogic.jar"/>
<include name="**/webservices.jar"/>
</fileset>
</path>
<property name="javac.fork" value="no"/>
<property name="javac.debug" value="no"/>
<property name="javac.optimize" value="on"/>
<property name="javac.listfiles" value="yes"/>
<property name="javac.failonerror" value="yes"/>
上面的属性应该不是很难理解,关键的是对于bea weblogic server安装目录和构造生成文件的路径说明,其次是对classpath的设置,需要用到的两个weblogic库是weblogic.jar和webservices.jar。
接着我们看看我们在本节中使用的Weblogic提供的Ant任务:
1、source2wsdd
source2wsdd Ant任务最基本的功能是根据我们编写的普通JAVA类源文件生成一个Web Service所必需的两个部件:web-services.xml和.wsdl描述文件。
下面是针对上面HelloWorldWS.java对应的Ant脚本:
<target name="genwsdd">
<source2wsdd javaSource="${src.dir}/com/wnetw/ws/
demo/HelloWorldWS.java"
ddFile="${build.dir}/wsddfiles/web-services.xml"
wsdlFile="${build.dir}/wsddfiles/HelloWorldWS.wsdl"
serviceURI="/HelloWorldWS">
<classpath refid="classpath"/>
</source2wsdd>
</target>
属性说明
javaSource:指定web service的实现后端组件,这里是普通JAVA类com.wnetw.ws.demo HelloWorldWS.java。注意属性里面是对源文件目录路径设置,而不是包路径。
ddFile:生成的web service部署描述符文件web-services.xml的存放路径。
wsdlFile:生成的.wsdl文件存放的路径和名字。
serviceURI:客户应用程序调用此Web服务的URL中的Web Service URI部分。注意:必须以“/”开头。例如:/ HelloWorldWS 。同时这个URI属性也会成为生成的web-services.xml 部署描述符文件中<web-service>元素的uri属性。
例如:本机访问本web service例子的url是http://localhost:7001/ WSDemo/ HelloWorldWS
上面的serviceURI属性就指定了上述url中的/ HelloWorldWS这一部分。
2、clientgen
clientgen可以用来生成JAVA环境下客户端应用调用一个Web Service客户端jar支持库。可以通过wsdl文件来生成,也可以通过一个包含web service实现的ear文件来生成。
下面是clientgen ant任务的脚本示例:
<target name="genclient">
<clientgen wsdl="${build.dir}/wsddfiles/HelloWorldWS.wsdl"
packageName="com.wnetw.ws.demo.client"
clientJar="${clients.lib}"
keepGenerated="false">
<classpath refid="classpath"/>
</clientgen>
</target>
这里采用从前面source2wsdd任务生成的wsdl文件来生成客户端jar支持库。通过wsdl属性指定。
3、war
这是ant提供的标准任务,这里与其他普通的war包有一点区别是,需要把web-services.xml文件打包到war中去。
说明:需要准备web.xml,后面对于安全设置的时候还需要weblogic.xml文件,这里先都打包进去,这些文件都需要提前编辑准备好:
---Web.xml---
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<mime-mapping>
<extension>wsdl</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
</web-app>
---weblogic.xml---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web
Application 7.0//EN" "http://www.bea.com/servers/wls700
/dtd/weblogic700-web-jar.dtd">
<weblogic-web-app>
</weblogic-web-app>
这个文件没设置,在后面关于安全的处理里面需要这里配置角色映射。
下面是war ant脚本示例:
<target name="genwar">
<war destfile="${war.file}" webxml="webapp/WEB-INF/web.xml">
<classes dir="${build.dir}/classes"/>
<webinf dir="${build.dir}/wsddfiles">
<include name="web-services.xml"/>
</webinf>
<webinf dir="webapp/WEB-INF">
<include name="weblogic.xml"/>
</webinf>
</war>
</target>
4、ear
这也是ant标准任务,需要注意的是必须提前编写application.xml文件,下面针对本文例子的application.xml文件:
<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE
Application 1.3//EN' 'http://java.sun.com/dtd/application_1_3.dtd'>
<application>
<display-name></display-name>
<module>
<web>
<web-uri>HelloWorldWS.war</web-uri>
<context-root>WSDemo</context-root>
</web>
</module>
</application>
说明:context-root元素指定此Web Service所在Web应用的应用根。
例如:本机访问本web service例子的url是http://localhost:7001/
WSDemo/ HelloWorldWS
上面的context-root元素就指定了上述url中的WSDemo这一部分。
下面是本文例子的ear ant任务脚本:
<target name="genear">
<ear destfile="${ear.file}" appxml="ddfiles/application.xml">
<fileset dir="${build.dir}/applications" includes="*.war"/>
</ear>
</target>
核心的ant任务说明完了,下面是完整的ant脚本文件:
--- build_wls_all.xml---
<project name="wls_ws_demo" default="all" basedir=".">
<property name="build.compiler" value="modern"/>
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="war.file" value="${build.dir}/applications/
HelloWorldWS.war" />
<property name="ear.file" value="${build.dir}/applications/
HelloWorldWS.ear" />
<property name="clients.lib" value="${build.dir}/clientslib/
HelloWorldWS_clients.jar"/>
<property name="bea.home" value="D:/bea"/>
<property name="wls.dir" value="${bea.home}/weblogic81/server"/>
<property name="wlslib.dir" value="${wls.dir}/lib"/>
<property name="wlsext.dir" value="${wls.dir}/ext"/>
<property name="namespace" value="http://www.wnetw.com/demo/"/>
<path id="classpath">
<dirset dir="${build.dir}/classes">
<include name="**"/>
</dirset>
<fileset dir="${wlslib.dir}">
<include name="**/weblogic.jar"/>
<include name="**/webservices.jar"/>
</fileset>
</path>
<property name="javac.fork" value="no"/>
<property name="javac.debug" value="no"/>
<property name="javac.optimize" value="on"/>
<property name="javac.listfiles" value="yes"/>
<property name="javac.failonerror" value="yes"/>
<target name="all" depends="clean,mdir,compile,genwsdd,
genclient,genwar,genear"/>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="mdir">
<mkdir dir="${build.dir}"/>
<mkdir dir="${build.dir}/classes"/>
<mkdir dir="${build.dir}/applications"/>
<mkdir dir="${build.dir}/clientslib"/>
<mkdir dir="${build.dir}/wsddfiles"/>
</target>
<target name="compile">
<javac encoding="GBK" srcdir="${src.dir}" destdir=
"${build.dir}/classes">
<classpath refid="classpath"/>
</javac>
</target>
<target name="genwsdd">
<source2wsdd javaSource="${src.dir}/com/wnetw/ws/
demo/HelloWorldWS.java"
ddFile="${build.dir}/wsddfiles/web-services.xml"
wsdlFile="${build.dir}/wsddfiles/HelloWorldWS.wsdl"
serviceURI="/HelloWorldWS">
<classpath refid="classpath"/>
</source2wsdd>
</target>
<target name="genclient">
<clientgen wsdl="${build.dir}/wsddfiles/HelloWorldWS.wsdl"
packageName="com.wnetw.ws.demo.client"
clientJar="${clients.lib}"
keepGenerated="false">
<classpath refid="classpath"/>
</clientgen>
</target>
<target name="genwar">
<war destfile="${war.file}" webxml="webapp/WEB-INF/web.xml">
<classes dir="${build.dir}/classes"/>
<webinf dir="${build.dir}/wsddfiles">
<include name="web-services.xml"/>
</webinf>
<webinf dir="webapp/WEB-INF">
<include name="weblogic.xml"/>
</webinf>
</war>
</target>
<target name="genear">
<ear destfile="${ear.file}" appxml="ddfiles/application.xml">
<fileset dir="${build.dir}/applications" includes="*.war"/>
</ear>
</target>
</project>
运行ant生成Web Service:
打开命令行窗口,转到工作目录D:\wls_ws_demo下,在此目录下先运行D:\bea\weblogic81\server\bin\setWLSEnv.cmd(此cmd文件具体路径与你的weblogic platform81实际安装目录相关)进行环境设置,然后运行:D:\bea\weblogic81\server\bin\ant.bat -buildfile build_wls_all.xml。
运行结束,出现“BUILD SUCCESSFUL”,那就代表OK了。转到工作目录下的build目录,你就会看到HelloWorldWS.ear这个文件。
三、测试Web Service
本节将讲述对前一节里生成的Web Service HelloWorldWS进行测试。
启动Weblogic Server,进入Weblogic Server控制台,在Deployments->Applications下部署上节生成的HelloWorldWS.ear。
1、通过Weblogic自动生成的测试主页测试
部署成功后,在浏览器中输入http://localhost:7001/WSDemo/HelloWorldWS访问Weblogic Server默认生成的上述HelloWorldWS Web Service的测试主页。
如下图:
图上列出了HelloWorldWS Web Service上的两个方法:welcome和sayHello。
点击welcome连接进入wecome方法的测试页,如下图:
在上述页面输入“老Z”,提交后就会看到如下图页面:
测试的结果跟上节中的HelloWorldWS.java实现此方法的结果是一样的。测试sayHello方法跟上面过程一样。
在测试主页中还能看到在JAVA环境下,基于clientgen ant任务生成的jar客户端stub支持库调用此HelloWorldWS Web服务的代码示例。
2、使用JAVA程序调用Web Service
下面实际编写一个java测试程序来调用上述Web Service。
--- HelloWorldWSTest.java ---
import com.wnetw.was.demo.client.*;
public class HelloWorldWSTest {
public static void main(String[] args){
try{
HelloWorldWS_Impl ws = new HelloWorldWS_Impl("http://localhost:7001
/WSDemo/HelloWorldWS?WSDL");
HelloWorldWSPort port = ws.getHelloWorldWSPort();
System.out.println(port.welcome(“老Z”));
}catch(Exception e){
e.printStackTrace();
System.out.println(e);
}
}
}
编译、运行上述测试程序的时候首先需要weblogic客户端webservice支持库webserviceclient.jar,还需要前面clientgen ant任务生成的jar客户端stub支持库HelloWorldWS_clients.jar。在下面的编译、运行测试程序的ant脚本中可以看到在classpath中引入了上述两个jar。
编译、运行测试程序的ant脚本如下:
<project name="wls_ws_demo" default="all" basedir=".">
<property name="build.compiler" value="modern"/>
<property name="bea.home" value="D:/bea"/>
<property name="wls.dir" value="${bea.home}/weblogic81/server"/>
<property name="wlslib.dir" value="${wls.dir}/lib"/>
<property name="wlsext.dir" value="${wls.dir}/ext"/>
<path id="classpath">
<fileset dir="${wlslib.dir}">
<include name="**/webserviceclient.jar"/>
</fileset>
<fileset dir="build/clientslib">
<include name="**/HelloWorldWS_clients.jar"/>
</fileset>
<pathelement path="test"/>
</path>
<property name="javac.fork" value="no"/>
<property name="javac.debug" value="no"/>
<property name="javac.optimize" value="on"/>
<property name="javac.listfiles" value="yes"/>
<property name="javac.failonerror" value="yes"/>
<target name="all" depends="compile,run"/>
<target name="compile">
<javac encoding="GBK" srcdir="test" destdir="test">
<classpath refid="classpath"/>
</javac>
</target>
<target name="run">
<java classname="HelloWorldWSTest">
<classpath refid="classpath"/>
</java>
</target>
</project>
运行上述ant脚本后,如果成功的话,应该得到类似下图结果:
3、在VB下调用Web Service
下面我在VB环境下来调用下这个Web Service,笔者使用的是Visual Basic 6.0,要在VB下调用Web Service需要先安装Microsoft SOAP toolkit。
新建一个VB工程,然后把Microsoft Soap Type Library引用进来,如下图:
新建一个form1,添加一个按钮command1,在form1源代码窗口中整个拷贝如下代码:
Dim soap As MSSOAPLib.SoapClient
Private Sub Command1_Click()
MsgBox soap.sayHello()
MsgBox soap.welcome("老Z")
If Err <> 0 Then
MsgBox "Web Service调用失败: " + Err.Description
End If
End Sub
Private Sub Form_Load()
Set soap = New MSSOAPLib.SoapClient
On Error Resume Next
Call soap.mssoapinit("http://localhost:7001/WSDemo/HelloWorldWS?WSDL")
If Err <> 0 Then
MsgBox "初始化SOAP失败: " + Err.Description
End If
End Sub
然后运行工程,点击窗口上的按钮就开始调用前面部署的Web Service(确保Weblogic Server在运行中),成功的话会得到如下图的两个MessageBox:
四、使用非内建数据类型
前面例子中的Web Service方法中使用的参数和返回值都是String,类似String,int等数据类型是属于Weblogic web service所支持的内建类型,关于Weblogic web service所支持的内建数据类型请参见:http://e-docs.bea.com/wls/docs81/webserv/implement.html#1054236
所支持的XML非内建类型请参见:
http://e-docs.bea.com/wls/docs81/webserv/assemble.html#1060805
所支持的Java非内建数据类型请参见:
http://e-docs.bea.com/wls/docs81/webserv/assemble.html#1068595
WebLogic Server能够对内建数据类型进行XML与Java表示之间的转换。但是,如果你在web service操作中使用了非内建数据类型,那么你必须提供以下信息,以确保weblogic server能够正确地进行转换。
- 用于处理数据的Java表示与XML之间的转换的序列化类;
- 包含了数据类型Java表示的Java类;
- 数据类型的XML Schema表示;
- web-services.xml部署描述文件中的数据类型映射信息。
Weblogic Server中带有servicegen和autotype Atn任务,这两个任务通过对web service的无状态EJB或者Java类后端组件的内省,从而自动生成上述部件。上述Ant任务能够处理许多非内建数据类型,所以大多数的开发者并不需要手工生成上述的部件。
有时,你可能也需要手工去创建非内建数据类型部件。因为你的数据类型可能很复杂,以致Ant任务不能正确生成前述部件。你也可能想要自己控制数据在XML和Java表示之间的转换过程,而不依赖Weblogic Server所使用的缺省转换程序。
本节将演示在Weblogic web service中如何处理非内建(自定义)的数据类型。
我们先编写一个数值Bean类UserInfo,如下:
package com.wnetw.ws.demo;
import java.util.*;
public class UserInfo{
private Integer userid;
private String username;
private String sex;
private Date birthday;
private int level;
private double salary;
private telcodes list;
public UserInfo(){}
public Integer getUserid(){
return userid;
}
public void setUserid(Integer userid){
this.userid = userid;
}
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public String getSex(){
return sex;
}
public void setSex(String sex){
this.sex = sex;
}
public Date getBirthday(){
return birthday;
}
public void setBirthday(Date birthday){
this.birthday = birthday;
}
public int getLevel(){
return level;
}
public void setLevel(int level){
this.level = level;
}
public double getSalary(){
return salary;
}
public void setSalary(double salary){
this.salary = salary;
}
public List getTelcodes(){
return telcodes;
}
public void setTelcodes (List telcodes){
this. telcodes = telcodes;
}
}
在前文中的后端组件类HelloWorldWS.java中增加一个方法:
public UserInfo getUserInfo(Integer userid){
UserInfo userinfo = new UserInfo();
userinfo.setUserid(userid);
userinfo.setUsername("李泽林");
userinfo.setSex("男");
userinfo.setBirthday(new Date());
userinfo.setLevel(2);
userinfo.setSalary(1000.51);
List telcodes = new ArrayList();
telcodes.add("123");
telcodes.add("321");
userinfo.setTelcodes (telcodes);
return userinfo;
}
在这个方法里,返回值是UserInfo,这是我们前面定义的数值Bean,由于这是非内建类型,而且也不属于受支持的非内建类型,所以需要我们必须自己来处理XML和UserInfo Java表示数据类型之间的转换。
在本文的例子中,我们使用Weblogic Server的autotype任务来做这件事情。我们先在build目录建一个autotype目录,然后在前文中ant完整脚本中的compile任务之后增加下述脚本:
<target name="gentypeinfo">
<autotype javatypes="com.wnetw.ws.demo.UserInfo"
targetNamespace="${namespace}"
packageName="com.wnetw.ws.demo"
destDir="${build.dir}/autotype"
keepGenerated="true">
<classpath refid="classpath"/>
</autotype>
<copy todir="${build.dir}/classes">
<fileset dir="${build.dir}/autotype">
<include name="**/*.class"/>
</fileset>
</copy>
</target>
autotype Ant任务有几个常用属性,下面简要说明下:
javatypes:需要进行类型转换的非内建(自定义)数据类型java类,注意取值是全限定类名,不需要带上java或者class扩展名。如果存在多个这样的数据类型类,用逗号“,”隔开;
targetNamespace:在对数据类型映射到XML的时候使用的命名空间;
packageName:生成的序列化相关类的封装包;
destDir:生成的序列化相关类存放的目录;
keepGenerated:是否保留中间java源文件,取值为:true或者false。
关于autotype任务的详细信息请参考:
http://e-docs.bea.com/wls/docs81/webserv/anttasks.html#1080062
上述ant任务成功运行后就会生成build/autotype/目录下生成types.xml文件以及按包封装的数据转换类的源文件和class文件。
由于增加了自定义数据类型,所以我们还得更新source2wsdd任务脚本,以下是增加了自定义数据类型处理后的source2wsdd任务脚本:
<target name="genwsdd">
<source2wsdd javaSource="${src.dir}/com/wnetw/ws/demo/HelloWorldWS.java"
typesInfo="${build.dir}/autotype/types.xml"
ddFile="${build.dir}/wsddfiles/web-services.xml"
wsdlFile="${build.dir}/wsddfiles/HelloWorldWS.wsdl"
serviceURI="/HelloWorldWS">
<classpath refid="classpath"/>
</source2wsdd>
</target>
跟以前的脚本相比,增加了typesInfo属性来指定自定义数据类型的XML描述文件。
增加了对自定义数据类型支持后的完整脚本请参考本文代码下载文件。
按照第一节所述方法运行ant脚本build_wls_all.xml后,再部署build\applications\目录下的HelloWorldWS.ear。就可以按照以前说的方法进行测试了。
这一次在Weblogic Server自动生成的web service测试主页:
http://localhost:7001/WSDemo/HelloWorldWS
可以发现多了一个叫getUserInfo的方法连接,进入此方法的调用测试页面,调用此方法后就可以看到此web service方法的调用结果,以下是结果截图:
从调用测试结果页面可以看到,这一次的Return Value是:
com.wnetw.ws.demo.UserInfo@82d235
这正是我们的web service方法返回值类型类型的一个对象,图中的下面也以SOAP消息的形式描述了调用的输入和返回结果。
我们接着修改测试类HelloWorldWSTest.java,如以下:
import com.wnetw.ws.demo.client.*;
import com.wnetw.ws.demo.UserInfo;
public class HelloWorldWSTest {
public static void main(String[] args){
try{
HelloWorldWS_Impl ws = new HelloWorldWS_Impl("http://localhost:7001
/WSDemo/HelloWorldWS?WSDL");
HelloWorldWSPort port = ws.getHelloWorldWSPort();
System.out.println(port.sayHello());
System.out.println(port.welcome("老Z"));
System.out.println("开始测试自定义数据类型的返回值。。。");
UserInfo info = port.getUserInfo(100);
System.out.println(info);
System.out.println(info.getUsername());
}catch(Exception e){
e.printStackTrace();
System.out.println(e);
}
}
}
看看以下代码好像有点问题,UserInfo info = port.getUserInfo(123);我们在HelloWorldWS.java类中定义的对应方法是getUserInfo(Integer userid),参数是Integer的,但是上述测试类代码中却使用int类型,这是正确的。我们可以把clientgen任务中的keepGenerated属性设为true,把自动生成的java源代码保留下来,build成功后,我们打开build\clientslib目录下HelloWorldWS_clients.jar文件中的com.wnetw.ws.demo.client.HelloWorldWSPor.java源文件,可以看到如下代码:
package com.wnetw.ws.demo.client;
/**
* Generated interface, do not edit.
*
* This stub interface was generated by weblogic
* webservice stub gen on Sat Sep 17 16:11:21 CST 2005 */
public interface HelloWorldWSPort extends java.rmi.Remote{
/**
* welcome
*/
public java.lang.String welcome(java.lang.String name)
throws java.rmi.RemoteException ;
/**
* sayHello
*/
public java.lang.String sayHello()
throws java.rmi.RemoteException ;
/**
* getUserInfo
*/
public com.wnetw.ws.demo.UserInfo getUserInfo(int userid)
throws java.rmi.RemoteException ;
}
其中的getUserInfo(int userid)方法是使用int参数的!如果你使用Integer类型参数,反而会编译通不过!只能认为这是weblogic server ant任务对数据类型映射的具体实现了,如果你仔细看了本节前面所述对java内建数据类型的支持列表,那么也是好理解的,因为java数据类型到XML Schema数据类型映射中,java中的int和java.lang.Integer都映射到了int。所以web service服务端接收到的SOAP消息中只会是XML Schema int类型,无法区分客户端使用的会是int或者java.lang.Integer,所以在ant工具根据wsdl文件自动生成客户端支持类的时候就只能使用int了,没法区分int或者java.lang.Integer。这是个有意思的问题^-^一不小心也许会在你工作中浪费不必要的时间。当然如果有必要,你完全可以手动修改、甚至完全自己来生成客户端支持库和数据类型转换类。不过嘛,除了出于研究和特殊情况外这是没有必要的。
我们接着看看HelloWorldWS_clients.jar中还有什么东西,发现有个language_builtins这样的包,从包名也许你能猜到这是干什么的,是对java语言内建数据类型处理的包,此包下面是util包,里面有ListCodec.class类。看看我们的UserInfo类,里面使用了List类,这个包里面的类正是用来处理java.util.List数据类型的,java.util.List属于Weblogic server web service所支持的非内建数据类型,也就是说不需要通过autotype明确来标志生成相关的数据转换类和类型信息。但是,java.util.List又有别于int、java.lang.String等wls web service所支持的内建类型,对于java.util.List等受支持的非内建类型由ant任务自动生动相关数据类型处理信息,不需要手工干预。对比来看,int、java.lang.String等wls web service所支持的内建类型是直接映射,不需要数据类型转换相关类。Java.util.List最终映射成了XML Shema SOAP Array类型。其他类型请参考:http://e-docs.bea.com/wls/docs81/webserv/assemble.html#1068595
运行修改后的build_wls_test.xml脚本,成功的话应该得到如下图类似结果:
增加了自定义数据类型后,VB测试客户端的处理也得增加一些处理来测试返回值为UserInfo的web service方法,如下面代码:
Set Nodes = soap.getUserInfo(100)
MsgBox Nodes(0).nodeName + ":" + Nodes(0).Text
MsgBox Nodes(1).nodeName + ":" + Nodes(1).Text
MsgBox Nodes(2).nodeName + ":" + Nodes(2).xml
MsgBox Nodes(3).nodeName + ":" + Nodes(3).Text
MsgBox Nodes(4).nodeName + ":" + Nodes(4).Text
MsgBox Nodes(5).nodeName + ":" + Nodes(5).Text
MsgBox Nodes(6).nodeName
完整VB测试客户端代码请见本文附带下载代码。
五、配置Web Service安全
Weblogic Web Service包括三种不同概念的安全设置:
- 消息层安全:对SOAP消息中数据的数字签名或者加密;
- 传输层安全:使用SSL来保证客户应用与Web Service之间连接的安全性;
- 访问控制:指定何种用户、组、角色被允许访问该Web Service。
在这里我们主要针对访问控制概念上的安全处理。
Weblogic Web Service最终是作为一个标准的J2EE ear打包文件提供进行部署的,其中包含了一个war包,也就是说web service是以web应用的形式提供并部署的,这从前面的章节就可以看出。
所以,针对web service的访问控制安全处理与J2EE中对于Web资源的访问控制处理是一样的。具体的说就是对特定Web资源增加安全约束。具体配置就是通过在Web应用部署描述符web.xml增加相应的元素:需要进行安全约束的资源集合、授权访问的角色列表、对用户数据的安全约束、角色映射等信息。
在这里,我们需要对前面用到的web.xml文件进行修改,如下所示:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<mime-mapping>
<extension>wsdl</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
<security-constraint>
<display-name>SecurityConstraint</display-name>
<web-resource-collection>
<web-resource-name>HelloWorldWS</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>testrole</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>testrole</role-name>
</security-role>
</web-app>
然后运行ant构造脚本,部署ear。部署成功后,你会在weblogic server运行命令行窗口中看到如下类似信息:
<2005-9-24 下午22时03分45秒 CST> <Warning> <HTTP> <BEA-101304>
<Webapp: ServletC
ontext(id=11680063,name=WSDemo,context-path=/WSDemo),
the role: testrole defined
in web.xml has not been mapped to principals in
security-role-assignment in web
logic.xml. Will use the rolename itself as the principal-name.>
这是因为没有进行角色映射,所以直接使用角色名作为用户名了。这只是一个警告信息,没有关系。后面将会讲述怎么进行角色映射。
然后进入weblogic server Console,新建一个名叫testrole的用户。接着在左侧目录树中一次展开Deployments-Applications- HelloWorldWS- WSDemo,在WSDemo节点上鼠标右击,选择Define Security Policy…
在Policy Condition项选择User name of the caller,点击增加,在接着出现的窗口中填入testrole,OK之后,点击上图页面中的Apply。接下来就可以跟以前一样测试了。
浏览器中输入http://localhost:7001/WSDemo/HelloWorldWS,这个时候会弹出来一个登陆框,如下图:
现在可以看到,访问控制起作用了。输入testrole以及拟增加用户的时候指定的密码后,就能进入到和以前一样的测试主页了。
上面那种使用角色名和用户名对应的方式显示在实际应用中是不方便的,因为具体会有什么样的用户会访问此web service在构建时是不确定的。我们可以使用角色映射的方式来避免这个问题。
进行角色映射需要在weblogic.xml文件中配置,下面我将对testrole映射到一个group,weblogic.xml文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web
Application 7.0//EN" "http://www.bea.com/servers/wls700/dtd
/weblogic700-web-jar.dtd">
<weblogic-web-app>
<security-role-assignment>
<role-name>testrole</role-name>
<principal-name>test_group</principal-name>
</security-role-assignment>
</weblogic-web-app>
在web.xml文件中指定的授权访问角色testrole映射到了test_group,也就是说test_group组中的所有用户都有权访问。这样一来用户授权和实现就解耦了。
使用ant脚本重新构建,然后部署ear。接着进入weblogic server console,删除testrole用户,新建test_group组,新建一个叫test_user的用户,并指派给test_group组。接着按照前面一样Define Security Policy,这一次在Policy Condition部分选择Caller is member of the group,然后点Add进入授权group指定页面,输入test_group,点增加-点OK,回到Define Security Policy主页面,点击Apply就好了。
然后我们在浏览中进入http://localhost:7001/WSDemo/HelloWorldWS,弹出登陆框,这一次我们可以使用test_group中的任何成员用户来登陆了,前面例子是test_user。这样在以后,需要分配新的用户授权访问此Web Service的时候就知需要在Cosole在test_group中增加一个成员就行了,不需要重新构建web service了。
加入了访问控制后,在调用web service的时候就需要提供授权凭证了,下面是需要增加的代码信息:
- JAVA客户
HelloWorldWS_Impl ws = new HelloWorldWS_Impl("http://localhost:7001/WSDemo/HelloWorldWS?WSDL");
HelloWorldWSPort port = ws.getHelloWorldWSPort("test_user","test_user");
改成
HelloWorldWS_Impl ws = new HelloWorldWS_Impl();
//因为加入了访问控制,所以对于http://localhost:7001/WSDemo/HelloWorldWS?WSDL的访问也需授权,所以我们使用缺省构建器,这样就会使用客户端支持库jar中的静态wsdl文件了。
HelloWorldWSPort port = ws.getHelloWorldWSPort(“test_user”, “test_user”);
//后面的参数是test_user的密码,根据你具体的密码更改
- VB客户端
Call soap.mssoapinit("HelloWorldWS.wsdl")
‘由于http://localhost:7001/WSDemo/HelloWorldWS?WSDL需要授权访问,所以我们把脚本生成的HelloWorldWS.wsdl文件直接拷贝到VB项目目录下,使用这个静态文件来初始化soap对象。
‘后面增加下属代码
soap.ConnectorProperty("AuthUser") = "test_user"
soap.ConnectorProperty("AuthPassword") = "test_user"
在我们运行上述两个测试程序的时候会发现调用不成功。原因接下来进行说明。
我们打开工作目录中下build\wsddfiles这个目录中的HelloWorldWS.wsdl这个文件,在最后可以看到下面的service元素内容,如下:
<service name="HelloWorldWS">
<port name="HelloWorldWSPort"
binding="tns:HelloWorldWSPort">
<soap:address location="http://pls.set.the.end.point.address/">
</soap:address>
</port>
</service>
问题就出在这里,soap:address节点的location属性有问题,因为客户端soap初始化后,会使用这个URL来调用本wsdl中描述的web service操作,显然这个地址与我们部署的实际地址是不一样的。所以我们把location属性改为我们部署的web service实际访问URL:
http://localhost:7001/WSDemo/HelloWorldWS。这就是上述两个测试程序不能正确运行的原因。
笔者也没有找到如何在生成web service部件时设置此正确属性的方法,正是因为需要修改上述wsdl文件属性,所以我们需要把build脚本分成两部分来执行,先生成相关部件,然后修改wsdl文件的上述属性,最后才进行打包和客户端支持库的生成,把build_wls_all.xml分开成了build_wls_1.xml和build_wls_2.xml两个build脚本文件。在运行完后build_wls_1.xml修改上述属性,然后运行build_wls_2.xml即可。
部署成功后,就可以测试上面两个调用例子了,注意把修改好的wsdl文件拷贝到VB项目目录中去。
如果在web.xml中<security-constraint>元素里加入下述项目
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
那就会强制要求客户端使用https进行访问,其他更多信息请参考J2EE中Web应用安全方面的资料。
六、杂项设置
本节要说的实际也是安全性方面的问题,只不过和一般的安全性概念不一样,这里讲的是针对在生产部署环境下的考虑。
1、定制主页
在生产环境下,一般是不允许公开web service默认主页的。其次由于通过主页:
http://localhost:7001/WSDemo/HelloWorldWS?WSDL
访问的wsdl描述符文件是动态生成,同时加入了访问控制安全约束后,客户程序访问此文件也存在问题,所以通常在生产环境下将禁止访问web service默认主页以及动态wsdl文件,可以使用专门的静态web站点来提供必要的信息,以及通过静态web站点来发布wsdl。
要禁用默认主页以及wsdl文件,需要在web-services.xml描述符文件中进行设置。如下所示在web-service节点中加入下面两个属性:
exposeWSDL="False"
exposeHomePage="False"
修改后类似下面示例:
。。。
<web-services>
<web-service name="HelloWorldWS"
targetNamespace="http://tempuri.org/"
uri="/HelloWorldWS"
exposeWSDL="False"
exposeHomePage="False">
。。。
这个修改也需要在运行build_wls_1.xml之后进行修改,才能保证应用打包部署后使得此设置生效。
在禁止了默认主页和WSDL文件后,为了保证web service更新后不需要更新客户程序的文件,所以最好建立一个静态web站点来发布web service,也就是发布wsdl文件。在用于发布wsdl的web应用中需要在web.xml中加入以下的Mime类型映射:
<mime-mapping>
<extension>wsdl</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
2、启用https协议
除了上一节中在web.xml中加入
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
来启用https通讯协议外,还可以在通过在web-service.xml文件中,在web-service(注意不是web-services)节点中加入下面属性:
protocol="https"
上述属性能保证客户端必须使用https来访问本web service。
七、结束语
本文只是针对很小的一方面来讲述基于weblogic ant任务开发web service的,只是起个抛砖引玉的作用。其次,通过本文你也能了解到web service的本质过程,无论通过什么工具来开发,本质上都是生成基础部件,然后打包。如果需要全面了解weblogic server web service开发方面的知识请参考bea文档:
http://e-docs.bea.com/wls/docs81/webservices.html
同时本文使用的环境是window 2000 server和weblogic platform8.1英文版。
本文示例项目代码可从以下地址下载:
http://www.wnetw.com/jclub_resources/technology/attachfiles/wls_ws_demo.rar