前言:本文不是专门讲述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
时间:2005-12-23
作者:
Russell Miles浏览次数:
3616
本文关键字:
Spring,
Java,
AOP,
crosscutting,
面向方面编程,
横切,
Cuckoo's Egg模式 在本系列的第一部分,我介绍了如何实现面向方面领域的“HelloWorld”:跟踪和记录方面。利用Spring框架所提供的面向方面编程(Aspect-Oriented Programming,AOP)功能,您看到了如何使用before-、after-和基于异常的通知,以及如何使用基于正则表达式的简单切入点。跟踪和记录方面提供了非常不错的上手例子,而本文将进一步介绍一种新的通知形式:around通知。
比起第一部分中介绍的那些通知类型,around形式的通知是一种更具侵入性也更强大的面向对象概念。本文将描述around通知的每个特性,以便您可以在自己的Spring AOP应用程序中正确地使用它。在本文最后,我将向您展示如何使用around通知来截获和改变应用程序中各个特性相互作用的方式,以便实现Cuckoo's Egg(杜鹃的蛋)面向方面设计模式。
概述Spring AOP、IoC和代理
在第一部分,我们快速浏览了Spring的一些AOP特性,而没有阐明Spring如何实现AOP的细节。要理解Spring框架如何运转,尤其是它如何实现其AOP功能,首先您要明白,Spring是一个依赖于控制反转(Inversion of Control,IoC)设计模式的轻量级框架。
注意:本文的目的不是要深入介绍IoC模式,介绍IoC只是为了使您明白该设计模式是如何影响Spring AOP实现的。有关IoC模式的更详细的介绍请参见本文末尾的参考资料。
IoC设计模式的出现已经有一段时间了。一个最明显的例子就是J2EE架构本身。随着企业开发尤其是J2EE平台的出现,应用程序开始依赖于由外部容器所提供的一些特性,比如bean创建、持久性、消息传递、会话以及事务管理。
IoC引入了一个新概念:由组件构成的框架,它与J2EE容器有许多类似之处。IoC框架分离了组件所依赖的功能,并且,根据Sam Newman文章中的说法,提供了“连接组件的‘胶水’”。
对组件所依赖特性的控制 被反转 了,这样外部框架就可以尽可能透明地提供这些特性了。IoC模式真正意识到了从传统的由依赖于功能的组件来负责这些功能,到由独立的框架来配置和提供这些功能的方式转变。
图1显示了一些构成IoC模式的不同组件角色的例子。
图1. 没有对BusinessLogic bean应用方面时的顺序图.
图字:
Component:组件
Provides Facilities:提供功能
Relies on and conforms to:依赖于并服从
Manages the services the framework can then use to provide facilities:管理框架随后可以用来提供功能的服务
Service:服务
Your Component:您的组件
IoC Framework:IoC框架
External services:外部服务
IoC模式使用3种不同的方法来解除组件与服务控制的耦合:类型1、类型2和类型3。
- 类型1:接口注入
这是大部分J2EE实现所使用的方法。组件显式地服从于一组接口,带有关联的配置元数据,以便允许框架对它们进行正确的管理。
- 类型2:Setter注入
外部元数据被用来配置组件相互作用的方式。在第一部分中,我们就是使用这种IoC方法利用springconfig.xml文件来配置Spring组件的。
- 类型3:构造函数注入
组件(包括构造组件时要用的参数)注册到框架,而框架提供组件的实例以及所有要应用的指定功能。
IoC在组件开发和企业开发中越来越受欢迎。IoC的实际例子包括传统的J2EE解决方案,比如:JBoss、Apache基金会的Avalon项目以及本文的Spring框架。实际上,Spring框架构建于IoC模式的基础上是为了帮助将它的轻量级功能注入到它的相关应用程序的组件中。
那么IoC对于Spring AOP有何意义呢?Spring的IoC特性是使用IoC springconfig.xml配置文件对应用程序应用方面的推动因素之一。springconfig.xml配置文件通知Spring框架运行时有关应用程序的组件要被注入的功能类型的信息,所以自然轻量级的AOP功能就以同样的方式应用了。然后Spring使用代理模式围绕现有的类和bean实现指定的AOP功能。
图2显示了Spring及其IoC框架如何使用代理对象提供AOP功能(根据springconfig.xml文件中的IoC配置。)
图2. springconfig.xml配置文件改变了Spring框架IoC,以便随后向第一部分中的一个顺序图提供AOP代理(单击图像查看大图)
在本系列下面的部分,您将不断看到现在包含在顺序图中的代理对象。这只是为了说明对于Spring AOP来说没有“魔法”,实际上只有一个面向对象设计模式的良好例子。
回到AOP:使用around通知的积极方面
在第一部分,您看到了如何使用Spring AOP来实现跟踪和记录方面。跟踪和记录都是“消极”方面,因为它们的出现并不会对应用程序的其他行为产生影响。它们都使用了消极的before和after形式的通知。
但是如果您希望改变应用程序的常规行为呢?例如说,您希望重写一个方法?这样的话,您就需要使用更积极的around形式的通知。
第一部分的简单例子应用程序包括IbusinessLogic接口、BusinessLogic类和MainApplication类,如下所示:
public interface IBusinessLogic
{
public void foo();
}
public class BusinessLogic
implements IBusinessLogic
{
public void foo()
{
System.out.println(
"Inside BusinessLogic.foo()");
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApplication
{
public static void main(String [] args)
{
// Read the configuration file
ApplicationContext ctx =
new FileSystemXmlApplicationContext(
"springconfig.xml");
//Instantiate an object
IBusinessLogic testObject =
(IBusinessLogic) ctx.getBean(
"businesslogicbean");
// Execute the public
// method of the bean
testObject.foo();
}
}
要对一个BusinessLogic类的实例彻底重写对foo()方法的调用,需要创建around通知,如下面的AroundAdvice类所示:
import org.aopalliance.intercept.MethodInvocation;
import org.aopalliance.intercept.MethodInterceptor;
public class AroundAdvice
implements MethodInterceptor
{
public Object invoke(
MethodInvocation invocation)
throws Throwable
{
System.out.println(
"Hello world! (by " +
this.getClass().getName() +
")");
return null;
}
}
要在Spring中用作around通知,AroundAdvice类必须实现MethodInterceptor接口和它的invoke(..)方法。每当截获到方法的重写,invoke(..)方法就会被调用。最后一步是改变包含在应用程序的springconfig.xml文件中的Spring运行时配置,以便可以对应用程序应用AroundAdvice。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Bean configuration -->
<bean id="businesslogicbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>theAroundAdvisor</value>
</list>
</property>
</bean>
<!-- Bean Classes -->
<bean id="beanTarget"
class="BusinessLogic"/>
<!-- Advisor pointcut definition for around advice -->
<bean id="theAroundAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theAroundAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean>
<!-- Advice classes -->
<bean id="theAroundAdvice"
class="AroundAdvice"/>
</beans>
根据该springconfig.xml配置文件,theAroundAdvisor截获所有对BusinessLogic类的方法的调用。接下来,theAroundAdvisor被关联到theAroundAdvice,表明当截获一个方法时,就应该使用在AroundAdvice类中指定的通知。既然已经指定了around通知的正确配置,下一次执行MainApplication类时,BusinessLogic bean的foo()方法就会被截获并重写,如图3所示:
图3. 使用around通知重写对BusinessLogic类中的foo()方法的调用
前面的例子显示,BusinessLogic类中的foo()方法可以通过AroundAdvice类中的invoke(..)方法彻底重写。原来的foo()方法完全不能被invoke(..)方法调用。如果希望从around通知内调用foo()方法,可以使用proceed()方法,可从invoke(..)方法的MethodInvocation参数中得到它。
public class AroundAdvice
implements MethodInterceptor
{
public Object invoke(
MethodInvocation invocation)
throws Throwable
{
System.out.println(
"Hello world! (by " +
this.getClass().getName() +
")");
invocation.proceed();
System.out.println("Goodbye! (by " +
this.getClass().getName() +
")");
return null;
}
}
图4显示了对proceed()的调用如何影响操作的顺序(与图3所示的初始around通知执行相比较)。
图4. 从around通知内使用proceed()调用原来的方法
当调用proceed()时,实际是在指示被截获的方法(在本例中是foo()方法)利用包含在MethodInvocation对象中的信息运行。您可以通过调用MethodInvocation类中的其他方法来改变该信息。
您可能希望更改包含在MethodInvocation类中的信息,以便在使用proceed()调用被截获的方法之前对被截获方法的参数设置新值。
通过对MethodInvocation对象调用getArguments()方法,然后在返回的数组中设置其中的一个参数对象,最初传递给被截获的方法的参数可以被更改。
如果IbusinessClass和BusinessLogic类的foo()方法被更改为使用整型参数,那么就可以将传递给被截获的调用的值由在AroundAdvice的notify(..)方法中传递改为在foo(int)中传递。
public class AroundAdvice
implements MethodInterceptor
{
public Object invoke(
MethodInvocation invocation)
throws Throwable
{
System.out.println(
"Hello world! (by " +
this.getClass().getName() +
")");
invocation.getArguments()[0] = new Integer(20);
invocation.proceed();
System.out.println(
"Goodbye! (by " +
this.getClass().getName() +
")");
return null;
}
}
在本例中,被截获的方法的第一个形参被假设为int。实参本身是作为对象传递的,所以通过将其包装在Integer类实例中的方法,基本的int类型的形参被改为对应数组中的新值。如果您将该参数设置为一个非Integer对象的值,那么在运行时就会抛出IllegalArgumentException异常。
您还将注意到,invoke(..)方法必须包含一个return语句,因为该方法需要返回值。但是,被重写的foo()方法并不返回对象,所以invoke(..)方法可以以返回null结束。如果在foo()方法不需要的情况下,您仍然返回了一个对象,那么该对象将被忽略。
如果foo()方法确实需要返回值,那么需要返回一个与foo()方法的初始返回类型在同一个类或其子类中的对象。如果foo()方法返回一个简单类型,例如,一个integer,那么您需要返回一个Integer类的对象,当方法被重写时,该对象会自动由AOP代理拆箱,如图5所示:
图5. around通知的装箱和自动拆箱
图字:
Object invoke:对象调用
The integer return value is boxed in a Integer object in the AroundAdvice and then unboxed by the AOP Proxy:整型返回值被装箱在AroundAdvic通知的一个Integer对象中,然后由AOP代理拆箱。
面向方面编程还是一个比较新的领域,尤其是与衍生出它的面向对象编程相比。设计模式通常被认为是常见问题的通用解决方案,因为面向方面发展的时间还不长,所以已发现的面向方面设计模式比较少。
此处要介绍的是一种正在浮现的模式,即Cuckoo's Egg设计模式。该模式还有其他的叫法,它在面向对象领域的对等体包括模仿对象(Mock Object)和模仿测试(Mock Testing),甚至代理模式也与它有一些类似之处。
Cuckoo's Egg面向方面设计模式可以被定义为应用程序上下文中功能部件的透明和模块化的置换。就像杜鹃偷偷地把自己的蛋放在另一种鸟的巢中一样,Cuckoo's Egg设计模式用一个替代功能部件实现置换现有的功能部件,而使造成的干扰尽可能少。
这种置换的实现方式可以是静态的、动态的、部分的、完全的,针对一个对象的多个部分,或针对多个组件。使用面向方面的方法可以透明地实现功能部件的置换,而无需对应用程序的其余部分进行更改。要置换应用程序中现有功能部件的替代功能部件就是“杜鹃的蛋”。图6显示了Cuckoo's Egg设计模式中的主要组成元素。
图6. Cuckoo's Egg设计模式中的主要组成元素
图字:
Application:应用程序
Component:组件
Replacement Feature:替代功能部件
Component 1 and 2 together encompass a distinct feature of the software:组件1和2共同包含了软件的一个独立的功能部件
The Cuckoo's Egg pattern transparently replaces an existing feature of the software:Cuckoo's Egg模式透明地置换了软件现有的功能部件
Before the pattern is applied:应用该模式前
After the pattern is applied:应用该模式后
Cuckoo's Egg设计模式依赖于around通知的概念。您需要借助于积极的和侵入性的around通知来截获并有效置换应用程序中现有的功能部件。
有关Cuckoo's Egg设计模式的更多信息,以及AspectJ中的一个可选实现,请参见《AspectJ Cookbook》(O'Reilly,2004年12月出版)。
要使用Spring AOP实现Cuckoo's Egg设计模式,需要声明一个around通知来截获所有对要置换的功能部件的调用。与hot-swappable target sources(Spring AOP的一个功能部件,将在本系列的另一篇文章中介绍)不同,around通知的显式使用使得Cuckoo's Egg实现可以有效地跨越对象边界(因此也可以跨越bean边界)进行整个功能部件的置换,如图7所示。
图7. 一个跨越bean边界的组件
图字:
A feature crosses the boundaries of BusinessLogic and BusinessLogic2 by depending on behavior supplied separately by the two beans:一个功能部件通过依赖于由BusinessLogic和BusinessLogic2各自提供的行为而跨越了这两个bean的边界
下面的代码显示了一个具有两个bean的简单应用程序,其中有一个功能部件跨越了该应用程序的多个方面。要置换的功能部件可以被视为包含IBusinessLogic bean中的foo()方法和IBusinessLogic2 bean中的bar()方法。IBusinessLogic2 bean中的baz()方法不是 该功能部件的一部分,所以不进行置换。
public interface IBusinessLogic
{
public void foo();
}
public interface IBusinessLogic2
{
public void bar();
public void baz();
}
该例子的完整源代码可在本文末尾的参考资料小节中下载。
此处,ReplacementFeature类扮演了“杜鹃的蛋”的角色,它提供了将被透明地引入应用程序的替代实现。ReplacementFeature类实现了所有在该类引入时要被置换的方法。
public class ReplacementFeature
{
public void foo()
{
System.out.println(
"Inside ReplacementFeature.foo()");
}
public void bar()
{
System.out.println(
"Inside ReplacementFeature.bar()");
}
}
现在需要声明一个around通知来截获对跨越bean的功能部件的方法调用。CuckoosEgg类提供了某种around通知来检查被截获的方法,并将适当的方法调用传递给ReplacementFeature类的实例。
public class CuckoosEgg implements MethodInterceptor
{
public ReplacementFeature replacementFeature =
new ReplacementFeature();
public Object invoke(MethodInvocation invocation)
throws Throwable
{
if (invocation.getMethod().getName().equals("foo"))
{
replacementFeature.foo();
}
else
{
replacementFeature.bar();
}
return null;
}
}
因为与Spring框架关系密切,Cuckoo's Egg设计的详细信息被放在springconfig.xml配置文件中。对springconfig.xml文件的更改将确保所有对IbusinessLogic和IBusinessLogic2 bean的foo()方法和bar()方法的调用都将被截获,并传递给CuckoosEgg类的around通知。
...
<!--CONFIG-->
<bean id="businesslogicbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>theCuckoosEggAdvisor</value>
</list>
</property>
</bean>
<bean id="businesslogicbean2"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic2</value>
</property>
<property name="target">
<ref local="beanTarget2"/>
</property>
<property name="interceptorNames">
<list>
<value>theCuckoosEgg2Advisor</value>
</list>
</property>
</bean>
<!--CLASS-->
<bean id="beanTarget" class="BusinessLogic"/>
<bean id="beanTarget2" class="BusinessLogic2"/>
<!--ADVISOR-->
<bean id="theCuckoosEggAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theReplacementFeaturePart1Advice"/>
</property>
<property name="pattern">
<value>IBusinessLogic.*</value>
</property>
</bean>
<bean id="theCuckoosEgg2Advisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theReplacementFeaturePart2Advice"/>
</property>
<property name="pattern">
<value>IBusinessLogic2.bar*</value>
</property>
</bean>
<!--ADVICE-->
<bean id="theReplacementFeaturePart1Advice" class="CuckoosEgg"/>
<bean id="theReplacementFeaturePart2Advice" class="CuckoosEgg"/>
...
当使用修改后的springconfig.xml文件运行例子应用程序时,要替换的、被指定为功能部件的一部分的方法调用完全被截获并传递给ReplacementFeature类。
通常,即使在同一个实现环境中,我们也可以用不同的方法来实现同一种设计模式。实现上例的另一种方法是实现两个独立的通知。
最后需要注意的是,使用Cuckoo's Egg设计模式置换的功能部件,不管它是跨越bean的还是在一个类中,它的生命周期与它所置换的功能部件的目标生命周期匹配。在上例中这没什么问题,因为只有一个功能部件实例被置换了,而且唯一的Cuckoo's Egg通知只维护一个替代功能部件。
这个例子非常简单,而在实践中,您很可能必须处理大量需要用各自的Cuckoo's Egg实例置换的功能部件实例。在这种情况下,单个的方面实例需要被关联到单个的要置换的功能部件实例。本系列的下一篇文章将会考虑方面生命周期的用法,届时将解决这个问题。
结束语
本文介绍了如何在Spring框架内谨慎使用around形式的通知。around形式的通知常用于实现Cuckoo's Egg设计模式时,所以我们引入了一个例子来说明如何使用Spring AOP实现这种面向方面设计模式。
在本系列的第三部分中,您将看到如何使用Spring框架中其他的AOP基本概念。这些概念包括:控制方面生命周期、使用基于introduction通知的积极方面改变应用程序的静态结构,以及使用control flow切入点实现对方面编织的更细微的控制。
参考资料
原文出处
An Introduction to Aspect-Oriented Programming with the Spring Framework, Part 2 http://www.onjava.com/pub/a/onjava/2004/10/20/springaop2.html
作者简介 |
| Russell Miles是General Dynamics UK公司的一名软件工程师,他负责Java和分布式系统,但是他目前主要的兴趣在面向方面领域,尤其是AspectJ。 |
时间:2005-12-16
作者:
Russell Miles浏览次数:
5882
本文关键字:
Java,
Spring,
aspect-oriented programming,
AOP,
crosscutting,
面向方面编程,
横切,
延迟加载 作为这个介绍Spring框架中的面向方面编程(Aspect-Oriented Programming,AOP)的系列的第一部分,本文介绍了使您可以使用Spring中的面向方面特性进行快速开发的基础知识。使用跟踪和记录方面(面向方面领域的HelloWorld)作为例子,本文展示了如何使用Spring框架所独有的特性来声明切入点和通知以便应用方面。本系列的第二部分将更深入地介绍如何运用Spring中的所有通知类型和切入点来实现更实用的方面和面向方面设计模式。对于AOP的更一般性的介绍,请查看ONJava站点上Graham O'Regan的文章,“Introduction to Aspect-Oriented Programming”。
本文的目的不是要介绍构成模块化J2EE系统——即Spring框架——的所有重要元素,我们将只把注意力放在Spring所提供的AOP功能上。由于Spring的模块化设计方法,我们可以只使用该框架的AOP元素,而无需对构成Spring框架的其他模块做太多考虑。
在AOP方面,Spring提供了什么?
“它的目标不是提供最完善的AOP实现(虽然Spring AOP非常强大);而是要提供AOP实现与Spring IoC的紧密集成,以便帮助解决企业应用中的常见问题。”
Spring Framework参考文档
为了实现这个目标,Spring框架目前支持一组AOP概念,从切入点到通知。本文将展示如何使用Spring框架中所实现的如下AOP概念:
- 通知(Advice):如何将before通知、afterReturning通知和afterThrowing通知声明为bean。
- 切入点(Pointcut):如何声明静态切入点逻辑以将XML Spring Bean Configuration文件中的所有内容联系在一起。
- Advisor:关联切入点定义与通知bean的方式。
设置场景:一个简单的例子应用程序
“一般而言,Spring并不是预描述的。虽然使用好的实践非常容易,但是它避免强制推行一种特定的方法。”
Spring Framework参考文档
要试用Spring框架的AOP功能,首先我们要创建一个简单的Java应用程序。IbusinessLogic接口和BusinessLogic类为Spring框架中的bean提供了简易构件块。虽然该接口对于我们的简单应用程序逻辑来说不是必需的,但是它是Spring框架所推荐的良好实践。
public interface IBusinessLogic
{
public void foo();
}
public class BusinessLogic
implements IBusinessLogic
{
public void foo()
{
System.out.println(
"Inside BusinessLogic.foo()");
}
}
可以编写MainApplication类,借此练习BusinessLogic bean的公有方法。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApplication
{
public static void main(String [] args)
{
// Read the configuration file
ApplicationContext ctx =
new FileSystemXmlApplicationContext(
"springconfig.xml");
//Instantiate an object
IBusinessLogic testObject =
(IBusinessLogic) ctx.getBean("businesslogicbean");
// Execute the public
// method of the bean
testObject.foo();
}
}
在BusinessLogic类及其关联接口中没有什么需要注意的。但是,MainApplication类初始化BusinessLogic对象的方式很有意思。通过使用ctx.getBean("businesslogicbean")调用,MainApplication将加载和管理BusinessLogic类的bean实例的任务转交给了Spring框架。
允许Spring控制BusinessLogic bean的初始化,这使得Spring运行时有机会在bean被返回给应用程序之前执行J2EE系统所需的所有与bean相关的管理任务。然后Spring运行时配置可以决定对bean应用哪些任务和模块。该配置信息由一个XML文件提供,类似于下面所示的:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Bean configuration -->
<bean id="businesslogicbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
</bean>
<!-- Bean Classes -->
<bean id="beanTarget"
class="BusinessLogic"/>
</beans>
该配置文件,即springconfig.xml,指定要加载一个接口与IbusinessLogic相匹配的bean。该bean随后被关联到BusinessLogic实现类。看起来好像是费了很大力气只为了加载一个简单的bean并调用一个方法,但是您要知道,这个配置文件只是使Spring框架可以透明地对应用程序应用其组件的众多特性的一个体现。
图1显示了基本的顺序图:MainApplication原样执行,没有应用方面。
图1.没有对BusinessLogic bean应用方面时的顺序图
请查看本文末尾处的参考资料,获取这个简单Spring应用程序的源代码。
应用方法跟踪(Method Tracing)方面
可能最基本的方面就是方法跟踪方面了。这可能是您找得到的最简单的方面了,因此它是研究新的AOP实现的一个很好的起点。
方法跟踪方面在一个目标应用程序内捕获对所跟踪的方法的调用以及方法的返回值,并以某种方式显示这种信息。在AOP中,通知的before和after类型用于捕获这些类型的联结点,因为这两种通知可以在方法调用联结点之前或之后触发。使用Spring框架,方法跟踪方面的before通知是在TracingBeforeAdvice类中声明的。
import java.lang.reflect.Method;
import org.springframework.aop. MethodBeforeAdvice;
public class TracingBeforeAdvice
implements MethodBeforeAdvice
{
public void before(Method m,
Object[] args,
Object target)
throws Throwable
{
System.out.println(
"Hello world! (by " +
this.getClass().getName() +
")");
}
}
类似地,after通知可以在TracingAfterAdvice类中声明。
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class TracingAfterAdvice
implements AfterReturningAdvice
{
public void afterReturning(Object object,
Method m,
Object[] args,
Object target)
throws Throwable
{
System.out.println(
"Hello world! (by " +
this.getClass().getName() +
")");
}
}
这两个类都通过实现Spring框架的适当通知接口而表示了特定的通知。每种类型的通知都指定实现before(..)或afterReturning(..)方法,以便使Spring运行时可以告诉通知适当的联结点会在何时出现。值得注意的是,TracingAfterAdvice实际上是从AfterReturningAdvice扩展而来的,表示只有在联结点在无异常的情况下获得返回值时才运行通知。
为了将通知与应用程序中的适当联结点关联起来,必须对springconfig.xml进行一些修改。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Bean configuration -->
<bean id="businesslogicbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>theTracingBeforeAdvisor</value>
<value>theTracingAfterAdvisor</value>
</list>
</property>
</bean>
<!-- Bean Classes -->
<bean id="beanTarget"
class="BusinessLogic"/>
<!-- Advisor pointcut definition for before advice -->
<bean id="theTracingBeforeAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theTracingBeforeAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean>
<!-- Advisor pointcut definition for after advice -->
<bean id="theTracingAfterAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theTracingAfterAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean<
<!-- Advice classes -->
<bean id="theTracingBeforeAdvice"
class="TracingBeforeAdvice"/>
<bean id="theTracingAfterAdvice"
class="TracingAfterAdvice"/>
</beans>
theTracingBeforeAdvisor和theTracingAfterAdvisor advisor被添加到前面所声明的businesslogicbean。每个advisor都可能截获所有bean所关联到的联结点。Advisor本身就是bean,而它唯一的作用就是将切入点定义与通知bean关联起来。本例中的切入点定义是在静态对象层次结构中指定相关联结点的正则表达式。
因为本例中使用了org.springframework.aop.support.RegexpMethodPointcutAdvisor切入点advisor,切入点逻辑是使用正则表达式指定的。正则表达式用于识别公有接口对IbusinessLogici接口的联结点。下面是一些可以用来指定IBusinessLogic接口上的不同联结点集合的正则表达式例子:
- <value>.*</value>:该表达式选择advisor所关联到的一个或多个bean上的所有联结点。
- <value>./IBusinessLogic/.foo</value>:该表达式只选择IbusinessLogic接口上的foo()方法的联结点。如果是advisor所关联到的bean,则该表达式只选择IBusinessLogic接口上的联结点。
springconfig.xml文件中最后的bean声明指定实现通知bean的类。
既然已经指定了跟踪方面的正确配置,那么下一次执行MainApplication时,这些方面就会在初始化过程中被编织进去,而BusinessLogic bean中的所有方法都将被跟踪,如图2所示。
图2. 方法跟踪方面应用到BusinessLogic bean之后的顺序图(单击图像查看大图)
方法跟踪方面和例子应用程序的源代码可在本文末尾的参考资料小节进行下载。
方面的重用
可以对方法跟踪方面进行扩展,提供一个稍微复杂的记录(Logging)方面。记录方面提供了一个很不错的重用例子,因为记录方面所需的许多特性都已经包含在方法跟踪方面中了。
在本例中,记录方面扩展了方法跟踪方面,以便显示附加的与(在应用程序的执行过程中)所引发的异常有关的信息。
要完全使用记录方面,需要对应用程序做一些更改。BusinessLogicException异常类提供了一个可以由IBusinessLogicInterface接口和BusinessLogic实现类新增的void bar()方法引发的异常。
public class BusinessLogicException
extends Exception
{
}
public interface IBusinessLogic
{
public void foo();
public void bar()
throws BusinessLogicException;
}
public class BusinessLogic
implements IBusinessLogic
{
public void foo()
{
System.out.println(
"Inside BusinessLogic.foo()");
}
public void bar()
throws BusinessLogicException
{
System.out.println(
"Inside BusinessLogic.bar()");
throw new BusinessLogicException();
}
}
MainApplication类现在将对void bar()方法进行一次额外的调用,并处理选中的、可能由该方法引发的异常。
import org.springframeworkcontext.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApplication
{
public static void main(String [] args)
{
// Read the configuration file
ApplicationContext ctx =
new FileSystemXmlApplicationContext(
"springconfig.xml");
//Instantiate an object
IBusinessLogic testObject =
(IBusinessLogic) ctx.getBean(
"businesslogicbean");
//Execute the public methods of the bean
testObject.foo();
try
{
testObject.bar();
}
catch(BusinessLogicException ble)
{
System.out.println(
"Caught BusinessLogicException");
}
}
}
来自方法跟踪方面的TracingBeforeAdvice和TracingAfterAdvice通知可以整体重用。LoggingThrowsAdvice类为新的异常记录提供了通知。
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class LoggingThrowsAdvice
implements ThrowsAdvice
{
public void afterThrowing(Method method,
Object[] args,
Object target,
Throwable subclass)
{
System.out.println(
"Logging that a " +
subclass +
"Exception was thrown.");
}
}
应用记录方面的最后一步是修改springconfig.xml配置文件,使其包含新添加的LoggingThrowsAdvice通知。
图3显示了运行MainApplication并使用Spring框架应用了记录方面的UML顺序图。
图3. 记录方面应用到BusinessLogic bean之后的顺序图(单击图像查看大图)
此处的记录方面清楚地说明了如何重用现有方面以及如何在Spring框架中使用通知的throws形式。通过为before和after通知声明新的通知来重写现有的方法跟踪方面实现,可以实现更复杂的记录方面,记录到更复杂的记录框架,比如LOG4J。关于记录方面和例子应用程序的源代码,请参见本文末尾的参考资料小节。
结束语
本文展示了使用Spring框架中的基本AOP结构所应用的一些简单方面。在本系列的下一篇文章中,我们将介绍一些更实用的方面,探讨方面的生命周期,使用Spring框架的around通知,并使用Spring来应用AOP模式。
参考资料
原文出处:An Introduction to Aspect-Oriented Programming with the Spring Framework, Part 1http://www.onjava.com/pub/a/onjava/2004/07/14/springaop.html
作者简介 |
| Russell Miles是General Dynamics UK公司的一名软件工程师,他负责Java和分布式系统,但是他目前主要的兴趣在面向方面领域,尤其是AspectJ。 |
在本文中,作者通过一个Web Service访问的实例,具体描述了SOA应用中所遇到的一系列具体问题,并描述如何利用IoC和AOP等技术进行代码重构,从而构建结构更加良好、灵活的SOA应用。
1.引言
SOA是一种构造分布式系统的方法,它将业务应用功能以服务的形式提供出来,以便更好的复用、组装和与外部系统集成,从而降低开发成本,提高开发效率。SOA的目标是为企业构建一个灵活,可扩展的IT基础架构来更好地支持随需应变的商务应用。
随着SOA技术和产品的不断成熟,现在越来越多的用户开始了解并认同SOA的理念,但对SOA项目的实施还缺乏信心。其主要原因是:SOA应用开发还相对比较复杂。
一年多来,本文作者所在的部门已经从事了许多国内外的SOA项目的实施和支持工作,积累了许多SOA应用开发经验。我们希望能够通过一系列的文章与读者分享这些想法,帮助您更好地构建SOA应用。
本文将从Web Service调用入手,在解决一系列具体问题的过程中,使用IoC (Inversion of Control) 和AOP (Aspect- Oriented Programming) 等方法重构Web Service的访问代码,使得业务逻辑与Web Service访问解耦,为您提供一个更加灵活和易于扩展的访问模式。
Spring是一个流行的轻量级容器,对IoC和AOP提供了良好的支持。本文为您提供了一个基于Spring的实现供您下载学习。示例代码工程使用Eclipse3.1/3.02和JDK1.4开发, 您还需要Spring 1.2.5和Axis1.3提供的支持。详细的下载信息请参见参考资源部分。
2.Web Service调用
Web Service是目前实现SOA应用的一项基本的,适用的技术,它为服务的访问提供了一个被广泛接受的开放标准。为了便于说明问题,我们将使用XMethods 网站(http://www.xmethods.net/)发布的货币兑换服务作为示例。并针对JAX-RPC 1.1,说明如何编写Web Service 的调用代码。
2.1 示例说明
http://xmethods.net 作为最早推出Web Service实际示例的网站,提供了很多优秀的Web Service 样例。其中有一个汇率计算服务,可以返回两个国家之间的货币兑换比例。获取该服务的详细信息,请参考该服务的服务描述文档(获取WSDL 文档) 。在此就不具体解析该服务描述文档了。读者可以从WSDL2Java生成的接口中了解该服务的用法:
public interface CurrencyExchangePortType extends java.rmi.Remote {
public float getRate(String country1, String country2) throws java.rmi.RemoteException;
}
|
2.2 客户端调用方法
JAX-RPC作为Java平台的RPC服务调用标准接口,为Web Service客户端调用提供了3种方法,分别是DII,动态代理,和静态Stub。 DII(Dynamic Invocation Interface)采用直接调用方式,可以在程序中设置诸多的调用属性,使用较为灵活,但是调用过程却相对繁琐复杂,易造成代码膨胀且可重用性低,每次调用不同的Web Service都要重复进行大量编码。
JAX-RPC中动态代理(Dynamic Proxy)的方法实现对Web Service的动态调用,可以在运行时根据用户定义的Client端接口创建适配对象。从而避免了直接操作底层的接口,减少了客户端的冗余,屏蔽了调用相关的复杂性。
使用静态Stub和Service Locator是目前最常用的调用方式。JAX-RPC使用静态的Stub方式包装对底层接口的调用,从而提供一种更为简便的调用方式。使用该方式需要利用支持环境(比如Axis)所提供的工具根据WSDL预生成Web Service客户端的实现代码。因此如果服务的WSDL发生变化,就必须重新生成新的客户端代码并进行重新部署。
为了更详细的了解静态Stub的调用方式,您可以将示例代码的WebServiceClient.jar导入到您现有Eclipse工作区之中。
客户端生成代码包括如下4个类:如图 1 所示:
图 1: 客户端代码类图
在上图中包括的几个类中:
CurrencyExchangePortType:服务端点接口,定义了Web Service的方法签名。
CurrencyExchangeService:Service接口,定义了获取服务端点接口的方法。
CurrencyExchangeServiceLocator:ServiceLocator类,实现了Service接口。
CurrencyExchangeBindingStub: Stub实现类,实现了服务端点接口,封装了对Web Service访问的底层逻辑。
使用Stub调用Web Service的过程也非常简单,读者可以参考清单 1:
清单 1:Web Service 调用代码示例
try {
//创建ServiceLocator
CurrencyExchangeServiceLocator locator = new
CurrencyExchangeServiceLocator();
//设定端点地址
URL endPointAddress = new URL("http://services.xmethods.net:80/soap");
//创建Stub实例
CurrencyExchangePortType stub =
locator.getCurrencyExchangePort(endPointAddress);
//设定超时为120秒
((CurrencyExchangeBindingStub)stub).setTimeout(120000);
//调用Web Service计算人民币与美元的汇率
float newPrice = stub.getRate("China", "USA") * 100;
} catch (MalformedURLException mex) {
//...
} catch (ServiceException sex) {
//...
} catch (RemoteException rex) {
//...
}
|
3.重构Web Service调用代码
3.1 实例代码中的"坏味道"
上面的基于Service Locator的Web Service访问代码虽然简单但暴露出以下几个问题:
1.访问Web Service所需的配置代码被嵌入应用逻辑之中
在Web Service调用中,我们需要设定一系列必要的参数。比如:服务端点地址、用户名/密码、超时设定等等。这些参数在开发和运行环境中都有可能发生变化。我们必须提供一种机制:在环境变化时,不必修改源代码就可以改变Web Service的访问配置。
2 客户端代码与Web Service访问代码绑定
在上面的代码中,业务逻辑与Web Service的Stub创建和配置代码绑定在一起。这也不是一种良好的编程方式。客户端代码只应关心服务的接口,而不应关心服务的实现和访问细节。比如,我们既可以通过Web Service的方式访问远程服务,也可以通过EJB的方式进行访问。访问方式对业务逻辑应该是透明的。
这种分离客户端代码与服务访问代码的方式也有利于测试。这样在开发过程中,负责集成的程序员就可能在远程服务还未完全实现的情况下,基于服务接口编写集成代码,并通过编写POJO(Plain Old Java Object)构建伪服务实现来进行单元测试和模拟运行。这种开发方式对于保证分布式系统代码质量具有重要意义。
因此,为了解决上面的问题我们需要:
1、将Web Service访问的配置管理与代码分离;
2、解除客户端代码与远程服务之间的依赖关系;
3.2 利用IoC模式进行重构代码
我们先介绍在Core J2EE Patterns一书中提到的一种业务层模式:Business Delegate。它所要解决的问题是屏蔽远程服务访问的复杂性。它的主要思想就是将Business Delegate作为远程服务的客户端抽象,隐藏服务访问细节。Business Delegate还可以封装并改变服务调用过程,比如将远程服务调用抛出的异常(例如RemoteException)转换为应用级别的异常类型。
其类图如图 2 所示:
图 2:Business Delegate 模式的类图图解
Business Delegate模式实现很好地实现了客户端与远程访问代码的解耦,但它并不关注Delegate与远程服务之间的解耦。为了更好解决Business Delegate和远程服务之间的依赖关系,并更好地进行配置管理,我们可以用IoC模式来加以解决。
IoC(Inversion of Contro)l意为控制反转,其背后的概念常被表述为"好莱坞法则":"Don't call me, I'll call you." IoC将一部分责任从应用代码交给framework(或者控制器)来做。通过IoC可以实现接口和具体实现的高度分离,降低对象之间的耦合程度。Spring是一个非常流行的IoC容器,它通过配置文件来定义对象的生命周期和依赖关系,并提供了良好的配置管理能力。
现在我们来重构我们的Web Service应用程序,我们首先为Business Delegate定义一个接口类型,它提供了一个应用级组件接口,所有客户端都应通过它来执行汇率计算,而不必关心实现细节,如清单 2 所示:
清单 2:接口定义的代码示例
Public interface CurrencyExchangeManager {
//货币兑换计算
//新价格 = 汇率 * 价格
public float calculate(String country1, String country2, float price)
throws CurrencyExchangeException;
}
|
Business Delegate的实现非常简单,主要工作是包装汇率计算 Web Service的调用,如清单 3 所示。
清单 3:Business Delegate的代码示例
public class CurrencyExchangeManagerImpl implements CurrencyExchangeManager {
//服务实例
private CurrencyExchangePortType stub;
//获取服务实例
public CurrencyExchangePortType getStub() {
return stub;
}
//设定服务实例
public void setStub(CurrencyExchangePortType stub) {
this.stub = stub;
}
//实现货币兑换
public float calculate(String country1, String country2, float price)
throws CurrencyExchangeException {
try {
//通过Stub调用WebService
float rate = stub.getRate(country1, country2);
return rate * price;
} catch (RemoteException rex) {
throw new CurrencyExchangeException(
"Failed to get exchange rate!", rex);
}
}
}
|
下面我们需要讨论如何利用Spring的IoC机制,来创建和配置对象,并定义它们的依赖关系。
Spring利用类工厂来创建和配置对象。在Spring框架中,已经为基于JAX-RPC的Web Service调用提供了一个客户端代理的类工厂实现:JaxRpcPortProxyFactoryBean。在配置文件bean.xml中,我们将使用JaxRpcPortProxyFactoryBean来创建和配置Web Service的客户端代理"CurrencyExchangeService",如清单 5 所示。我们还将定义一个名为"CurrencyExchangeManager"的CurrencyExchangeManagerImpl实例,并建立它与CurrencyExchangeService之间的依赖关系。有关Spring 配置和JaxRpcPortProxyFactoryBean的使用细节请参见参考资料。
清单 5:bean.xml的配置文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="CurrencyExchangeService"
class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.
CurrencyExchangePortType</value>
</property>
<property name="wsdlDocumentUrl">
<value>http://www.xmethods.net/sd/2001/CurrencyExchangeService.
wsdl</value>
</property>
<property name="namespaceUri">
<value>http://www.xmethods.net/sd/CurrencyExchangeService.
wsdl</value>
</property>
<property name="serviceName">
<value>CurrencyExchangeService</value>
</property>
<property name="portName">
<value>CurrencyExchangePort</value>
</property>
<property name="endpointAddress">
<value>http://services.xmethods.net:80/soap</value>
</property>
</bean>
<bean id="CurrencyExchangeManager"
class="test.ws.CurrencyExchangeManagerImpl">
<property name="stub">
<ref bean="CurrencyExchangeService"/>
</property>
</bean>
</beans>
|
最后我们创建一个测试程序来验证我们的代码,如清单6 所示:
清单 6:测试代码
public class Main {
// For test only
public static void main(String[] args) {
// Spring Framework将根据配置文件创建并配置CurrencyExchangeManager实例
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
// 获取CurrencyExchangeManager实例
CurrencyExchangeManager manager = (CurrencyExchangeManager) ctx
.getBean("CurrencyExchangeManager");
try {
System.out.println(manager.calculate("China", "USA", 100));
System.out.println(manager.calculate("China", "Japan", 200));
System.out.println(manager.calculate("China", "USA", 200));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
|
此时运行测试客户端,等待片刻将会看见测试结果,如清单 7 所示:
清单 7:测试结果。
注:该结果会随着汇率的变化而出现不同的值。
该程序的类图和顺序图如图3及图4所示:
图 3:示例程序的类图
从上面的类图我们可以看到,我们的测试程序(Main.java)通过Spring框架获取了BusinessDelegate的实例。而且Spring 框架还会根据配置中的依赖关系,在运行时将Web Service的客户端代理" 注射"到CurrencyExchangeManagerImpl实例中,这就是依赖注入(Dependency Injection)。通过这种方式解决了应用逻辑和BusinessDelegate之间的依赖关系,以及BusinessDelegate的实现与远程服务之间的依赖关系,如图 4 所示。
图 4: 示例程序的顺序图
Spring框架提供的ApplicationContext实现会根据配置文件中的描述信息来实现对象生命周期管理,配置管理以及依赖管理等功能。这一切对于应用程序是透明的,应用程序代码只依赖接口进行编程,而无需考虑其它复杂问题。无论是Web Service的配置发生变化,或是改用不同的服务实现时,都不会对客户端应用代码的产生影响。这很好地实现了业务逻辑与Web Service调用之间的解耦。
3.3 构建自己的 Web Service代理工厂
Spring所提供的JaxRpcPortProxyFactoryBean封装了构造Web Service客户端代理的细节,可以通过参数配置来创建Dynamic Proxy和DII类型的Web Service客户端代理。(如果您希望深入了解其实现细节可以参考org.springframework.remoting.jaxrpc包下的源代码。)但由于JaxRpcPortProxyFactoryBean需要使用者对WSDL中Port,Service,名空间等概念有深入的了解;而且如果Web Service使用了复杂数据类型,开发人员需要手工定义类型映射代码。所以JaxRpcPortProxyFactoryBean并不适合Web Service的初学者来使用。
为了进一步简化Web Service代理的创建,并帮助读者更好地理解类工厂在Spring框架下的作用。我们提供了一个基于静态Stub的Web Service客户端代理工厂实现。其核心代码非常简单,就是通过ServiceLocator提供的方法来创建Web Service客户端代理。
其主要代码如清单8所示:
清单8:静态代理工厂的代码
public class WebServiceStubFactoryBean implements FactoryBean,
InitializingBean {
private Class serviceInterface;
private Class serviceLocator;
private Object stub;
…
public void afterPropertiesSet() throws Exception {
//利用serviceLocator和服务接口创建Web Service客户端代理
stub = ((javax.xml.rpc.Service)
serviceLocator.newInstance()).getPort(serviceInterface);
//为Stub设定endpointAddress,usernam, 超时等参数
preparePortStub((javax.xml.rpc.Stub) stub);
}
public Object getObject() {
// 返回客户端代理
return stub;
}
public Class getObjectType() {
// 返回服务接口
return serviceInterface;
}
public boolean isSingleton() {
return true;
}
}
|
我们需要修改配置文件bean.xml中有关Web Service代理创建的部分,让新的Web Service 代理工厂发挥作用。如清单9所示:
清单9:修改后的bean.xml的配置文件
<bean id="CurrencyExchangeService" class="test.ws.WebServiceStubFactoryBean">
<property name="serviceInterface">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.CurrencyExchangePortType</value>
</property>
<property name="serviceLocator">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.CurrencyExchangeServiceLocator</value>
</property>
<property name="endpointAddress2">
<value>http://services.xmethods.net:80/soap</value>
</property>
<property name="timeout">
<value>120000</value>
</property>
</bean>
|
得益于Spring框架,虽然我们已经替换了对象的类工厂,却并不需要更改应用代码。通过Spring框架的IoC机制,我们可以完全使用面向接口的编程方式,而将实现的创建、配置和依赖管理交由Spring在运行时完成。即使实现发生了变化,也不需要改变应用程序结构。
4.新的思考
故事并没有结束,在开发过程中,我们又遇到了一系列关于Web Service调用的问题。
4.1性能
系统性能是分布式应用中的一个重要问题。许多用户都担心由Web Service技术所引入的额外开销是否会影响到产品的性能。随着技术的不断发展,Web Service引擎性能已经有了很大提高,一般来说使用Web Service的系统的性能可以满足绝大部分应用的需求。但在特定情况下,如果系统性能无法满足客户需求,我们首先需要对系统性能进行科学地分析和测定才能定位真正的性能瓶颈。这个问题在上文简单的示例中并不难解决,只需要在Web Service调用前后加入日志代码记录调用时间即可实现。但在实际系统中,比如一个产品目录的Web Service可能提供数十种查询方法,而程序中很多组件都会依赖于该服务提供的查询功能。如果在系统中所有的地方加入性能测定代码,这个工作就变得非常繁琐和困难。我们需要用一种更加优雅的解决方式,在增添新功能的同时并不影响系统代码或结构。
4.2缓存
在项目实践中,一个有效的改善Web Service系统性能的方法就是利用缓存来减少Web Service的重复调用。在具体实现中我们可以采用客户端缓存和服务器端缓存等不同方式,他们具有不同的特点和适用范围。在本文例子中,我们希望实现客户端缓存来提高系统性能。但由于Web Service业务逻辑的差别,我们希望能够为特定的Web Service提供特定的缓存策略,而且这些策略应该是能够被灵活配置的,它们不应于应用程序的逻辑代码耦合在一起。
4.3故障恢复:
对于Web Service应用,系统的可用性也是一个需要考虑的重要问题。在运行时由于网络运行环境的复杂性和不确定性,用户希望能够对Web Service访问提供一定的故障恢复机制:比如重试或者访问备份服务(当系统在调用Web Service失败后,使用备份Web Service的服务地址来继续访问)。这些故障恢复策略应该是可配置的,对应用逻辑透明的。
5.使用AOP解决SOA应用中的Crosscutting Concern
通过对上边一系列问题的分析,读者也许会发现这些问题并不是Web Service访问的核心问题,但会影响系统中许多不同的组件。而且其中一些问题需要我们能够灵活配置不同的实现策略,因此我们不应该将处理这些问题的代码与应用代码混合。
下面我们将利用AOP(Aspect-Oriented Programming)提供的方法来解决上述的问题。AOP是一种新兴的方法学,它最基本的概念就是关注隔离(Separation of Concern)。AOP提供了一系列的技术使得我们能够从代码中分离那些影响到许多系统模块的crosscutting concerns,并将他们模块化为Aspects。AOP的主要目的仍然是解耦,在分离关注点后,才能将关注点的变更控制一定范围内,增加程序的灵活性,才能使得关注能够根据需求和环境作出随时调整。
我们将利用Spring所提供的AOP功能支持来解决以上问题。这里我们只简单地介绍涉及到的AOP基本概念以及实现,如果您希望更好地了解AOP的概念以及Spring AOP支持的细节请参见参考资料。
- Joinpoint 是程序的运行点。在Spring AOP中,一个Joinpoint对应着一个方法调用。
- Advice 定义了AOP框架在特定的Joinpoint的处理逻辑。Spring AOP框架通过interceptor方式实现了advice,并且提供了多种advice类型。其中最基本的"around advice"会在一个方法调用之前和之后被执行。
下面我们将利用Spring提供的MethodInterceptor来为Web Service调用实现我们的定义的处理逻辑。
5.1 PerformanceMonitorInterceptor
性能测量是AOP最简单的例子之一,我们可以直接利用Spring提供的实现在bean.xml中声明我们的WebServicePerformanceMonitorInterceptor。
5.2 CacheInterceptor
为了不引入缓存策略的复杂性,我们只提供了一个利用HashMap的简单实现:它利用 Web Service的调用参数列表作为HashMap键值。在Web Service调用之前,首先检查缓存中是否拥有与现在参数列表相同的项,如果有则返回缓存的结果,否则调用Web Service并将<参数列表,结果>记录在HashMap中。在实际应用中,您应该根据具体情况来选择、构造适合Web Service的业务特性的Cache实现,也可以采用成熟的Cache实现。
在下面代码实现中有一个生成Web Service调用主键的小技巧。因为Web Service引擎要求所有调用参数必须是可序列化的,所以我们可以利用Java提供的序列化功能来实现对象的克隆。如清单10所示:
清单10:SimpleCacheInterceptor的代码示例
public class SimpleCacheInterceptor implements MethodInterceptor {
private Map cache = new HashMap();
private Object cloneObject(Object obj) throws Exception {
Object newObj = null;
if (obj != null) {
// 通过序列化/反序列化来克隆对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(obj);
out.flush();
out.close();
ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()));
newObj = in.readObject();
}
return newObj;
}
//基于参数列表数组,生成用于HashMap的键值
public Object generateKey(Object[] args) throws Exception {
Object[] newArgs = (Object[]) cloneObject(args);
List key = Arrays.asList(newArgs);
return key;
}
//实现使用缓存技术的invoke方法
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
Object data = null;
Object key = null;
try {
key = generateKey(methodInvocation.getArguments());
data = cache.get(key);
} catch (Exception ex) {
logger.error("Failed to find from the cache", ex);
}
if (data == null) {
//如果Cache中没有缓存结果,调用服务执行生成用于HashMap的键值
result = methodInvocation.proceed();
try {
data = cloneObject(result);
cache.put(key, data);
} catch (Exception ex) {
logger.error("Failed to cache the result!", ex);
}
} else {
result = data;
}
return result;
}
}
|
5.3 FailoverInterceptor
下面代码提供了一个基于服务备份切换的故障恢复实现,在运行时,如果Interceptor检测到服务调用由于网络故障抛出异常时,它将使用备份服务的端点地址并重新调用。如清单11所示:
清单 11: SimpleFailoverInterceptor的代码示例
public class SimpleFailoverInterceptor implements MethodInterceptor { …
…
//实现支持端点运行时切换的invoke方法
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
try {
result = methodInvocation.proceed();
} catch (Throwable ex) {
if (isNetworkFailure(ex)) {
//切换服务端点地址
switchEndPointAddress((Stub) methodInvocation.getThis());
result = methodInvocation.proceed();
} else {
throw ex;
}
}
return result;
}
}
|
为了支持备份服务切换的功能,我们在WebServicePortProxyFactoryBean中为填加了配置参数"endpointAddress2",它会在创建的Web Service客户端代理对象中记录备份URL。
我们可以在CurrencyExchangeService加入下列参数来试验SimpleFailoverInterceptor的功能。其中第一个端点地址为一个错误的URL。在第一次调用服务时,SimpleFailoverInterceptor会侦测到网络故障的发生,并自动切换使用第二个端点地址继续访问。如清单12所示:
清单12:配置文件种增加的属性
<property name="endpointAddress">
<value>http://localhost/wrong_endpoint_address</value>
</property>
<property name="endpointAddress2">
<value>http://services.xmethods.net:80/soap</value>
</property>
|
5.4配置文件和运行结果
现在我们需要在Spring配置文件中,为所有interceptor添加定义,并描述如何为CurrencyExchangeService构建AOP Proxy。需要指出的是,我们要在interceptorName列表中声明interceptor链的调用顺序,还要将原有CurrencyExchangeManager引用的stub对象替换为新AOP Proxy。如清单13所示:
清单13:修改后的配置文件片段
<bean id="WebServicePerformanceMonitorInterceptor"
class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor">
<property name="prefix">
<value>Web Service </value>
</property>
<property name="suffix">
<value></value>
</property>
</bean>
<bean id="CacheInterceptor" class="test.ws.SimpleCacheInterceptor"/>
<bean id="FailoverInterceptor" class="test.ws.SimpleFailoverInterceptor"/>
<bean id="CurrencyExchangeProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.
CurrencyExchangePortType</value>
</property>
<property name="target">
<ref local="CurrencyExchangeService"/>
</property>
<property name="interceptorNames">
<list>
<value>WebServicePerformanceMonitorInterceptor</value>
<value>CacheInterceptor</value>
<value>FailoverInterceptor</value>
</list>
</property>
</bean>
<bean id="CurrencyExchangeManager"
class="test.ws.CurrencyExchangeManagerImpl">
<property name="stub">
<ref bean="CurrencyExchangeProxy"/>
</property>
</bean>
|
这里我们通过为AOP 的ProxyFactoryBean为 Web Service Stub创建了一个AOP代理,并且建立了一个Interceptor链。这样在调用Web Service时,Spring框架会依次调用Interceptor执行。实例执行的顺序图将如图5所示:
图5系统运行顺序图
5.5 Interceptor与JAX-RPC Handler的关系与区别
SOAP Message Handler是JAX-RPC为用户自定义Web Service处理过程提供的一种扩展机制。在处理Web Service请求/响应过程中,Web Service 引擎会根据部署描述中的定义,按照一定的次序调用Handler的处理代码。用户编写的Handler实现可以截获并修改Web Service消息和处理流程,从而实现对Web Service引擎处理行为的定制和增强。
比如,我们可以实现一个服务器端Handler,记录Web Service在受到请求消息和发出响应消息之间的时间间隔来实现对服务器端业务性能的测定。而且我们只需在部署描述中增加Handler声明即可,无需修改任何服务器端代码。
从此可以看出,JAX-RPC Handler与我们在上文中所提供的AOP Interceptor都可以帮助我们的SOA应用程序实现关注分离(Separate Concern)的目标,在不改变应用代码的同时,增强或改变Web Service服务访问的功能。虽然我们可以利用它们实现一些类似的功能,但它们具有着不同的特点和适用范围。
JAX-RPC Handler是Web Service引擎的扩展机制。如果我们需要实现对SOAP消息进行的修改和处理,加入自定义的SOAP Header或对消息内容进行加密,Handler是我们的最佳选择。而AOP是针对对象级别的扩展机制,它更适合对应用层逻辑进行操作。
比如,我们在上文展示的利用AOP实现的CacheInterceptor,它缓存的是Web Service调用参数和结果。而我们也可以通过JAX-RPC Handler实现一个面向SOAP消息的实现,它将缓存Web Service的请求消息和响应消息。这两个实现相比,基于AOP的实现更加简单、直观、快速、对资源消耗也比较小。而面向SOAP消息的实现则更加灵活,对于不采用RPC方式的Web Service访问也能提供支持。
所以在具体的实践过程中,开发人员应该根据具体的需求选择合适的技术,也可以将这两种技术结合使用。
6.总结
"分而治之"的方法是人们解决复杂问题的一种常见做法。而IoC、AOP等技术都体现了这种思想。通过更好的切分程序逻辑,使得程序结构更加良好,更加富有弹性,易于变化。也使得开发人员可以更加专注于业务逻辑本身,而将一部分其他逻辑交给容器和框架进行处理。
在本文中,我们通过一个Web Service访问的实例,具体描述了SOA应用中所遇到的一系列具体问题,并描述如何利用IoC和AOP等技术进行代码重构,构建更加结构良好、灵活的SOA应用。综上所述,我们可以看到:
1使用IoC框架来实现对象的生命周期管理、配置管理和依赖管理,可以解除业务逻辑对服务调用的依赖关系;
2 使用AOP方法来解决Web Service调用中的crosscutting concerns,将为系统增加新的功能而不必更改应用程序。
3通过IoC和AOP来屏蔽Web Service访问的复杂性,使得开发人员可以更加专注于业务逻辑本身,也使得系统更加稳定和富有弹性。
代码:下载
由于项目需要,需要做一些类似javascript中eval的方法的计算。
找到beanshell感觉很好用,跟大家分享!
请参考beanshell:
http://www.beanshell.org以下是demo:
/**
* gf测试宏计算
* @return
*/
public void testBshInterpreter(){
Interpreter interpreter= new Interpreter();
//String bStr="(100>50 && 100<101)";
try {
//1
interpreter.set("add", interpreter.eval("(100+101)"));
System.out.println(interpreter.get("add"));
interpreter.set("boolean", interpreter.eval("(100>101)"));
System.out.println(interpreter.get("boolean"));
//2
Interpreter i = new Interpreter(); // Construct an interpreter
i.set("foo", 5); // Set variables
i.set("date", new Date() );
Date date = (Date)
i.get("date"); // retrieve a variable
// Eval a statement and get the result
i.eval("bar = foo*10");
System.out.println( i.get("bar") );
} catch (EvalError e) {
e.printStackTrace();
}
}
public class GetCh2Spell { public static int compare(String str1, String str2) { int result = 0; String m_s1 = null; String m_s2 = null; try { m_s1 = new String(str1.getBytes(_FromEncode_), _ToEncode_); m_s2 = new String(str2.getBytes(_FromEncode_), _ToEncode_); } catch(Exception e) { return str1.compareTo(str2); } result = chineseCompareTo(m_s1, m_s2); return result; }
public static int getCharCode(String s) { if(s == null && s.equals("")) return -1; byte b[] = s.getBytes(); int value = 0; for(int i = 0; i < b.length && i <= 2; i++) value = value * 100 + b[i];
return value; }
public static int chineseCompareTo(String s1, String s2) { int len1 = s1.length(); int len2 = s2.length(); int n = Math.min(len1, len2); for(int i = 0; i < n; i++) { int s1_code = getCharCode(s1.charAt(i) + ""); int s2_code = getCharCode(s2.charAt(i) + ""); if(s1_code * s2_code < 0) return Math.min(s1_code, s2_code); if(s1_code != s2_code) return s1_code - s2_code; }
return len1 - len2; }
public static String getBeginCharacter(String res) { String a = res; String result = ""; for(int i = 0; i < a.length(); i++) { String current = a.substring(i, i + 1); if(compare(current, "\u554A") < 0) result = result + current; else if(compare(current, "\u554A") >= 0 && compare(current, "\u5EA7") <= 0) if(compare(current, "\u531D") >= 0) result = result + "z"; else if(compare(current, "\u538B") >= 0) result = result + "y"; else if(compare(current, "\u6614") >= 0) result = result + "x"; else if(compare(current, "\u6316") >= 0) result = result + "w"; else if(compare(current, "\u584C") >= 0) result = result + "t"; else if(compare(current, "\u6492") >= 0) result = result + "s"; else if(compare(current, "\u7136") >= 0) result = result + "r"; else if(compare(current, "\u671F") >= 0) result = result + "q"; else if(compare(current, "\u556A") >= 0) result = result + "p"; else if(compare(current, "\u54E6") >= 0) result = result + "o"; else if(compare(current, "\u62FF") >= 0) result = result + "n"; else if(compare(current, "\u5988") >= 0) result = result + "m"; else if(compare(current, "\u5783") >= 0) result = result + "l"; else if(compare(current, "\u5580") >= 0) result = result + "k"; else if(compare(current, "\u51FB") > 0) result = result + "j"; else if(compare(current, "\u54C8") >= 0) result = result + "h"; else if(compare(current, "\u5676") >= 0) result = result + "g"; else if(compare(current, "\u53D1") >= 0) result = result + "f"; else if(compare(current, "\u86FE") >= 0) result = result + "e"; else if(compare(current, "\u642D") >= 0) result = result + "d"; else if(compare(current, "\u64E6") >= 0) result = result + "c"; else if(compare(current, "\u82AD") >= 0) result = result + "b"; else if(compare(current, "\u554A") >= 0) result = result + "a"; }
return result; }
public static String getFirstStr(String str) { char a = str.charAt(0); char aa[] = { a }; String sss = new String(aa); if(Character.isDigit(aa[0])) sss = "data"; else if(a >= 'a' && a <= 'z' || a >= 'A' && a <= 'Z') sss = "character"; else sss = getBeginCharacter(sss); return sss; }
private static String _FromEncode_ = "GBK"; private static String _ToEncode_ = "GBK";
}
|
Hibernate3.0 采用新的基于ANTLR的HQL/SQL查询翻译器,在Hibernate的配置文件中,hibernate.query.factory_class属性用来选择查询翻译器。
(1)选择Hibernate3.0的查询翻译器:
hibernate.query.factory_class= org.hibernate.hql.ast.ASTQueryTranslatorFactory
(2)选择Hibernate2.1的查询翻译器
hibernate.query.factory_class= org.hibernate.hql.classic.ClassicQueryTranslatorFactory
为了使用3.0的批量更新和删除功能,只能选择(1)否则不能解释批量更新的语句。选择(2)但没法解释批量更新语句了。
大批量更新/删除(Bulk update/delete)
就像已经讨论的那样,自动和透明的 对象/关系 映射(object/relational mapping)关注于管理对象的状态。 这就意味着对象的状态存在于内存,因此直接更新或者删除 (使用 SQL 语句 UPDATE 和 DELETE) 数据库中的数据将不会影响内存中的对象状态和对象数据。 不过,Hibernate提供通过Hibernate查询语言来执行大批 量SQL风格的(UPDATE)和(DELETE) 语句的方法。
UPDATE 和 DELETE语句的语法为: ( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?。 有几点说明:
-
在FROM子句(from-clause)中,FROM关键字是可选的
-
在FROM子句(from-clause)中只能有一个类名,并且它不能有别名
-
不能在大批量HQL语句中使用连接(显式或者隐式的都不行)。不过在WHERE子句中可以使用子查询。
-
整个WHERE子句是可选的。
举个例子,使用Query.executeUpdate()方法执行一个HQL UPDATE语句:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法 (此方法是为 那些熟悉JDBC PreparedStatement.executeUpdate() 的人们而设定的)
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
由Query.executeUpdate()方法返回的整型值表明了受此操作影响的记录数量。 注意这个数值可能与数据库中被(最后一条SQL语句)影响了的“行”数有关,也可能没有。一个大批量HQL操作可能导致多条实际的SQL语句被执行, 举个例子,对joined-subclass映射方式的类进行的此类操作。这个返回值代表了实际被语句影响了的记录数量。在那个joined-subclass的例子中, 对一个子类的删除实际上可能不仅仅会删除子类映射到的表而且会影响“根”表,还有可能影响与之有继承关系的joined-subclass映射方式的子类的表。
摘要: 第一部分、SQL&PL/SQL[Q]怎么样查询特殊字符,如通配符%与_[A]select * from table where name like 'A_%' escape ''[Q]如何插入单引号到数据库表中[A]可以用ASCII码处理,其它特殊字符如&也一样,如insert into t values('i'||chr(39)||'m'); -- chr(39)代表字符'或者用...
阅读全文
先记下,以后有用得着的时候也好找。
所见即所得(WYSIWYG - What You See Is What You Get)
1、
eWebEditor[Mode In China]
简介:
eWebEditor是eWebSoft.com旗下eWeb团队开发的基于网页的、所见即所得的在线HTML编辑器。她能够在网页上实现许多桌面编辑软件(如:Word)所具有的强大可视编辑功能;她是一个真正的绿色软件,不需要在计算机上安装任何的客户端软件;她的易用使得WEB开发人员只要一行代码即可完成调用。
当前最新版本:Version 3.8 (ASP版、PHP版、JSP版、ASP.NET版)
对于一般站点来说,eWebEditor已经很不错了,简单易上手。
使用方法/接口:
在线演示:
http://ewebeditor.webasp.net/demo.aspMailMagic下载:eWebEditor Version 2.8.0 修正版及绍文档
mailmagic://zoDAsdNafHdAAJzazaazvAoxzdDsadxsqdvNaxfAzfNdfxDzxqzqJzoAqiHfoioqaHxHNAnafsoNzJosfxdsDfnxxvdqvNaqnsnindNfdzsiJiaHJsAJAqzAinAHqNaoqHixiiHvAAzdNHHvnqqfxAndiHonqiAzsNdosvavAqiqvaNNJNqsqznovDqNdAvvvondaaNxiDiffdDsainsaqDifnxvJJaJADzAfafdHnJddxzJJiNHvdqqfviDvodiiAziAHxNnHnzAHnzHanvDisaDHDJHqJn
2、
HTMLArea简介:
HTMLArea is a free, customizable online editor. It works inside your browser. It uses a non-standard feature implemented in Internet Explorer 5.5 or better for Windows and Mozilla 1.3 or better (any platform), therefore it will only work in one of these browsers.
使用方法/接口:
在线演示:
http://www.dynarch.com/demos/htmlarea/MailMagic下载:HTMLArea release 3.0-rc1
mailmagic://qzsHqHnfAviDiNoqJvvisNHzsziqfzqxnzznosxnoDAvsvdxqfDqoJdiNiJnxAinsanxfznNnnAzoqvxdnqadJnnqsiJaxHsaassdaaaxAzDNxdDNHDzondfoNAJJodnaiqqzzziadsovfdfAJnoxzHAHsadosJsJffaNfHDnNoAxAvAsfnJADdqdvdsazifJNnHsxNxfavfJnzDfisnxzzfNAxfadnnvHDaxJfoqDHAzfNDsDxNNoizoviHNNaJ
3、
FCKeditor简介:
This HTML text editor brings to the web many of the powerful functionalities of desktop editors like MS Word. It's lightweight and doesn't require any kind of installation on the client computer.
Because it is Open Source, you may use it however you want.
使用方法/接口:
在线演示:
http://www.fckeditor.net/demo/default.htmlMailMagic下载:FCKeditor 2.2 released
mailmagic://zxsANfJqNosoAAosnqifNHnDNAxNHaadnHfdHJqAqiDNsNqJAqqqHandxADAfJodoDDzzvoqaHsNvDdvJzAJqvsdxqzasdavfsqvzaHfzHzxqaiNvvHifAzDonxqofHxzxaqDszxqfafAJffJdqsDvvNHNqJsvisooxixAAsfodfJanqfANHNzxvqsxoJszAHnNdqzqvNafaavHJovAvvxHxzqsiisHDNfHDiovfffznJxiAdNfDHxnifizDxdqq
4、
TinyMCE简介:
TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor control released as Open Source under LGPL by Moxiecode Systems AB. It has the ability to convert HTML TEXTAREA fields or other HTML elements to editor instances. TinyMCE is very easy to integrate into other CMS systems.
使用方法/接口:
在线演示:
MailMagic下载:TinyMCE 2.0.4
mailmagic://JiDqnAddiHsJnDHDiiaDDAJiNAvDioAAdqNddoizHindzsDxofniHaxDzxNszxqxvavivHiNzxzisovNqqxaDadzDoaxAJdzdNdxozdfvnxozHHdfJqssaiDzvaiHNiiHdHxqzsJAHsJfdDdxHDqvADvNaJsnJovoDDAfaaDqDoiaasAqnJoAxinvssziiAqJNsxsdfvoxNNdoAoxiiDaanJnfsNAnqDnfxfazssfJssqJJzixvHnDqiaiodJiJq
5、
FreeTextBox简介:
The no.1 free ASP.NET HTML Editor for IE and Mozilla.
使用方法/接口:
在线演示:
http://www.freetextbox.com/MailMagic下载:FreeTextBox 3.0 Free License (free!)
mailmagic://oHNxNdiDzAxxofxiNHizdsaDADzfxoxfJsaNHdzAoaxNAdnaDvxNoxdxnxszqqANDoxJffJNAvixAoNnfiHasAzJJHAaJNJinJDNNioffviooqfHvffnnNHHxfvNfidnAoofdDfsaofAxafJoAJnNfanodovJofHAJzqanfJzqHdzvaJsosixaNaoxHNodfsHzzzdsDnovvJisNqvdqfzHNnzoaAoxiJdodoJdfiJisnvHfqdffHonsDioAvofan
6、
widgEditor[
支持XHTML]
简介:
widgEditor is an easily installed, easily customisable WYSIWYG editor for simple content. It replaces existing textareas with an improved editing pane using JavaScript, therefore if you don't have JavaScript (or your browser doesn't support HTML editing) it degrades gracefully. Try it out by typing below, and submit to see the processed output.
使用方法/接口:
在线演示:
http://www.themaninblue.com/experiment/widgEditor/MailMagic下载:widgEditor
mailmagic://xxvvvsHAzAzJdAffaqNqAdxfNqvxqJsndsHNzDDJqadAAfqqzHaxnNsfdqDaxzfnNdnddzivsfnaNoixzqziDAvsiqqfqiqvAdzxfzDnvqNiNnadivAzdvqxzvoDfdJfNnxaqovnAfJfDnxiDvNDvqaAvqNizqzodfAfqHvNofzxzsivaxaqioaavHvDNqqDJJnzvqsoHxozoqniaJfavaisoHqaDJJffJdzHiHqfisisssdoDaoiAaJHiNDsddi
这篇文章对主流的在线WYSIWYG编辑器做了个详细的比较:
http://www.geniisoft.com/showcase.nsf/WebEditors
物化视图是包括一个查询结果的数据库对像,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表。物化视图存储基于远程表的数据,也可以称为快照。
物化视图可以查询表,视图和其它的物化视图。
通常情况下,物化视图被称为主表(在复制期间)或明细表(在数据仓库中)。
对于复制,物化视图允许你在本地维护远程数据的副本
,
这些副本是只读的。如果你想修改本地副本,必须用高级复制的功能。当你想从一个表或视图中抽取数据时,你可以用从物化视图中抽取。
对于数据仓库,创建的物化视图通常情况下是聚合视图,单一表聚合视图和连接视图。
本文我们将会看到怎样创建物化视图并且讨论它的刷新选项。
在复制环境下,创建的物化视图通常情况下主键,
rowid,
和子查询视图。
1.
主键物化视图:
下面的语法在远程数据库表
emp
上创建主键物化视图
SQL> CREATE MATERIALIZED VIEW mv_emp_pk
REFRESH FAST START WITH SYSDATE
NEXT SYSDATE + 1/48
WITH PRIMARY KEY
AS SELECT * FROM emp@remote_db;
Materialized view created.
注意:当用
FAST
选项创建物化视图,必须创建基于主表的视图日志
,
如下
:
SQL> CREATE MATERIALIZED VIEW LOG ON emp;Materialized view log created.
2.
Rowid
物化视图
下面的语法在远程数据库表
emp
上创建
Rowid
物化视图
SQL> CREATE MATERIALIZED VIEW mv_emp_rowid
REFRESH WITH ROWID
AS SELECT * FROM emp@remote_db;
Materialized view log created.
3.
子查询物化视图
下面的语法在远程数据库表
emp
上创建基于
emp
和
dept
表的子查询物化视图
SQL> CREATE MATERIALIZED VIEW mv_empdept
AS SELECT * FROM emp@remote_db e
WHERE EXISTS
(SELECT * FROM dept@remote_db d
WHERE e.dept_no = d.dept_no)
Materialized view log created.
REFRESH
子句
[refresh [fast|complete|force]
[on demand | commit]
[start with date] [next date]
[with {primary key|rowid}]]
Refresh
选项说明
:
a.
oracle
用刷新方法在物化视图中刷新数据
.
b.
是基于主键还是基于
rowid
的物化视图
c.
物化视图的刷新时间和间隔刷新时间
Refresh
方法
-FAST
子句
增量刷新用物化视图日志(参照上面所述)来发送主表已经修改的数据行到物化视图中
.
如果指定
REFRESH FAST
子句,那么应该对主表创建物化视图日志
SQL> CREATE MATERIALIZED VIEW LOG ON emp;
Materialized view log created.
对于增量刷新选项,如果在子查询中存在分析函数,则物化视图不起作用。
Refresh
方法
-COMPLETE
子句
完全刷新重新生成整个视图,如果请求完全刷新,
oracle
会完成
完全刷新即使增量刷新可用。
Refresh Method – FORCE
子句
当指定
FORCE
子句,如果增量刷新可用
Oracle
将完成增量刷新,否则将完成完全刷新
,
如果不指定刷新方法
(FAST, COMPLETE, or FORCE),Force
选项是默认选项
主键和
ROWD
子句
WITH PRIMARY KEY
选项生成主键物化视图
,
也就是说物化视图是基于主表的主键,而不是
ROWID(
对应于
ROWID
子句
). PRIMARY KEY
是默认选项
,
为了生成
PRIMARY KEY
子句,应该在主表上定义主键,否则应该用基于
ROWID
的物化视图
.
主键物化视图允许识别物化视图主表而不影响物化视图增量刷新的可用性。
Rowid
物化视图只有一个单一的主表,不能包括下面任何一项
:
n
Distinct
或者聚合函数
.
n
Group by
,子查询,连接和
SET
操作
刷新时间
START WITH
子句通知数据库完成从主表到本地表第一次复制的时间
,
应该及时估计下一次运行的时间点
, NEXT
子句说明了刷新的间隔时间
.
SQL> CREATE MATERIALIZED VIEW mv_emp_pk
REFRESH FAST
START WITH SYSDATE
NEXT SYSDATE + 2
WITH PRIMARY KEY
AS SELECT * FROM emp@remote_db;
Materialized view created.
在上面的例子中,物化视图数据的第一个副本在创建时生成,以后每两天刷新一次
.
总结
物化视图提供了可伸缩的基于主键或
ROWID
的视图
,
指定了刷新方法和自动刷新的时间。