本文根据 SUN 官方网站 Enterprise Java Technologies Tech Tips 栏目上的一篇文章改写的,所有过程均调试通过。
一.前言
SOA 思想的核心在于 "S" ,凡是从服务的角度去看待系统功能,并且构建和实现应用,都可以认为是 SOA 的某种实现形式。下面一段要说的是: SOA 技术的核心在于 "O" --只有做到 Service-Orientation 的技术,才能真正称为 SOA 技术。
怎样才能算作 Service-Orientation ?我们再以 OO 进行类比: VB 之所以被称为基于对象 (Object-Based) 而不是面向对象 (Object-Oriented) 的语言,是因为 VB 的运行时结构不具有 VMT 等基本构造,语法上也不支持私有成员,继承等基础特性,两者结合,就造成无法支持封装,继承,多态等面向对象的关键技术。一句话, VB 不是围绕 “ 对象 ” 这个核心概念设计的语言。 对于 SOA 技术,它也可以从这个角度划分两个层次:一个是 SOA 的 “ 运行时 ” 相关结构标准,例如 SOAP , WSDL,WS-* 等,相当于 OO 中的对象内部结构, VMT 构造等。另外一个则是语言和工具层面的支持,例如基于元数据的服务描述,支持 Web Service 的类库, Proxy 生成工具等,相当于 OO 语言中提供的基本 OO 语法,类库,编绎器等。这两个层次共同构成 SOA 体系结构中的要素,让人们能够围绕 “ 服务 ” 这个核心概念进行系统开发和应用。因此,一种技术架构,平台或产品要称作 SOA ,应当同时具备这一两方面的特征。
所以,下面将要讨论的 JAX-WS 技术,从上面的观点来看,也就是 SOA 技术的第二个层次,即语言和工具层面的支持 --Java ,例如基于元数据的服务描述,支持 Web Service 的类库, Proxy 生成工具等 .
Java API for XML Web Services ( JAX-WS ) 2.0, 是 Java EE 5 平台的一个重要的部分。作为 Java API for XML-baseed RPC 1.1 ( JAX-RPC )的后续版本,在 JAX-RPC 1.1 中 , 开发人员需要写一个接口类 Service Endpoint Interface(SEI), 在 JAX-WS 2.0 中 , 开发人员一上来就可以直接写自己的实现类 . 通过使用 annotations, 自动生成 SEI 和其他一些文件 . 这样有助于开发人员专注于自己想开发的部分 , 而不必要地分散精力去维护其他的一些附属文件 .
二 . 快速实践 JAX-WS2.0
下面通过一个两个数相加的简单例子来看看 ,JAX-WS 2.0 API 的应用过程 . 程序是一个独立的客户端传给服务端两个整数 , 经过服务端处理后 , 将结果返回到客户端并打印出来 .
一 . 环境配置 .
1. JDK 5.0 or higher
下载 : http://java.sun.com/javase/downloads/index.jsp
2. Java EE 5.0 App Server.
这个例子是基于 Java EE 5 的一个开源实现项目 GlassFish 。 https://glassfish.dev.java.net/public/downloadsindex.html
本例子所需的基本代码的压缩包可以通过这个 链接 下载。里面包括了这个例子需要的代码,构建脚本和一个 build 文件。
环境变量的配置:
·
GLASSFISH_HOME.
这个应该指向你安装
GlassFish
的目录
(
比如,我系统上的是:
J:/Sun/AppServer)
·
ANT_HOME.
这个应该指向
ant
所安装的目录。在你下载
GlassFish bundle
时
Ant
已经被包含在里面了。
(
对于
Windows
系统,它是在
lib/ant
子目录
)
。不过你也可以从
Apache Ant Project page.
下载
Ant
。对于这个例子需要
Apache ant 1.6.5
·
JAVA_HOME.
这个应该指向你系统上安装的
JDK 5.0
(
or higher
)的目录。
同时,把
ant
的
bin
目录添加到
Path
环境变量中去
(J:/apache-ant-1.6.5/bin)
,当然了
JDK
的
bin
目录也加进去了。
然后
下载例子的代码包
并且解压。根文件夹是
jaxws-techtip
。
endpoint/
目录下有一个文件
Calculator.java
client/
目录下有一个文件
JAXWSClient.java
二
.
编写构建服务端
随着第一步环境配置的完全,现在该开始构建一个 web 服务了。在这个例子里, web 服务是从一个 Java 类来开发的。为了构建这个 web 服务:
1 . 写一个端点实现类 ( endpoint implementation class) 。
2 . 编绎这个 端点实现类。
3 . 有选择的产生对 web 服务的运行必须具备的那些可移植的制品。
4 . 把 web 服务打包成一个 WAR 文件并且在 App Server 中部署它。
1 编写实现类 .
进到
endpoint/
目录下,可以看到里面有一个文件
Calculator.java.
它是一个端点实现类,具备有对两个整数进行相加的简单服务。
JAX-WS 2.0
大量地依赖注释(
annotations
)的使用,它是
A Metadata Facility for the Java Programming Language (JSR 175)
描述的规范和
Web Services Metadata for the Java Platform (JSR 181)
描述的规范。
import javax.jws.WebService;
import javax.jws.WebMethod;
@WebService(
name = " Calculator " ,
serviceName = " CalculatorService " ,
targetNamespace = " http://techtip.com/jaxws/sample "
)
public class Calculator {
public Calculator() {}
@WebMethod(operationName = " add " ,action = " urn:Add " )
public int add( int i, int j) {
int k = i + j;
System.out.println(i + " + " + j + " = " + k);
return k;
}
}
研究上面的实现类
Calculator
,注意到类里的两个注释的使用
@WebService
跟
@WebMethod
。一个正确的端点实现类必须包含有一个
@WebService
注释。这个注释标注这个类将作为一个
web
服务对外开放。
@WebService
的
name
属性表明了
web
服务描述语言
(WSDL)
里的端口类型
(
portType
)
(在这个例子里是
”Calculator”
)。而
serviceName="CalculatorService"
对应的是一个
WSDL
里的服务元素
(service)
。
targetNamespace
属性为
WSDL
说明了
XML
的命名空间。所有的这些属性都是可选的。对于这些属性的默认值是什么,请参考
Web Services Metadata for the Java Platform
规范,
JSR 181
。
再来看看另外一个重要的注释
@WebMethod
,被它注释过的方法说明将它以一个
web
服务的方法暴露出来,被其他应用来调用。
@WebMethod
注释里的
operationName
声明了
WSDL
里的一个元素
WSDL
operation
(在这个例子里,
”add”
)
,
另外一
个属性
action =("
urn:Add
"),
它为
WSDL
还有一些从这个
web
服务操作
(web service operation)
生
成的元素声明了一个命名空间。这两个属性都是可选的。如果你没有列出来的
话, WSDL 操作 (operation) 的值将会默认为方法名,还有 action 值也会默认为
服务的
targetNamespace
。
2
编绎实现类
写完了上面的实现类之后,你需要编绎它。点击
开始-
>
程序-
>Sun Microsystems
-
>“Start Default Server”
启动应用服务器或者通过在
DOS
窗口下敲下面的命令来启动它:
<GF_install_dir>/bin/asadmin start-domain domain1
,其中
GF_install_dir
是你安装
GlassFish
的目录,也就是说先到
<GF_install_dir>/bin
目录下,然后用命令
asadmin start-domain domain1
来启动应用服务器。现在将目录转到
jaxws-techtip
文件夹下,运行下面的
ant
命令,也就是执行第一个任务
complie:
ant compile
执行这个命令就相当于执行以下的 javac 命令 ( 都是在同一行 ) :
javac -classpath $GLASSFISH_HOME/lib/javaee.jar -d
./build/classes/service/ endpoint/Calculator.java
3 为 web 服务的执行产生可移植的制品
这一步是可选的。如果在这个 web 服务的部署期间,他们没有和一个可配置的服务单元绑定, GlassFish 的部署工具能够自动地产生这些制品。然而对于刚刚接触 JAX-WS 来说,对于弄清楚整个编程模式来说,通过手动产生地会话会更有帮助,即运行下面的命令:
ant generate-runtime-artifacts
这个任务将会在 jaxws-techtip 目录下生成 build/generated 目录,并且运行了下面的 wsgen 命令 ( 都是在同一行 ):
$GLASSFISH_HOME/bin/wsgen -cp ./build/classes/service -keep -d ./build/classes/service –r ./build/generated -wsdl endpoint.Calculator
一个 WSDL 文件 (CalculatorService.wsdl) 在 build/generated 目录下生成了,还在同个目录下生成了另外一个 schema 文件 (CalculatorService_schema1.xsd), 它为 CalculatorService.wsdl 定义了 schema 。
JavaBean 技术组件 ( JavaBeans ) 在编组 ( marshaling,java->XML ) 的方法调用,响应,还有 service-specific 异常 中起了很大的作用。这些类将会在 web 服务在一个应用服务器中运行的时候被使用。 JavaBean 类在 jaxws-techtip 目录下的 /build/classes/service/endpoint/jaxws 目录被生成了,这些类是:
Add.java
Add.class
AddResponse.java
AddResponse.class
4 打包并部署 WAR 文件
接下来你需要做的工作就是对服务进行打包和部署。为了做这个,你需要在一个部署描述符中详细说明这个服务。 Web 服务可以绑定成 servlet 的形式或者无状态的 session bean 形式打包成 Web Archive (WAR) 文件。在这个例子里把它绑定为一个 servlet 。
为了把这个服务打包成一个 WAR 文件,定位到 jaxws-techtip 文件夹,并且在 DOS 窗口上运行下面的命令:
ant pkg-war
对于这个 war 文件的结构,我们可以到 build.xml 文件里看看 pkg-war 目标:
<
target
name
="pkg-war"
depends
="init-common">
<
mkdir
dir
="${assemble.dir}"/>
<
echo
message
="mybuildclassesdiris:${build.classes.dir}"
level
="verbose"/>
<
mkdir
dir
="${build.classes.dir}/tmp"/>
<
mkdir
dir
="${build.classes.dir}/tmp/WEB-INF"/>
<
mkdir
dir
="${build.classes.dir}/tmp/WEB-INF/classes"/>
<
mkdir
dir
="${build.classes.dir}/tmp/WEB-INF/wsdl"/>
<
copy
file
="${web.xml}"
tofile
="${build.classes.dir}/tmp/WEB-INF/web.xml"
failonerror
="false"/>
<
copy
todir
="${build.classes.dir}/tmp/WEB-INF/classes">
<
fileset
dir
="${build.classes.dir}/service">
<
include
name
="**/*.class"/>
<
include
name
="**/${handler.name}"/>
</
fileset
>
</
copy
>
<
copy
todir
="${build.classes.dir}/tmp/WEB-INF/wsdl">
<
fileset
dir
="${build.generated.dir}">
<
include
name
="**/*.*"/>
</
fileset
>
</
copy
>
<
echo
message
="Creatingwarfile${assemble.dir}/${appname}-web.war"
level
="verbose"/>
<
jar
jarfile
="${assemble.dir}/${appname}-web.war"
update
="true">
<
fileset
dir
="${build.classes.dir}/tmp"
casesensitive
="yes">
<
include
name
="**/*class*"/>
<
include
name
="**/${handler.name}"/>
</
fileset
>
<
fileset
dir
="${build.classes.dir}/tmp/"
casesensitive
="true">
<
include
name
="WEB-INF/web.xml"/>
</
fileset
>
<
fileset
dir
="${build.classes.dir}/tmp"
casesensitive
="yes">
<
include
name
="WEB-INF/wsdl/*.*"/>
</
fileset
>
</
jar
>
<
echo
message
="createdwarfile${assemble.dir}/${appname}-web.war"
level
="verbose"/>
</
target
>
我们可以通过执行下面的命令来部署已经生成的 war 文件:
ant deploy-app
这等同于执行下面的 asadmin 部署命令 ( 都是在同一行 ) :
bash$GLASSFISH_HOME/bin/asadmin deploy --user admin
--passwordfile passwd --host localhost --port 4848
--contextroot jaxws-webservice --upload=true --target server
三 编写构建客户端
在你部署完这个 web 服务之后,你可以通过一个客户端程序来访问它。下面是构建这个客户端的步骤:
1 编写客户端
2 生成编绎这个客户端必须要有的可移植制品。
3 编绎客户端。
4 运行客户端。
5.1 编写客户端
下面的程序, JAXWSClient, 是一个独立的客户端程序,它在这个例子所提供的代码里可以找到。这个客户端类调用了部署好的服务的一个 add 操作十次,从数字 0 到 9 挨个加 10.
package
client;
import
javax.xml.ws.WebServiceRef;
import
com.techtip.jaxws.sample.CalculatorService;
import
com.techtip.jaxws.sample.Calculator;
public
class
JAXWSClient
{
@WebServiceRef(wsdlLocation=
"http://localhost:8080/jaxws-webservice/CalculatorService?WSDL")
static
CalculatorServiceservice;
public
static
void
main(String[]args)
{
try
{
JAXWSClientclient=
new
JAXWSClient();
client.doTest(args);
}
catch
(Exceptione)
{
e.printStackTrace();
}
}
public
void
doTest(String[]args)
{
try
{
System.out.println(
"Retrievingportfromtheservice"+service);
Calculatorport=service.getCalculatorPort();
System.out.println(
"Invokingaddoperationonthecalculatorport");
for
(
int
i=0;i>10;i++)
{
int
ret=port.add(i,10);
if
(ret!=(i+10))
{
System.out.println("Unexpectedgreeting"+ret);
return
;
}
System.out.println(
"Adding:"+i+"+10="+ret);
}
}
catch
(Exceptione)
{
e.printStackTrace();
}
}
}
研究下上面代码的特点,在 JAXWSClient 类里的 @WebServiceRef 注释是用来定义一个 web 服务的引用。 @WebServiceRef 注释的 wsdlLocation 参数它指向了一个所要引用的服务的 WSDL 文件。 @WebServiceRef 注释支持其它的可选属性,就像在 JSR 224 里所说的。静态变量名 service 将会被客户端容器在运行时被动态地注入。
注意到 JAXWSClient 的 import 语句:
com.techtip.jaxws.sample.CalculatorService and com.techtip.jaxws.sample.Calculator .
这些 import 语句是对那些在下一步里将要产生的可移植制品的声明。 CalculatorService 是服务实现类的可移植制品。 Calculator 是一个对于服务端点的 Java 接口,它是从 @WebServiceRef 注释中的 wsdlLocation 属性所说明的 WSDL 文件生成的。
这个客户端从 getWebServiceRefNamePort 方法得到一个 CalculatorService ,从而得到一个端点 Calculator 接口 Calculator port = service.getCalculatorPort();WebServiceRefName 是 @WebServiceRef 注释的 name 属性,或者说是在生成的 WSDL 文件里 WSDP 端口的值。在获得了这个端点后,客户端调用了十次加的操作。
5.2 生成客户端的可移植的制品
就像在之前所提到的, CalculatorService 跟 Calculator 都是可移植的制品。为了生成客户端所需的所有制品,定位到 jaxws-techtip 文件夹,并且在 DOS 窗口下输入下面的命令:
ant generate-client-artifacts
这相当于执行下面的 wsimport 命令 ( 都在同一行里 ) :
$GLASSFISH_HOME/bin/wsimport -keep-d ./build/classes/client
http://localhost:8080/jaxws-webservice/CalculatorService?WSDL
这将会在 jaxws-techtip 文件夹的 build/classes/client/com/techtip/jaxws/sample 目录下生成以下的制品:
Add.java
Add.class
AddResponse.java
AddResponse.class
Calculator.java
Calculator.class
CalculatorService.java
CalculatorService.class
package-info.java
package-info.class
ObjectFactory.class
ObjectFactory.java
5.3 编绎客户端类
下一步需要做的工作就是编绎客户端类。我们可以通过输入下面的命令来完成这项工作:
ant compile-client
ant 编绎任务将会编绎 client/JAXWSClient 并且把 class 文件写到 build /classes/client 子目录下。它等同于运行下面的命令 ( 都是在同一行 ) :
javac -d ./build/classes/client
-classpath $GLASSFISH_HOME/lib/javaee.jar:
$GLASSFISH_HOME/lib/appserv-ws.jar:
./build/classes/client client/JAXWSClient.java
5 . 4 运行客户端
为了了解这个例子是如何工作的,运行下面的命令:
ant runtest-jaxws
它就相当于在 build/classes/client 文件夹下, 运行下面的命令:
$GLASSFISH_HOME/bin/appclient -mainclass client.JAXWSClient
在 DOS 窗口可以看到类似下面的输出:
runtest-jaxws:
[echo] Executing appclient with client class as
client.JAXWSClient
[exec]Retrieving port from the service
com.techtip.jaxws.sample.CalculatorService@162522b
[exec]Invoking add operation on the calculator port
[exec]Adding : 0 + 10 = 10
[exec]Adding : 1 + 10 = 11
[exec]Adding : 2 + 10 = 12
[exec]Adding : 3 + 10 = 13
[exec]Adding : 4 + 10 = 14
[exec]Adding : 5 + 10 = 15
[exec]Adding : 6 + 10 = 16
[exec]Adding : 7 + 10 = 17
[exec]Adding : 8 + 10 = 18
[exec]Adding : 9 + 10 = 19
all:
BUILD SUCCESSFUL
Total time: 6 seconds