前言:本文不是專門講述Web Service技術(shù)的,讀者在閱讀本文之前需要具備一定的SOAP和Web Service知識(shí)基礎(chǔ),同時(shí)對(duì)Weblogic Server的使用也應(yīng)該熟悉。如果要自己動(dòng)手實(shí)踐本文的例子,就需要安裝Weblogic Server 81,盡管本文是以weblogic server 81為測試環(huán)境,但是針對(duì)weblogic server 7下也是差不多的。本文只是起個(gè)拋磚引玉的作用,如果想深入研究Web Service的開發(fā),還需要參考、學(xué)習(xí)相關(guān)的資料,包括Weblogic Service的相關(guān)文檔。
一、概述
在JBuilder中也支持開發(fā)基于weblogic的web service,不過實(shí)際上在JBuilder下開發(fā)web service也是基于ant任務(wù)來生成和構(gòu)造web service的。但是,當(dāng)初筆者在一個(gè)項(xiàng)目中使用JBuilder下自動(dòng)生成構(gòu)造ant腳本生成的web service時(shí)碰到了一個(gè)問題,通過JBuilder生成的web service,如果你的web service調(diào)用接口中存在一個(gè)或者多個(gè)String類型參數(shù)的時(shí)候,在生成的wsdl文件中對(duì)該接口的參數(shù)命名不會(huì)按照你的后端組件對(duì)應(yīng)方法中參數(shù)的名字,而是以string、string0、string1…等形式命名的。而在那個(gè)項(xiàng)目中需要在Delphi環(huán)境中調(diào)用web service,問題就出現(xiàn)了,string在Delphi中是關(guān)鍵詞,產(chǎn)生了沖突,不能進(jìn)行調(diào)用。于是筆者決定采用自編寫ant腳本的方式來生成和構(gòu)造web service來解決前面所述Delphi調(diào)用的問題。
BEA Weblogic提供了一些Ant任務(wù),用來幫助開發(fā)者生成、構(gòu)造一個(gè)Web服務(wù)的重要部件,(例如:序列化類、客戶端jar支持庫、以及web-services.xml描述文件),并且把一個(gè)Weblogic Web 服務(wù)的所有部分打包成一個(gè)可部署的EAR文件。
BEA Weblogic所提供的Web服務(wù)Ant任務(wù),支持從實(shí)現(xiàn)了Web Service接口的普通JAVA源文件和EJB jar生成Web Service部件,也支持從WSDL描述文件生成,同時(shí)支持基于http/https傳輸協(xié)議和JMS傳輸協(xié)議的Web Service。在這一節(jié)我們只講述通過基于一個(gè)普通JAVA類作為后端組件來實(shí)現(xiàn)的Web Service,傳輸協(xié)議使用http(基于https的方式將在后述關(guān)于Web Service安全的部分講述)。
二、使用Weblogic ant工具生成Web Service
我們先建立D:\wls_ws_demo的工作目錄,在此目錄下分別建立src、build、ddfiles、webapp、test目錄。具體用途后文會(huì)涉及到。
首先我們編寫一個(gè)實(shí)現(xiàn)了兩個(gè)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!";
}
}
上面兩個(gè)方法就不需要解釋了,很簡單。把此類按封裝包一致的路徑放置在src目錄下。
下面是本示例中ant腳本文件內(nèi)的屬性設(shè)置:
<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"/>
上面的屬性應(yīng)該不是很難理解,關(guān)鍵的是對(duì)于bea weblogic server安裝目錄和構(gòu)造生成文件的路徑說明,其次是對(duì)classpath的設(shè)置,需要用到的兩個(gè)weblogic庫是weblogic.jar和webservices.jar。
接著我們看看我們在本節(jié)中使用的Weblogic提供的Ant任務(wù):
1、source2wsdd
source2wsdd Ant任務(wù)最基本的功能是根據(jù)我們編寫的普通JAVA類源文件生成一個(gè)Web Service所必需的兩個(gè)部件:web-services.xml和.wsdl描述文件。
下面是針對(duì)上面HelloWorldWS.java對(duì)應(yīng)的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的實(shí)現(xiàn)后端組件,這里是普通JAVA類com.wnetw.ws.demo HelloWorldWS.java。注意屬性里面是對(duì)源文件目錄路徑設(shè)置,而不是包路徑。
ddFile:生成的web service部署描述符文件web-services.xml的存放路徑。
wsdlFile:生成的.wsdl文件存放的路徑和名字。
serviceURI:客戶應(yīng)用程序調(diào)用此Web服務(wù)的URL中的Web Service URI部分。注意:必須以“/”開頭。例如:/ HelloWorldWS 。同時(shí)這個(gè)URI屬性也會(huì)成為生成的web-services.xml 部署描述符文件中<web-service>元素的uri屬性。
例如:本機(jī)訪問本web service例子的url是http://localhost:7001/ WSDemo/ HelloWorldWS
上面的serviceURI屬性就指定了上述url中的/ HelloWorldWS這一部分。
2、clientgen
clientgen可以用來生成JAVA環(huán)境下客戶端應(yīng)用調(diào)用一個(gè)Web Service客戶端jar支持庫。可以通過wsdl文件來生成,也可以通過一個(gè)包含web service實(shí)現(xiàn)的ear文件來生成。
下面是clientgen ant任務(wù)的腳本示例:
<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任務(wù)生成的wsdl文件來生成客戶端jar支持庫。通過wsdl屬性指定。
3、war
這是ant提供的標(biāo)準(zhǔn)任務(wù),這里與其他普通的war包有一點(diǎn)區(qū)別是,需要把web-services.xml文件打包到war中去。
說明:需要準(zhǔn)備web.xml,后面對(duì)于安全設(shè)置的時(shí)候還需要weblogic.xml文件,這里先都打包進(jìn)去,這些文件都需要提前編輯準(zhǔn)備好:
---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>
這個(gè)文件沒設(shè)置,在后面關(guān)于安全的處理里面需要這里配置角色映射。
下面是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標(biāo)準(zhǔn)任務(wù),需要注意的是必須提前編寫application.xml文件,下面針對(duì)本文例子的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應(yīng)用的應(yīng)用根。
例如:本機(jī)訪問本web service例子的url是http://localhost:7001/
WSDemo/ HelloWorldWS
上面的context-root元素就指定了上述url中的WSDemo這一部分。
下面是本文例子的ear ant任務(wù)腳本:
<target name="genear">
<ear destfile="${ear.file}" appxml="ddfiles/application.xml">
<fileset dir="${build.dir}/applications" includes="*.war"/>
</ear>
</target>
核心的ant任務(wù)說明完了,下面是完整的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>
運(yùn)行ant生成Web Service:
打開命令行窗口,轉(zhuǎn)到工作目錄D:\wls_ws_demo下,在此目錄下先運(yùn)行D:\bea\weblogic81\server\bin\setWLSEnv.cmd(此cmd文件具體路徑與你的weblogic platform81實(shí)際安裝目錄相關(guān))進(jìn)行環(huán)境設(shè)置,然后運(yùn)行:D:\bea\weblogic81\server\bin\ant.bat -buildfile build_wls_all.xml。
運(yùn)行結(jié)束,出現(xiàn)“BUILD SUCCESSFUL”,那就代表OK了。轉(zhuǎn)到工作目錄下的build目錄,你就會(huì)看到HelloWorldWS.ear這個(gè)文件。
三、測試Web Service
本節(jié)將講述對(duì)前一節(jié)里生成的Web Service HelloWorldWS進(jìn)行測試。
啟動(dòng)Weblogic Server,進(jìn)入Weblogic Server控制臺(tái),在Deployments->Applications下部署上節(jié)生成的HelloWorldWS.ear。
1、通過Weblogic自動(dòng)生成的測試主頁測試
部署成功后,在瀏覽器中輸入http://localhost:7001/WSDemo/HelloWorldWS訪問Weblogic Server默認(rèn)生成的上述HelloWorldWS Web Service的測試主頁。
如下圖:

圖上列出了HelloWorldWS Web Service上的兩個(gè)方法:welcome和sayHello。
點(diǎn)擊welcome連接進(jìn)入wecome方法的測試頁,如下圖:

在上述頁面輸入“老Z”,提交后就會(huì)看到如下圖頁面:

測試的結(jié)果跟上節(jié)中的HelloWorldWS.java實(shí)現(xiàn)此方法的結(jié)果是一樣的。測試sayHello方法跟上面過程一樣。
在測試主頁中還能看到在JAVA環(huán)境下,基于clientgen ant任務(wù)生成的jar客戶端stub支持庫調(diào)用此HelloWorldWS Web服務(wù)的代碼示例。
2、使用JAVA程序調(diào)用Web Service
下面實(shí)際編寫一個(gè)java測試程序來調(diào)用上述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);
}
}
}
編譯、運(yùn)行上述測試程序的時(shí)候首先需要weblogic客戶端webservice支持庫webserviceclient.jar,還需要前面clientgen ant任務(wù)生成的jar客戶端stub支持庫HelloWorldWS_clients.jar。在下面的編譯、運(yùn)行測試程序的ant腳本中可以看到在classpath中引入了上述兩個(gè)jar。
編譯、運(yùn)行測試程序的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>
運(yùn)行上述ant腳本后,如果成功的話,應(yīng)該得到類似下圖結(jié)果:

3、在VB下調(diào)用Web Service
下面我在VB環(huán)境下來調(diào)用下這個(gè)Web Service,筆者使用的是Visual Basic 6.0,要在VB下調(diào)用Web Service需要先安裝Microsoft SOAP toolkit。
新建一個(gè)VB工程,然后把Microsoft Soap Type Library引用進(jìn)來,如下圖:

新建一個(gè)form1,添加一個(gè)按鈕command1,在form1源代碼窗口中整個(gè)拷貝如下代碼:
Dim soap As MSSOAPLib.SoapClient
Private Sub Command1_Click()
MsgBox soap.sayHello()
MsgBox soap.welcome("老Z")
If Err <> 0 Then
MsgBox "Web Service調(diào)用失敗: " + 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
然后運(yùn)行工程,點(diǎn)擊窗口上的按鈕就開始調(diào)用前面部署的Web Service(確保Weblogic Server在運(yùn)行中),成功的話會(huì)得到如下圖的兩個(gè)MessageBox:


四、使用非內(nèi)建數(shù)據(jù)類型
前面例子中的Web Service方法中使用的參數(shù)和返回值都是String,類似String,int等數(shù)據(jù)類型是屬于Weblogic web service所支持的內(nèi)建類型,關(guān)于Weblogic web service所支持的內(nèi)建數(shù)據(jù)類型請(qǐng)參見:http://e-docs.bea.com/wls/docs81/webserv/implement.html#1054236
所支持的XML非內(nèi)建類型請(qǐng)參見:
http://e-docs.bea.com/wls/docs81/webserv/assemble.html#1060805
所支持的Java非內(nèi)建數(shù)據(jù)類型請(qǐng)參見:
http://e-docs.bea.com/wls/docs81/webserv/assemble.html#1068595
WebLogic Server能夠?qū)?nèi)建數(shù)據(jù)類型進(jìn)行XML與Java表示之間的轉(zhuǎn)換。但是,如果你在web service操作中使用了非內(nèi)建數(shù)據(jù)類型,那么你必須提供以下信息,以確保weblogic server能夠正確地進(jìn)行轉(zhuǎn)換。
- 用于處理數(shù)據(jù)的Java表示與XML之間的轉(zhuǎn)換的序列化類;
- 包含了數(shù)據(jù)類型Java表示的Java類;
- 數(shù)據(jù)類型的XML Schema表示;
- web-services.xml部署描述文件中的數(shù)據(jù)類型映射信息。
Weblogic Server中帶有servicegen和autotype Atn任務(wù),這兩個(gè)任務(wù)通過對(duì)web service的無狀態(tài)EJB或者Java類后端組件的內(nèi)省,從而自動(dòng)生成上述部件。上述Ant任務(wù)能夠處理許多非內(nèi)建數(shù)據(jù)類型,所以大多數(shù)的開發(fā)者并不需要手工生成上述的部件。
有時(shí),你可能也需要手工去創(chuàng)建非內(nèi)建數(shù)據(jù)類型部件。因?yàn)槟愕臄?shù)據(jù)類型可能很復(fù)雜,以致Ant任務(wù)不能正確生成前述部件。你也可能想要自己控制數(shù)據(jù)在XML和Java表示之間的轉(zhuǎn)換過程,而不依賴Weblogic Server所使用的缺省轉(zhuǎn)換程序。
本節(jié)將演示在Weblogic web service中如何處理非內(nèi)建(自定義)的數(shù)據(jù)類型。
我們先編寫一個(gè)數(shù)值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中增加一個(gè)方法:
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;
}
在這個(gè)方法里,返回值是UserInfo,這是我們前面定義的數(shù)值Bean,由于這是非內(nèi)建類型,而且也不屬于受支持的非內(nèi)建類型,所以需要我們必須自己來處理XML和UserInfo Java表示數(shù)據(jù)類型之間的轉(zhuǎn)換。
在本文的例子中,我們使用Weblogic Server的autotype任務(wù)來做這件事情。我們先在build目錄建一個(gè)autotype目錄,然后在前文中ant完整腳本中的compile任務(wù)之后增加下述腳本:
<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任務(wù)有幾個(gè)常用屬性,下面簡要說明下:
javatypes:需要進(jìn)行類型轉(zhuǎn)換的非內(nèi)建(自定義)數(shù)據(jù)類型java類,注意取值是全限定類名,不需要帶上java或者class擴(kuò)展名。如果存在多個(gè)這樣的數(shù)據(jù)類型類,用逗號(hào)“,”隔開;
targetNamespace:在對(duì)數(shù)據(jù)類型映射到XML的時(shí)候使用的命名空間;
packageName:生成的序列化相關(guān)類的封裝包;
destDir:生成的序列化相關(guān)類存放的目錄;
keepGenerated:是否保留中間java源文件,取值為:true或者false。
關(guān)于autotype任務(wù)的詳細(xì)信息請(qǐng)參考:
http://e-docs.bea.com/wls/docs81/webserv/anttasks.html#1080062
上述ant任務(wù)成功運(yùn)行后就會(huì)生成build/autotype/目錄下生成types.xml文件以及按包封裝的數(shù)據(jù)轉(zhuǎn)換類的源文件和class文件。
由于增加了自定義數(shù)據(jù)類型,所以我們還得更新source2wsdd任務(wù)腳本,以下是增加了自定義數(shù)據(jù)類型處理后的source2wsdd任務(wù)腳本:
<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屬性來指定自定義數(shù)據(jù)類型的XML描述文件。
增加了對(duì)自定義數(shù)據(jù)類型支持后的完整腳本請(qǐng)參考本文代碼下載文件。
按照第一節(jié)所述方法運(yùn)行ant腳本build_wls_all.xml后,再部署build\applications\目錄下的HelloWorldWS.ear。就可以按照以前說的方法進(jìn)行測試了。
這一次在Weblogic Server自動(dòng)生成的web service測試主頁:
http://localhost:7001/WSDemo/HelloWorldWS
可以發(fā)現(xiàn)多了一個(gè)叫g(shù)etUserInfo的方法連接,進(jìn)入此方法的調(diào)用測試頁面,調(diào)用此方法后就可以看到此web service方法的調(diào)用結(jié)果,以下是結(jié)果截圖:

從調(diào)用測試結(jié)果頁面可以看到,這一次的Return Value是:
com.wnetw.ws.demo.UserInfo@82d235
這正是我們的web service方法返回值類型類型的一個(gè)對(duì)象,圖中的下面也以SOAP消息的形式描述了調(diào)用的輸入和返回結(jié)果。
我們接著修改測試類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("開始測試自定義數(shù)據(jù)類型的返回值。。。");
UserInfo info = port.getUserInfo(100);
System.out.println(info);
System.out.println(info.getUsername());
}catch(Exception e){
e.printStackTrace();
System.out.println(e);
}
}
}
看看以下代碼好像有點(diǎn)問題,UserInfo info = port.getUserInfo(123);我們在HelloWorldWS.java類中定義的對(duì)應(yīng)方法是getUserInfo(Integer userid),參數(shù)是Integer的,但是上述測試類代碼中卻使用int類型,這是正確的。我們可以把clientgen任務(wù)中的keepGenerated屬性設(shè)為true,把自動(dòng)生成的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參數(shù)的!如果你使用Integer類型參數(shù),反而會(huì)編譯通不過!只能認(rèn)為這是weblogic server ant任務(wù)對(duì)數(shù)據(jù)類型映射的具體實(shí)現(xiàn)了,如果你仔細(xì)看了本節(jié)前面所述對(duì)java內(nèi)建數(shù)據(jù)類型的支持列表,那么也是好理解的,因?yàn)閖ava數(shù)據(jù)類型到XML Schema數(shù)據(jù)類型映射中,java中的int和java.lang.Integer都映射到了int。所以web service服務(wù)端接收到的SOAP消息中只會(huì)是XML Schema int類型,無法區(qū)分客戶端使用的會(huì)是int或者java.lang.Integer,所以在ant工具根據(jù)wsdl文件自動(dòng)生成客戶端支持類的時(shí)候就只能使用int了,沒法區(qū)分int或者java.lang.Integer。這是個(gè)有意思的問題^-^一不小心也許會(huì)在你工作中浪費(fèi)不必要的時(shí)間。當(dāng)然如果有必要,你完全可以手動(dòng)修改、甚至完全自己來生成客戶端支持庫和數(shù)據(jù)類型轉(zhuǎn)換類。不過嘛,除了出于研究和特殊情況外這是沒有必要的。
我們接著看看HelloWorldWS_clients.jar中還有什么東西,發(fā)現(xiàn)有個(gè)language_builtins這樣的包,從包名也許你能猜到這是干什么的,是對(duì)java語言內(nèi)建數(shù)據(jù)類型處理的包,此包下面是util包,里面有ListCodec.class類。看看我們的UserInfo類,里面使用了List類,這個(gè)包里面的類正是用來處理java.util.List數(shù)據(jù)類型的,java.util.List屬于Weblogic server web service所支持的非內(nèi)建數(shù)據(jù)類型,也就是說不需要通過autotype明確來標(biāo)志生成相關(guān)的數(shù)據(jù)轉(zhuǎn)換類和類型信息。但是,java.util.List又有別于int、java.lang.String等wls web service所支持的內(nèi)建類型,對(duì)于java.util.List等受支持的非內(nèi)建類型由ant任務(wù)自動(dòng)生動(dòng)相關(guān)數(shù)據(jù)類型處理信息,不需要手工干預(yù)。對(duì)比來看,int、java.lang.String等wls web service所支持的內(nèi)建類型是直接映射,不需要數(shù)據(jù)類型轉(zhuǎn)換相關(guān)類。Java.util.List最終映射成了XML Shema SOAP Array類型。其他類型請(qǐng)參考:http://e-docs.bea.com/wls/docs81/webserv/assemble.html#1068595
運(yùn)行修改后的build_wls_test.xml腳本,成功的話應(yīng)該得到如下圖類似結(jié)果:

增加了自定義數(shù)據(jù)類型后,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測試客戶端代碼請(qǐng)見本文附帶下載代碼。
五、配置Web Service安全
Weblogic Web Service包括三種不同概念的安全設(shè)置:
- 消息層安全:對(duì)SOAP消息中數(shù)據(jù)的數(shù)字簽名或者加密;
- 傳輸層安全:使用SSL來保證客戶應(yīng)用與Web Service之間連接的安全性;
- 訪問控制:指定何種用戶、組、角色被允許訪問該Web Service。
在這里我們主要針對(duì)訪問控制概念上的安全處理。
Weblogic Web Service最終是作為一個(gè)標(biāo)準(zhǔn)的J2EE ear打包文件提供進(jìn)行部署的,其中包含了一個(gè)war包,也就是說web service是以web應(yīng)用的形式提供并部署的,這從前面的章節(jié)就可以看出。
所以,針對(duì)web service的訪問控制安全處理與J2EE中對(duì)于Web資源的訪問控制處理是一樣的。具體的說就是對(duì)特定Web資源增加安全約束。具體配置就是通過在Web應(yīng)用部署描述符web.xml增加相應(yīng)的元素:需要進(jìn)行安全約束的資源集合、授權(quán)訪問的角色列表、對(duì)用戶數(shù)據(jù)的安全約束、角色映射等信息。
在這里,我們需要對(duì)前面用到的web.xml文件進(jìn)行修改,如下所示:
<!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>
然后運(yùn)行ant構(gòu)造腳本,部署ear。部署成功后,你會(huì)在weblogic server運(yùn)行命令行窗口中看到如下類似信息:
<2005-9-24 下午22時(shí)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.>
這是因?yàn)闆]有進(jìn)行角色映射,所以直接使用角色名作為用戶名了。這只是一個(gè)警告信息,沒有關(guān)系。后面將會(huì)講述怎么進(jìn)行角色映射。
然后進(jìn)入weblogic server Console,新建一個(gè)名叫testrole的用戶。接著在左側(cè)目錄樹中一次展開Deployments-Applications- HelloWorldWS- WSDemo,在WSDemo節(jié)點(diǎn)上鼠標(biāo)右擊,選擇Define Security Policy…


在Policy Condition項(xiàng)選擇User name of the caller,點(diǎn)擊增加,在接著出現(xiàn)的窗口中填入testrole,OK之后,點(diǎn)擊上圖頁面中的Apply。接下來就可以跟以前一樣測試了。
瀏覽器中輸入http://localhost:7001/WSDemo/HelloWorldWS,這個(gè)時(shí)候會(huì)彈出來一個(gè)登陸框,如下圖:

現(xiàn)在可以看到,訪問控制起作用了。輸入testrole以及擬增加用戶的時(shí)候指定的密碼后,就能進(jìn)入到和以前一樣的測試主頁了。
上面那種使用角色名和用戶名對(duì)應(yīng)的方式顯示在實(shí)際應(yīng)用中是不方便的,因?yàn)榫唧w會(huì)有什么樣的用戶會(huì)訪問此web service在構(gòu)建時(shí)是不確定的。我們可以使用角色映射的方式來避免這個(gè)問題。
進(jìn)行角色映射需要在weblogic.xml文件中配置,下面我將對(duì)testrole映射到一個(gè)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文件中指定的授權(quán)訪問角色testrole映射到了test_group,也就是說test_group組中的所有用戶都有權(quán)訪問。這樣一來用戶授權(quán)和實(shí)現(xiàn)就解耦了。
使用ant腳本重新構(gòu)建,然后部署ear。接著進(jìn)入weblogic server console,刪除testrole用戶,新建test_group組,新建一個(gè)叫test_user的用戶,并指派給test_group組。接著按照前面一樣Define Security Policy,這一次在Policy Condition部分選擇Caller is member of the group,然后點(diǎn)Add進(jìn)入授權(quán)group指定頁面,輸入test_group,點(diǎn)增加-點(diǎn)OK,回到Define Security Policy主頁面,點(diǎn)擊Apply就好了。
然后我們在瀏覽中進(jìn)入http://localhost:7001/WSDemo/HelloWorldWS,彈出登陸框,這一次我們可以使用test_group中的任何成員用戶來登陸了,前面例子是test_user。這樣在以后,需要分配新的用戶授權(quán)訪問此Web Service的時(shí)候就知需要在Cosole在test_group中增加一個(gè)成員就行了,不需要重新構(gòu)建web service了。
加入了訪問控制后,在調(diào)用web service的時(shí)候就需要提供授權(quán)憑證了,下面是需要增加的代碼信息:
- 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();
//因?yàn)榧尤肓嗽L問控制,所以對(duì)于http://localhost:7001/WSDemo/HelloWorldWS?WSDL的訪問也需授權(quán),所以我們使用缺省構(gòu)建器,這樣就會(huì)使用客戶端支持庫jar中的靜態(tài)wsdl文件了。
HelloWorldWSPort port = ws.getHelloWorldWSPort(“test_user”, “test_user”);
//后面的參數(shù)是test_user的密碼,根據(jù)你具體的密碼更改
- VB客戶端
Call soap.mssoapinit("HelloWorldWS.wsdl")
‘由于http://localhost:7001/WSDemo/HelloWorldWS?WSDL需要授權(quán)訪問,所以我們把腳本生成的HelloWorldWS.wsdl文件直接拷貝到VB項(xiàng)目目錄下,使用這個(gè)靜態(tài)文件來初始化soap對(duì)象。
‘后面增加下屬代碼
soap.ConnectorProperty("AuthUser") = "test_user"
soap.ConnectorProperty("AuthPassword") = "test_user"
在我們運(yùn)行上述兩個(gè)測試程序的時(shí)候會(huì)發(fā)現(xiàn)調(diào)用不成功。原因接下來進(jìn)行說明。
我們打開工作目錄中下build\wsddfiles這個(gè)目錄中的HelloWorldWS.wsdl這個(gè)文件,在最后可以看到下面的service元素內(nèi)容,如下:
<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節(jié)點(diǎn)的location屬性有問題,因?yàn)榭蛻舳藄oap初始化后,會(huì)使用這個(gè)URL來調(diào)用本wsdl中描述的web service操作,顯然這個(gè)地址與我們部署的實(shí)際地址是不一樣的。所以我們把location屬性改為我們部署的web service實(shí)際訪問URL:
http://localhost:7001/WSDemo/HelloWorldWS。這就是上述兩個(gè)測試程序不能正確運(yùn)行的原因。
筆者也沒有找到如何在生成web service部件時(shí)設(shè)置此正確屬性的方法,正是因?yàn)樾枰薷纳鲜鰓sdl文件屬性,所以我們需要把build腳本分成兩部分來執(zhí)行,先生成相關(guān)部件,然后修改wsdl文件的上述屬性,最后才進(jìn)行打包和客戶端支持庫的生成,把build_wls_all.xml分開成了build_wls_1.xml和build_wls_2.xml兩個(gè)build腳本文件。在運(yùn)行完后build_wls_1.xml修改上述屬性,然后運(yùn)行build_wls_2.xml即可。
部署成功后,就可以測試上面兩個(gè)調(diào)用例子了,注意把修改好的wsdl文件拷貝到VB項(xiàng)目目錄中去。
如果在web.xml中<security-constraint>元素里加入下述項(xiàng)目
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
那就會(huì)強(qiáng)制要求客戶端使用https進(jìn)行訪問,其他更多信息請(qǐng)參考J2EE中Web應(yīng)用安全方面的資料。
六、雜項(xiàng)設(shè)置
本節(jié)要說的實(shí)際也是安全性方面的問題,只不過和一般的安全性概念不一樣,這里講的是針對(duì)在生產(chǎn)部署環(huán)境下的考慮。
1、定制主頁
在生產(chǎn)環(huán)境下,一般是不允許公開web service默認(rèn)主頁的。其次由于通過主頁:
http://localhost:7001/WSDemo/HelloWorldWS?WSDL
訪問的wsdl描述符文件是動(dòng)態(tài)生成,同時(shí)加入了訪問控制安全約束后,客戶程序訪問此文件也存在問題,所以通常在生產(chǎn)環(huán)境下將禁止訪問web service默認(rèn)主頁以及動(dòng)態(tài)wsdl文件,可以使用專門的靜態(tài)web站點(diǎn)來提供必要的信息,以及通過靜態(tài)web站點(diǎn)來發(fā)布wsdl。
要禁用默認(rèn)主頁以及wsdl文件,需要在web-services.xml描述符文件中進(jìn)行設(shè)置。如下所示在web-service節(jié)點(diǎn)中加入下面兩個(gè)屬性:
exposeWSDL="False"
exposeHomePage="False"
修改后類似下面示例:
。。。
<web-services>
<web-service name="HelloWorldWS"
targetNamespace="http://tempuri.org/"
uri="/HelloWorldWS"
exposeWSDL="False"
exposeHomePage="False">
。。。
這個(gè)修改也需要在運(yùn)行build_wls_1.xml之后進(jìn)行修改,才能保證應(yīng)用打包部署后使得此設(shè)置生效。
在禁止了默認(rèn)主頁和WSDL文件后,為了保證web service更新后不需要更新客戶程序的文件,所以最好建立一個(gè)靜態(tài)web站點(diǎn)來發(fā)布web service,也就是發(fā)布wsdl文件。在用于發(fā)布wsdl的web應(yīng)用中需要在web.xml中加入以下的Mime類型映射:
<mime-mapping>
<extension>wsdl</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
2、啟用https協(xié)議
除了上一節(jié)中在web.xml中加入
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
來啟用https通訊協(xié)議外,還可以在通過在web-service.xml文件中,在web-service(注意不是web-services)節(jié)點(diǎn)中加入下面屬性:
protocol="https"
上述屬性能保證客戶端必須使用https來訪問本web service。
七、結(jié)束語
本文只是針對(duì)很小的一方面來講述基于weblogic ant任務(wù)開發(fā)web service的,只是起個(gè)拋磚引玉的作用。其次,通過本文你也能了解到web service的本質(zhì)過程,無論通過什么工具來開發(fā),本質(zhì)上都是生成基礎(chǔ)部件,然后打包。如果需要全面了解weblogic server web service開發(fā)方面的知識(shí)請(qǐng)參考bea文檔:
http://e-docs.bea.com/wls/docs81/webservices.html
同時(shí)本文使用的環(huán)境是window 2000 server和weblogic platform8.1英文版。
本文示例項(xiàng)目代碼可從以下地址下載:
http://www.wnetw.com/jclub_resources/technology/attachfiles/wls_ws_demo.rar
時(shí)間:2005-12-23
作者:
Russell Miles瀏覽次數(shù):
3616
本文關(guān)鍵字:
Spring,?
Java,?
AOP,?
crosscutting,?
面向方面編程,?
橫切,?
Cuckoo's Egg模式 在本系列的第一部分,我介紹了如何實(shí)現(xiàn)面向方面領(lǐng)域的“HelloWorld”:跟蹤和記錄方面。利用Spring框架所提供的面向方面編程(Aspect-Oriented Programming,AOP)功能,您看到了如何使用before-、after-和基于異常的通知,以及如何使用基于正則表達(dá)式的簡單切入點(diǎn)。跟蹤和記錄方面提供了非常不錯(cuò)的上手例子,而本文將進(jìn)一步介紹一種新的通知形式:around通知。
比起第一部分中介紹的那些通知類型,around形式的通知是一種更具侵入性也更強(qiáng)大的面向?qū)ο蟾拍睢1疚膶⒚枋鯽round通知的每個(gè)特性,以便您可以在自己的Spring AOP應(yīng)用程序中正確地使用它。在本文最后,我將向您展示如何使用around通知來截獲和改變應(yīng)用程序中各個(gè)特性相互作用的方式,以便實(shí)現(xiàn)Cuckoo's Egg(杜鵑的蛋)面向方面設(shè)計(jì)模式。
概述Spring AOP、IoC和代理
在第一部分,我們快速瀏覽了Spring的一些AOP特性,而沒有闡明Spring如何實(shí)現(xiàn)AOP的細(xì)節(jié)。要理解Spring框架如何運(yùn)轉(zhuǎn),尤其是它如何實(shí)現(xiàn)其AOP功能,首先您要明白,Spring是一個(gè)依賴于控制反轉(zhuǎn)(Inversion of Control,IoC)設(shè)計(jì)模式的輕量級(jí)框架。
注意:本文的目的不是要深入介紹IoC模式,介紹IoC只是為了使您明白該設(shè)計(jì)模式是如何影響Spring AOP實(shí)現(xiàn)的。有關(guān)IoC模式的更詳細(xì)的介紹請(qǐng)參見本文末尾的參考資料。
IoC設(shè)計(jì)模式的出現(xiàn)已經(jīng)有一段時(shí)間了。一個(gè)最明顯的例子就是J2EE架構(gòu)本身。隨著企業(yè)開發(fā)尤其是J2EE平臺(tái)的出現(xiàn),應(yīng)用程序開始依賴于由外部容器所提供的一些特性,比如bean創(chuàng)建、持久性、消息傳遞、會(huì)話以及事務(wù)管理。
IoC引入了一個(gè)新概念:由組件構(gòu)成的框架,它與J2EE容器有許多類似之處。IoC框架分離了組件所依賴的功能,并且,根據(jù)Sam Newman文章中的說法,提供了“連接組件的‘膠水’”。
對(duì)組件所依賴特性的控制 被反轉(zhuǎn) 了,這樣外部框架就可以盡可能透明地提供這些特性了。IoC模式真正意識(shí)到了從傳統(tǒng)的由依賴于功能的組件來負(fù)責(zé)這些功能,到由獨(dú)立的框架來配置和提供這些功能的方式轉(zhuǎn)變。
圖1顯示了一些構(gòu)成IoC模式的不同組件角色的例子。

圖1. 沒有對(duì)BusinessLogic bean應(yīng)用方面時(shí)的順序圖.
圖字:
Component:組件
Provides Facilities:提供功能
Relies on and conforms to:依賴于并服從
Manages the services the framework can then use to provide facilities:管理框架隨后可以用來提供功能的服務(wù)
Service:服務(wù)
Your Component:您的組件
IoC Framework:IoC框架
External services:外部服務(wù)
IoC模式使用3種不同的方法來解除組件與服務(wù)控制的耦合:類型1、類型2和類型3。
- 類型1:接口注入
這是大部分J2EE實(shí)現(xiàn)所使用的方法。組件顯式地服從于一組接口,帶有關(guān)聯(lián)的配置元數(shù)據(jù),以便允許框架對(duì)它們進(jìn)行正確的管理。
- 類型2:Setter注入
外部元數(shù)據(jù)被用來配置組件相互作用的方式。在第一部分中,我們就是使用這種IoC方法利用springconfig.xml文件來配置Spring組件的。
- 類型3:構(gòu)造函數(shù)注入
組件(包括構(gòu)造組件時(shí)要用的參數(shù))注冊到框架,而框架提供組件的實(shí)例以及所有要應(yīng)用的指定功能。
IoC在組件開發(fā)和企業(yè)開發(fā)中越來越受歡迎。IoC的實(shí)際例子包括傳統(tǒng)的J2EE解決方案,比如:JBoss、Apache基金會(huì)的Avalon項(xiàng)目以及本文的Spring框架。實(shí)際上,Spring框架構(gòu)建于IoC模式的基礎(chǔ)上是為了幫助將它的輕量級(jí)功能注入到它的相關(guān)應(yīng)用程序的組件中。
那么IoC對(duì)于Spring AOP有何意義呢?Spring的IoC特性是使用IoC springconfig.xml配置文件對(duì)應(yīng)用程序應(yīng)用方面的推動(dòng)因素之一。springconfig.xml配置文件通知Spring框架運(yùn)行時(shí)有關(guān)應(yīng)用程序的組件要被注入的功能類型的信息,所以自然輕量級(jí)的AOP功能就以同樣的方式應(yīng)用了。然后Spring使用代理模式圍繞現(xiàn)有的類和bean實(shí)現(xiàn)指定的AOP功能。
圖2顯示了Spring及其IoC框架如何使用代理對(duì)象提供AOP功能(根據(jù)springconfig.xml文件中的IoC配置。)

圖2. springconfig.xml配置文件改變了Spring框架IoC,以便隨后向第一部分中的一個(gè)順序圖提供AOP代理(單擊圖像查看大圖)
在本系列下面的部分,您將不斷看到現(xiàn)在包含在順序圖中的代理對(duì)象。這只是為了說明對(duì)于Spring AOP來說沒有“魔法”,實(shí)際上只有一個(gè)面向?qū)ο笤O(shè)計(jì)模式的良好例子。
回到AOP:使用around通知的積極方面
在第一部分,您看到了如何使用Spring AOP來實(shí)現(xiàn)跟蹤和記錄方面。跟蹤和記錄都是“消極”方面,因?yàn)樗鼈兊某霈F(xiàn)并不會(huì)對(duì)應(yīng)用程序的其他行為產(chǎn)生影響。它們都使用了消極的before和after形式的通知。
但是如果您希望改變應(yīng)用程序的常規(guī)行為呢?例如說,您希望重寫一個(gè)方法?這樣的話,您就需要使用更積極的around形式的通知。
第一部分的簡單例子應(yīng)用程序包括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();
}
}
要對(duì)一個(gè)BusinessLogic類的實(shí)例徹底重寫對(duì)foo()方法的調(diào)用,需要?jiǎng)?chuàng)建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類必須實(shí)現(xiàn)MethodInterceptor接口和它的invoke(..)方法。每當(dāng)截獲到方法的重寫,invoke(..)方法就會(huì)被調(diào)用。最后一步是改變包含在應(yīng)用程序的springconfig.xml文件中的Spring運(yùn)行時(shí)配置,以便可以對(duì)應(yīng)用程序應(yīng)用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>
根據(jù)該springconfig.xml配置文件,theAroundAdvisor截獲所有對(duì)BusinessLogic類的方法的調(diào)用。接下來,theAroundAdvisor被關(guān)聯(lián)到theAroundAdvice,表明當(dāng)截獲一個(gè)方法時(shí),就應(yīng)該使用在AroundAdvice類中指定的通知。既然已經(jīng)指定了around通知的正確配置,下一次執(zhí)行MainApplication類時(shí),BusinessLogic bean的foo()方法就會(huì)被截獲并重寫,如圖3所示:

圖3. 使用around通知重寫對(duì)BusinessLogic類中的foo()方法的調(diào)用
前面的例子顯示,BusinessLogic類中的foo()方法可以通過AroundAdvice類中的invoke(..)方法徹底重寫。原來的foo()方法完全不能被invoke(..)方法調(diào)用。如果希望從around通知內(nèi)調(diào)用foo()方法,可以使用proceed()方法,可從invoke(..)方法的MethodInvocation參數(shù)中得到它。
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顯示了對(duì)proceed()的調(diào)用如何影響操作的順序(與圖3所示的初始around通知執(zhí)行相比較)。

圖4. 從around通知內(nèi)使用proceed()調(diào)用原來的方法
當(dāng)調(diào)用proceed()時(shí),實(shí)際是在指示被截獲的方法(在本例中是foo()方法)利用包含在MethodInvocation對(duì)象中的信息運(yùn)行。您可以通過調(diào)用MethodInvocation類中的其他方法來改變該信息。
您可能希望更改包含在MethodInvocation類中的信息,以便在使用proceed()調(diào)用被截獲的方法之前對(duì)被截獲方法的參數(shù)設(shè)置新值。
通過對(duì)MethodInvocation對(duì)象調(diào)用getArguments()方法,然后在返回的數(shù)組中設(shè)置其中的一個(gè)參數(shù)對(duì)象,最初傳遞給被截獲的方法的參數(shù)可以被更改。
如果IbusinessClass和BusinessLogic類的foo()方法被更改為使用整型參數(shù),那么就可以將傳遞給被截獲的調(diào)用的值由在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;
}
}
在本例中,被截獲的方法的第一個(gè)形參被假設(shè)為int。實(shí)參本身是作為對(duì)象傳遞的,所以通過將其包裝在Integer類實(shí)例中的方法,基本的int類型的形參被改為對(duì)應(yīng)數(shù)組中的新值。如果您將該參數(shù)設(shè)置為一個(gè)非Integer對(duì)象的值,那么在運(yùn)行時(shí)就會(huì)拋出IllegalArgumentException異常。
您還將注意到,invoke(..)方法必須包含一個(gè)return語句,因?yàn)樵摲椒ㄐ枰祷刂怠5牵恢貙懙膄oo()方法并不返回對(duì)象,所以invoke(..)方法可以以返回null結(jié)束。如果在foo()方法不需要的情況下,您仍然返回了一個(gè)對(duì)象,那么該對(duì)象將被忽略。
如果foo()方法確實(shí)需要返回值,那么需要返回一個(gè)與foo()方法的初始返回類型在同一個(gè)類或其子類中的對(duì)象。如果foo()方法返回一個(gè)簡單類型,例如,一個(gè)integer,那么您需要返回一個(gè)Integer類的對(duì)象,當(dāng)方法被重寫時(shí),該對(duì)象會(huì)自動(dòng)由AOP代理拆箱,如圖5所示:

圖5. around通知的裝箱和自動(dòng)拆箱
圖字:
Object invoke:對(duì)象調(diào)用
The integer return value is boxed in a Integer object in the AroundAdvice and then unboxed by the AOP Proxy:整型返回值被裝箱在AroundAdvic通知的一個(gè)Integer對(duì)象中,然后由AOP代理拆箱。
面向方面編程還是一個(gè)比較新的領(lǐng)域,尤其是與衍生出它的面向?qū)ο缶幊滔啾取TO(shè)計(jì)模式通常被認(rèn)為是常見問題的通用解決方案,因?yàn)槊嫦蚍矫姘l(fā)展的時(shí)間還不長,所以已發(fā)現(xiàn)的面向方面設(shè)計(jì)模式比較少。
此處要介紹的是一種正在浮現(xiàn)的模式,即Cuckoo's Egg設(shè)計(jì)模式。該模式還有其他的叫法,它在面向?qū)ο箢I(lǐng)域的對(duì)等體包括模仿對(duì)象(Mock Object)和模仿測試(Mock Testing),甚至代理模式也與它有一些類似之處。
Cuckoo's Egg面向方面設(shè)計(jì)模式可以被定義為應(yīng)用程序上下文中功能部件的透明和模塊化的置換。就像杜鵑偷偷地把自己的蛋放在另一種鳥的巢中一樣,Cuckoo's Egg設(shè)計(jì)模式用一個(gè)替代功能部件實(shí)現(xiàn)置換現(xiàn)有的功能部件,而使造成的干擾盡可能少。
這種置換的實(shí)現(xiàn)方式可以是靜態(tài)的、動(dòng)態(tài)的、部分的、完全的,針對(duì)一個(gè)對(duì)象的多個(gè)部分,或針對(duì)多個(gè)組件。使用面向方面的方法可以透明地實(shí)現(xiàn)功能部件的置換,而無需對(duì)應(yīng)用程序的其余部分進(jìn)行更改。要置換應(yīng)用程序中現(xiàn)有功能部件的替代功能部件就是“杜鵑的蛋”。圖6顯示了Cuckoo's Egg設(shè)計(jì)模式中的主要組成元素。

圖6. Cuckoo's Egg設(shè)計(jì)模式中的主要組成元素
圖字:
Application:應(yīng)用程序
Component:組件
Replacement Feature:替代功能部件
Component 1 and 2 together encompass a distinct feature of the software:組件1和2共同包含了軟件的一個(gè)獨(dú)立的功能部件
The Cuckoo's Egg pattern transparently replaces an existing feature of the software:Cuckoo's Egg模式透明地置換了軟件現(xiàn)有的功能部件
Before the pattern is applied:應(yīng)用該模式前
After the pattern is applied:應(yīng)用該模式后
Cuckoo's Egg設(shè)計(jì)模式依賴于around通知的概念。您需要借助于積極的和侵入性的around通知來截獲并有效置換應(yīng)用程序中現(xiàn)有的功能部件。
有關(guān)Cuckoo's Egg設(shè)計(jì)模式的更多信息,以及AspectJ中的一個(gè)可選實(shí)現(xiàn),請(qǐng)參見《AspectJ Cookbook》(O'Reilly,2004年12月出版)。
要使用Spring AOP實(shí)現(xiàn)Cuckoo's Egg設(shè)計(jì)模式,需要聲明一個(gè)around通知來截獲所有對(duì)要置換的功能部件的調(diào)用。與hot-swappable target sources(Spring AOP的一個(gè)功能部件,將在本系列的另一篇文章中介紹)不同,around通知的顯式使用使得Cuckoo's Egg實(shí)現(xiàn)可以有效地跨越對(duì)象邊界(因此也可以跨越bean邊界)進(jìn)行整個(gè)功能部件的置換,如圖7所示。

圖7. 一個(gè)跨越bean邊界的組件
圖字:
A feature crosses the boundaries of BusinessLogic and BusinessLogic2 by depending on behavior supplied separately by the two beans:一個(gè)功能部件通過依賴于由BusinessLogic和BusinessLogic2各自提供的行為而跨越了這兩個(gè)bean的邊界
下面的代碼顯示了一個(gè)具有兩個(gè)bean的簡單應(yīng)用程序,其中有一個(gè)功能部件跨越了該應(yīng)用程序的多個(gè)方面。要置換的功能部件可以被視為包含IBusinessLogic bean中的foo()方法和IBusinessLogic2 bean中的bar()方法。IBusinessLogic2 bean中的baz()方法不是 該功能部件的一部分,所以不進(jìn)行置換。
public interface IBusinessLogic
{
public void foo();
}
public interface IBusinessLogic2
{
public void bar();
public void baz();
}
該例子的完整源代碼可在本文末尾的參考資料小節(jié)中下載。
此處,ReplacementFeature類扮演了“杜鵑的蛋”的角色,它提供了將被透明地引入應(yīng)用程序的替代實(shí)現(xiàn)。ReplacementFeature類實(shí)現(xiàn)了所有在該類引入時(shí)要被置換的方法。
public class ReplacementFeature
{
public void foo()
{
System.out.println(
"Inside ReplacementFeature.foo()");
}
public void bar()
{
System.out.println(
"Inside ReplacementFeature.bar()");
}
}
現(xiàn)在需要聲明一個(gè)around通知來截獲對(duì)跨越bean的功能部件的方法調(diào)用。CuckoosEgg類提供了某種around通知來檢查被截獲的方法,并將適當(dāng)?shù)姆椒ㄕ{(diào)用傳遞給ReplacementFeature類的實(shí)例。
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;
}
}
因?yàn)榕cSpring框架關(guān)系密切,Cuckoo's Egg設(shè)計(jì)的詳細(xì)信息被放在springconfig.xml配置文件中。對(duì)springconfig.xml文件的更改將確保所有對(duì)IbusinessLogic和IBusinessLogic2 bean的foo()方法和bar()方法的調(diào)用都將被截獲,并傳遞給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"/>
...
當(dāng)使用修改后的springconfig.xml文件運(yùn)行例子應(yīng)用程序時(shí),要替換的、被指定為功能部件的一部分的方法調(diào)用完全被截獲并傳遞給ReplacementFeature類。
通常,即使在同一個(gè)實(shí)現(xiàn)環(huán)境中,我們也可以用不同的方法來實(shí)現(xiàn)同一種設(shè)計(jì)模式。實(shí)現(xiàn)上例的另一種方法是實(shí)現(xiàn)兩個(gè)獨(dú)立的通知。
最后需要注意的是,使用Cuckoo's Egg設(shè)計(jì)模式置換的功能部件,不管它是跨越bean的還是在一個(gè)類中,它的生命周期與它所置換的功能部件的目標(biāo)生命周期匹配。在上例中這沒什么問題,因?yàn)橹挥幸粋€(gè)功能部件實(shí)例被置換了,而且唯一的Cuckoo's Egg通知只維護(hù)一個(gè)替代功能部件。
這個(gè)例子非常簡單,而在實(shí)踐中,您很可能必須處理大量需要用各自的Cuckoo's Egg實(shí)例置換的功能部件實(shí)例。在這種情況下,單個(gè)的方面實(shí)例需要被關(guān)聯(lián)到單個(gè)的要置換的功能部件實(shí)例。本系列的下一篇文章將會(huì)考慮方面生命周期的用法,屆時(shí)將解決這個(gè)問題。
結(jié)束語
本文介紹了如何在Spring框架內(nèi)謹(jǐn)慎使用around形式的通知。around形式的通知常用于實(shí)現(xiàn)Cuckoo's Egg設(shè)計(jì)模式時(shí),所以我們引入了一個(gè)例子來說明如何使用Spring AOP實(shí)現(xiàn)這種面向方面設(shè)計(jì)模式。
在本系列的第三部分中,您將看到如何使用Spring框架中其他的AOP基本概念。這些概念包括:控制方面生命周期、使用基于introduction通知的積極方面改變應(yīng)用程序的靜態(tài)結(jié)構(gòu),以及使用control flow切入點(diǎn)實(shí)現(xiàn)對(duì)方面編織的更細(xì)微的控制。
參考資料
原文出處
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公司的一名軟件工程師,他負(fù)責(zé)Java和分布式系統(tǒng),但是他目前主要的興趣在面向方面領(lǐng)域,尤其是AspectJ。 |
時(shí)間:2005-12-16
作者:
Russell Miles瀏覽次數(shù):
5882
本文關(guān)鍵字:
Java,?
Spring,?
aspect-oriented programming,?
AOP,?
crosscutting,?
面向方面編程,?
橫切,?
延遲加載 作為這個(gè)介紹Spring框架中的面向方面編程(Aspect-Oriented Programming,AOP)的系列的第一部分,本文介紹了使您可以使用Spring中的面向方面特性進(jìn)行快速開發(fā)的基礎(chǔ)知識(shí)。使用跟蹤和記錄方面(面向方面領(lǐng)域的HelloWorld)作為例子,本文展示了如何使用Spring框架所獨(dú)有的特性來聲明切入點(diǎn)和通知以便應(yīng)用方面。本系列的第二部分將更深入地介紹如何運(yùn)用Spring中的所有通知類型和切入點(diǎn)來實(shí)現(xiàn)更實(shí)用的方面和面向方面設(shè)計(jì)模式。對(duì)于AOP的更一般性的介紹,請(qǐng)查看ONJava站點(diǎn)上Graham O'Regan的文章,“Introduction to Aspect-Oriented Programming”。
本文的目的不是要介紹構(gòu)成模塊化J2EE系統(tǒng)——即Spring框架——的所有重要元素,我們將只把注意力放在Spring所提供的AOP功能上。由于Spring的模塊化設(shè)計(jì)方法,我們可以只使用該框架的AOP元素,而無需對(duì)構(gòu)成Spring框架的其他模塊做太多考慮。
在AOP方面,Spring提供了什么?
“它的目標(biāo)不是提供最完善的AOP實(shí)現(xiàn)(雖然Spring AOP非常強(qiáng)大);而是要提供AOP實(shí)現(xiàn)與Spring IoC的緊密集成,以便幫助解決企業(yè)應(yīng)用中的常見問題。”
Spring Framework參考文檔
為了實(shí)現(xiàn)這個(gè)目標(biāo),Spring框架目前支持一組AOP概念,從切入點(diǎn)到通知。本文將展示如何使用Spring框架中所實(shí)現(xiàn)的如下AOP概念:
- 通知(Advice):如何將before通知、afterReturning通知和afterThrowing通知聲明為bean。
- 切入點(diǎn)(Pointcut):如何聲明靜態(tài)切入點(diǎn)邏輯以將XML Spring Bean Configuration文件中的所有內(nèi)容聯(lián)系在一起。
- Advisor:關(guān)聯(lián)切入點(diǎn)定義與通知bean的方式。
設(shè)置場景:一個(gè)簡單的例子應(yīng)用程序
“一般而言,Spring并不是預(yù)描述的。雖然使用好的實(shí)踐非常容易,但是它避免強(qiáng)制推行一種特定的方法。”
Spring Framework參考文檔
要試用Spring框架的AOP功能,首先我們要?jiǎng)?chuàng)建一個(gè)簡單的Java應(yīng)用程序。IbusinessLogic接口和BusinessLogic類為Spring框架中的bean提供了簡易構(gòu)件塊。雖然該接口對(duì)于我們的簡單應(yīng)用程序邏輯來說不是必需的,但是它是Spring框架所推薦的良好實(shí)踐。
public interface IBusinessLogic
{
public void foo();
}
public class BusinessLogic
implements IBusinessLogic
{
public void foo()
{
System.out.println(
"Inside BusinessLogic.foo()");
}
}
可以編寫MainApplication類,借此練習(xí)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類及其關(guān)聯(lián)接口中沒有什么需要注意的。但是,MainApplication類初始化BusinessLogic對(duì)象的方式很有意思。通過使用ctx.getBean("businesslogicbean")調(diào)用,MainApplication將加載和管理BusinessLogic類的bean實(shí)例的任務(wù)轉(zhuǎn)交給了Spring框架。
允許Spring控制BusinessLogic bean的初始化,這使得Spring運(yùn)行時(shí)有機(jī)會(huì)在bean被返回給應(yīng)用程序之前執(zhí)行J2EE系統(tǒng)所需的所有與bean相關(guān)的管理任務(wù)。然后Spring運(yùn)行時(shí)配置可以決定對(duì)bean應(yīng)用哪些任務(wù)和模塊。該配置信息由一個(gè)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,指定要加載一個(gè)接口與IbusinessLogic相匹配的bean。該bean隨后被關(guān)聯(lián)到BusinessLogic實(shí)現(xiàn)類。看起來好像是費(fèi)了很大力氣只為了加載一個(gè)簡單的bean并調(diào)用一個(gè)方法,但是您要知道,這個(gè)配置文件只是使Spring框架可以透明地對(duì)應(yīng)用程序應(yīng)用其組件的眾多特性的一個(gè)體現(xiàn)。
圖1顯示了基本的順序圖:MainApplication原樣執(zhí)行,沒有應(yīng)用方面。

圖1.沒有對(duì)BusinessLogic bean應(yīng)用方面時(shí)的順序圖
請(qǐng)查看本文末尾處的參考資料,獲取這個(gè)簡單Spring應(yīng)用程序的源代碼。
應(yīng)用方法跟蹤(Method Tracing)方面
可能最基本的方面就是方法跟蹤方面了。這可能是您找得到的最簡單的方面了,因此它是研究新的AOP實(shí)現(xiàn)的一個(gè)很好的起點(diǎn)。
方法跟蹤方面在一個(gè)目標(biāo)應(yīng)用程序內(nèi)捕獲對(duì)所跟蹤的方法的調(diào)用以及方法的返回值,并以某種方式顯示這種信息。在AOP中,通知的before和after類型用于捕獲這些類型的聯(lián)結(jié)點(diǎn),因?yàn)檫@兩種通知可以在方法調(diào)用聯(lián)結(jié)點(diǎn)之前或之后觸發(fā)。使用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() +
")");
}
}
這兩個(gè)類都通過實(shí)現(xiàn)Spring框架的適當(dāng)通知接口而表示了特定的通知。每種類型的通知都指定實(shí)現(xiàn)before(..)或afterReturning(..)方法,以便使Spring運(yùn)行時(shí)可以告訴通知適當(dāng)?shù)穆?lián)結(jié)點(diǎn)會(huì)在何時(shí)出現(xiàn)。值得注意的是,TracingAfterAdvice實(shí)際上是從AfterReturningAdvice擴(kuò)展而來的,表示只有在聯(lián)結(jié)點(diǎn)在無異常的情況下獲得返回值時(shí)才運(yùn)行通知。
為了將通知與應(yīng)用程序中的適當(dāng)聯(lián)結(jié)點(diǎn)關(guān)聯(lián)起來,必須對(duì)springconfig.xml進(jìn)行一些修改。
<?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。每個(gè)advisor都可能截獲所有bean所關(guān)聯(lián)到的聯(lián)結(jié)點(diǎn)。Advisor本身就是bean,而它唯一的作用就是將切入點(diǎn)定義與通知bean關(guān)聯(lián)起來。本例中的切入點(diǎn)定義是在靜態(tài)對(duì)象層次結(jié)構(gòu)中指定相關(guān)聯(lián)結(jié)點(diǎn)的正則表達(dá)式。
因?yàn)楸纠惺褂昧薿rg.springframework.aop.support.RegexpMethodPointcutAdvisor切入點(diǎn)advisor,切入點(diǎn)邏輯是使用正則表達(dá)式指定的。正則表達(dá)式用于識(shí)別公有接口對(duì)IbusinessLogici接口的聯(lián)結(jié)點(diǎn)。下面是一些可以用來指定IBusinessLogic接口上的不同聯(lián)結(jié)點(diǎn)集合的正則表達(dá)式例子:
- <value>.*</value>:該表達(dá)式選擇advisor所關(guān)聯(lián)到的一個(gè)或多個(gè)bean上的所有聯(lián)結(jié)點(diǎn)。
- <value>./IBusinessLogic/.foo</value>:該表達(dá)式只選擇IbusinessLogic接口上的foo()方法的聯(lián)結(jié)點(diǎn)。如果是advisor所關(guān)聯(lián)到的bean,則該表達(dá)式只選擇IBusinessLogic接口上的聯(lián)結(jié)點(diǎn)。
springconfig.xml文件中最后的bean聲明指定實(shí)現(xiàn)通知bean的類。
既然已經(jīng)指定了跟蹤方面的正確配置,那么下一次執(zhí)行MainApplication時(shí),這些方面就會(huì)在初始化過程中被編織進(jìn)去,而BusinessLogic bean中的所有方法都將被跟蹤,如圖2所示。

圖2. 方法跟蹤方面應(yīng)用到BusinessLogic bean之后的順序圖(單擊圖像查看大圖)
方法跟蹤方面和例子應(yīng)用程序的源代碼可在本文末尾的參考資料小節(jié)進(jìn)行下載。
方面的重用
可以對(duì)方法跟蹤方面進(jìn)行擴(kuò)展,提供一個(gè)稍微復(fù)雜的記錄(Logging)方面。記錄方面提供了一個(gè)很不錯(cuò)的重用例子,因?yàn)橛涗浄矫嫠璧脑S多特性都已經(jīng)包含在方法跟蹤方面中了。
在本例中,記錄方面擴(kuò)展了方法跟蹤方面,以便顯示附加的與(在應(yīng)用程序的執(zhí)行過程中)所引發(fā)的異常有關(guān)的信息。
要完全使用記錄方面,需要對(duì)應(yīng)用程序做一些更改。BusinessLogicException異常類提供了一個(gè)可以由IBusinessLogicInterface接口和BusinessLogic實(shí)現(xiàn)類新增的void bar()方法引發(fā)的異常。
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類現(xiàn)在將對(duì)void bar()方法進(jìn)行一次額外的調(diào)用,并處理選中的、可能由該方法引發(fā)的異常。
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.");
}
}
應(yīng)用記錄方面的最后一步是修改springconfig.xml配置文件,使其包含新添加的LoggingThrowsAdvice通知。
圖3顯示了運(yùn)行MainApplication并使用Spring框架應(yīng)用了記錄方面的UML順序圖。

圖3. 記錄方面應(yīng)用到BusinessLogic bean之后的順序圖(單擊圖像查看大圖)
此處的記錄方面清楚地說明了如何重用現(xiàn)有方面以及如何在Spring框架中使用通知的throws形式。通過為before和after通知聲明新的通知來重寫現(xiàn)有的方法跟蹤方面實(shí)現(xiàn),可以實(shí)現(xiàn)更復(fù)雜的記錄方面,記錄到更復(fù)雜的記錄框架,比如LOG4J。關(guān)于記錄方面和例子應(yīng)用程序的源代碼,請(qǐng)參見本文末尾的參考資料小節(jié)。
結(jié)束語
本文展示了使用Spring框架中的基本AOP結(jié)構(gòu)所應(yīng)用的一些簡單方面。在本系列的下一篇文章中,我們將介紹一些更實(shí)用的方面,探討方面的生命周期,使用Spring框架的around通知,并使用Spring來應(yīng)用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公司的一名軟件工程師,他負(fù)責(zé)Java和分布式系統(tǒng),但是他目前主要的興趣在面向方面領(lǐng)域,尤其是AspectJ。 |
在本文中,作者通過一個(gè)Web Service訪問的實(shí)例,具體描述了SOA應(yīng)用中所遇到的一系列具體問題,并描述如何利用IoC和AOP等技術(shù)進(jìn)行代碼重構(gòu),從而構(gòu)建結(jié)構(gòu)更加良好、靈活的SOA應(yīng)用。
1.引言
SOA是一種構(gòu)造分布式系統(tǒng)的方法,它將業(yè)務(wù)應(yīng)用功能以服務(wù)的形式提供出來,以便更好的復(fù)用、組裝和與外部系統(tǒng)集成,從而降低開發(fā)成本,提高開發(fā)效率。SOA的目標(biāo)是為企業(yè)構(gòu)建一個(gè)靈活,可擴(kuò)展的IT基礎(chǔ)架構(gòu)來更好地支持隨需應(yīng)變的商務(wù)應(yīng)用。
隨著SOA技術(shù)和產(chǎn)品的不斷成熟,現(xiàn)在越來越多的用戶開始了解并認(rèn)同SOA的理念,但對(duì)SOA項(xiàng)目的實(shí)施還缺乏信心。其主要原因是:SOA應(yīng)用開發(fā)還相對(duì)比較復(fù)雜。
一年多來,本文作者所在的部門已經(jīng)從事了許多國內(nèi)外的SOA項(xiàng)目的實(shí)施和支持工作,積累了許多SOA應(yīng)用開發(fā)經(jīng)驗(yàn)。我們希望能夠通過一系列的文章與讀者分享這些想法,幫助您更好地構(gòu)建SOA應(yīng)用。
本文將從Web Service調(diào)用入手,在解決一系列具體問題的過程中,使用IoC (Inversion of Control) 和AOP (Aspect- Oriented Programming) 等方法重構(gòu)Web Service的訪問代碼,使得業(yè)務(wù)邏輯與Web Service訪問解耦,為您提供一個(gè)更加靈活和易于擴(kuò)展的訪問模式。
Spring是一個(gè)流行的輕量級(jí)容器,對(duì)IoC和AOP提供了良好的支持。本文為您提供了一個(gè)基于Spring的實(shí)現(xiàn)供您下載學(xué)習(xí)。示例代碼工程使用Eclipse3.1/3.02和JDK1.4開發(fā), 您還需要Spring 1.2.5和Axis1.3提供的支持。詳細(xì)的下載信息請(qǐng)參見參考資源部分。
2.Web Service調(diào)用
Web Service是目前實(shí)現(xiàn)SOA應(yīng)用的一項(xiàng)基本的,適用的技術(shù),它為服務(wù)的訪問提供了一個(gè)被廣泛接受的開放標(biāo)準(zhǔn)。為了便于說明問題,我們將使用XMethods 網(wǎng)站(http://www.xmethods.net/)發(fā)布的貨幣兌換服務(wù)作為示例。并針對(duì)JAX-RPC 1.1,說明如何編寫Web Service 的調(diào)用代碼。
2.1 示例說明
http://xmethods.net 作為最早推出Web Service實(shí)際示例的網(wǎng)站,提供了很多優(yōu)秀的Web Service 樣例。其中有一個(gè)匯率計(jì)算服務(wù),可以返回兩個(gè)國家之間的貨幣兌換比例。獲取該服務(wù)的詳細(xì)信息,請(qǐng)參考該服務(wù)的服務(wù)描述文檔(獲取WSDL 文檔) 。在此就不具體解析該服務(wù)描述文檔了。讀者可以從WSDL2Java生成的接口中了解該服務(wù)的用法:
public interface CurrencyExchangePortType extends java.rmi.Remote {
public float getRate(String country1, String country2) throws java.rmi.RemoteException;
}
|
2.2 客戶端調(diào)用方法
JAX-RPC作為Java平臺(tái)的RPC服務(wù)調(diào)用標(biāo)準(zhǔn)接口,為Web Service客戶端調(diào)用提供了3種方法,分別是DII,動(dòng)態(tài)代理,和靜態(tài)Stub。 DII(Dynamic Invocation Interface)采用直接調(diào)用方式,可以在程序中設(shè)置諸多的調(diào)用屬性,使用較為靈活,但是調(diào)用過程卻相對(duì)繁瑣復(fù)雜,易造成代碼膨脹且可重用性低,每次調(diào)用不同的Web Service都要重復(fù)進(jìn)行大量編碼。
JAX-RPC中動(dòng)態(tài)代理(Dynamic Proxy)的方法實(shí)現(xiàn)對(duì)Web Service的動(dòng)態(tài)調(diào)用,可以在運(yùn)行時(shí)根據(jù)用戶定義的Client端接口創(chuàng)建適配對(duì)象。從而避免了直接操作底層的接口,減少了客戶端的冗余,屏蔽了調(diào)用相關(guān)的復(fù)雜性。
使用靜態(tài)Stub和Service Locator是目前最常用的調(diào)用方式。JAX-RPC使用靜態(tài)的Stub方式包裝對(duì)底層接口的調(diào)用,從而提供一種更為簡便的調(diào)用方式。使用該方式需要利用支持環(huán)境(比如Axis)所提供的工具根據(jù)WSDL預(yù)生成Web Service客戶端的實(shí)現(xiàn)代碼。因此如果服務(wù)的WSDL發(fā)生變化,就必須重新生成新的客戶端代碼并進(jìn)行重新部署。
為了更詳細(xì)的了解靜態(tài)Stub的調(diào)用方式,您可以將示例代碼的WebServiceClient.jar導(dǎo)入到您現(xiàn)有Eclipse工作區(qū)之中。
客戶端生成代碼包括如下4個(gè)類:如圖 1 所示:
圖 1: 客戶端代碼類圖
在上圖中包括的幾個(gè)類中:
CurrencyExchangePortType:服務(wù)端點(diǎn)接口,定義了Web Service的方法簽名。
CurrencyExchangeService:Service接口,定義了獲取服務(wù)端點(diǎn)接口的方法。
CurrencyExchangeServiceLocator:ServiceLocator類,實(shí)現(xiàn)了Service接口。
CurrencyExchangeBindingStub: Stub實(shí)現(xiàn)類,實(shí)現(xiàn)了服務(wù)端點(diǎn)接口,封裝了對(duì)Web Service訪問的底層邏輯。
使用Stub調(diào)用Web Service的過程也非常簡單,讀者可以參考清單 1:
清單 1:Web Service 調(diào)用代碼示例
try {
//創(chuàng)建ServiceLocator
CurrencyExchangeServiceLocator locator = new
CurrencyExchangeServiceLocator();
//設(shè)定端點(diǎn)地址
URL endPointAddress = new URL("http://services.xmethods.net:80/soap");
//創(chuàng)建Stub實(shí)例
CurrencyExchangePortType stub =
locator.getCurrencyExchangePort(endPointAddress);
//設(shè)定超時(shí)為120秒
((CurrencyExchangeBindingStub)stub).setTimeout(120000);
//調(diào)用Web Service計(jì)算人民幣與美元的匯率
float newPrice = stub.getRate("China", "USA") * 100;
} catch (MalformedURLException mex) {
//...
} catch (ServiceException sex) {
//...
} catch (RemoteException rex) {
//...
}
|
3.重構(gòu)Web Service調(diào)用代碼
3.1 實(shí)例代碼中的"壞味道"
上面的基于Service Locator的Web Service訪問代碼雖然簡單但暴露出以下幾個(gè)問題:
1.訪問Web Service所需的配置代碼被嵌入應(yīng)用邏輯之中
在Web Service調(diào)用中,我們需要設(shè)定一系列必要的參數(shù)。比如:服務(wù)端點(diǎn)地址、用戶名/密碼、超時(shí)設(shè)定等等。這些參數(shù)在開發(fā)和運(yùn)行環(huán)境中都有可能發(fā)生變化。我們必須提供一種機(jī)制:在環(huán)境變化時(shí),不必修改源代碼就可以改變Web Service的訪問配置。
2 客戶端代碼與Web Service訪問代碼綁定
在上面的代碼中,業(yè)務(wù)邏輯與Web Service的Stub創(chuàng)建和配置代碼綁定在一起。這也不是一種良好的編程方式。客戶端代碼只應(yīng)關(guān)心服務(wù)的接口,而不應(yīng)關(guān)心服務(wù)的實(shí)現(xiàn)和訪問細(xì)節(jié)。比如,我們既可以通過Web Service的方式訪問遠(yuǎn)程服務(wù),也可以通過EJB的方式進(jìn)行訪問。訪問方式對(duì)業(yè)務(wù)邏輯應(yīng)該是透明的。
這種分離客戶端代碼與服務(wù)訪問代碼的方式也有利于測試。這樣在開發(fā)過程中,負(fù)責(zé)集成的程序員就可能在遠(yuǎn)程服務(wù)還未完全實(shí)現(xiàn)的情況下,基于服務(wù)接口編寫集成代碼,并通過編寫POJO(Plain Old Java Object)構(gòu)建偽服務(wù)實(shí)現(xiàn)來進(jìn)行單元測試和模擬運(yùn)行。這種開發(fā)方式對(duì)于保證分布式系統(tǒng)代碼質(zhì)量具有重要意義。
因此,為了解決上面的問題我們需要:
1、將Web Service訪問的配置管理與代碼分離;
2、解除客戶端代碼與遠(yuǎn)程服務(wù)之間的依賴關(guān)系;
3.2 利用IoC模式進(jìn)行重構(gòu)代碼
我們先介紹在Core J2EE Patterns一書中提到的一種業(yè)務(wù)層模式:Business Delegate。它所要解決的問題是屏蔽遠(yuǎn)程服務(wù)訪問的復(fù)雜性。它的主要思想就是將Business Delegate作為遠(yuǎn)程服務(wù)的客戶端抽象,隱藏服務(wù)訪問細(xì)節(jié)。Business Delegate還可以封裝并改變服務(wù)調(diào)用過程,比如將遠(yuǎn)程服務(wù)調(diào)用拋出的異常(例如RemoteException)轉(zhuǎn)換為應(yīng)用級(jí)別的異常類型。
其類圖如圖 2 所示:
圖 2:Business Delegate 模式的類圖圖解
Business Delegate模式實(shí)現(xiàn)很好地實(shí)現(xiàn)了客戶端與遠(yuǎn)程訪問代碼的解耦,但它并不關(guān)注Delegate與遠(yuǎn)程服務(wù)之間的解耦。為了更好解決Business Delegate和遠(yuǎn)程服務(wù)之間的依賴關(guān)系,并更好地進(jìn)行配置管理,我們可以用IoC模式來加以解決。
IoC(Inversion of Contro)l意為控制反轉(zhuǎn),其背后的概念常被表述為"好萊塢法則":"Don't call me, I'll call you." IoC將一部分責(zé)任從應(yīng)用代碼交給framework(或者控制器)來做。通過IoC可以實(shí)現(xiàn)接口和具體實(shí)現(xiàn)的高度分離,降低對(duì)象之間的耦合程度。Spring是一個(gè)非常流行的IoC容器,它通過配置文件來定義對(duì)象的生命周期和依賴關(guān)系,并提供了良好的配置管理能力。
現(xiàn)在我們來重構(gòu)我們的Web Service應(yīng)用程序,我們首先為Business Delegate定義一個(gè)接口類型,它提供了一個(gè)應(yīng)用級(jí)組件接口,所有客戶端都應(yīng)通過它來執(zhí)行匯率計(jì)算,而不必關(guān)心實(shí)現(xiàn)細(xì)節(jié),如清單 2 所示:
清單 2:接口定義的代碼示例
Public interface CurrencyExchangeManager {
//貨幣兌換計(jì)算
//新價(jià)格 = 匯率 * 價(jià)格
public float calculate(String country1, String country2, float price)
throws CurrencyExchangeException;
}
|
Business Delegate的實(shí)現(xiàn)非常簡單,主要工作是包裝匯率計(jì)算 Web Service的調(diào)用,如清單 3 所示。
清單 3:Business Delegate的代碼示例
public class CurrencyExchangeManagerImpl implements CurrencyExchangeManager {
//服務(wù)實(shí)例
private CurrencyExchangePortType stub;
//獲取服務(wù)實(shí)例
public CurrencyExchangePortType getStub() {
return stub;
}
//設(shè)定服務(wù)實(shí)例
public void setStub(CurrencyExchangePortType stub) {
this.stub = stub;
}
//實(shí)現(xiàn)貨幣兌換
public float calculate(String country1, String country2, float price)
throws CurrencyExchangeException {
try {
//通過Stub調(diào)用WebService
float rate = stub.getRate(country1, country2);
return rate * price;
} catch (RemoteException rex) {
throw new CurrencyExchangeException(
"Failed to get exchange rate!", rex);
}
}
}
|
下面我們需要討論如何利用Spring的IoC機(jī)制,來創(chuàng)建和配置對(duì)象,并定義它們的依賴關(guān)系。
Spring利用類工廠來創(chuàng)建和配置對(duì)象。在Spring框架中,已經(jīng)為基于JAX-RPC的Web Service調(diào)用提供了一個(gè)客戶端代理的類工廠實(shí)現(xiàn):JaxRpcPortProxyFactoryBean。在配置文件bean.xml中,我們將使用JaxRpcPortProxyFactoryBean來創(chuàng)建和配置Web Service的客戶端代理"CurrencyExchangeService",如清單 5 所示。我們還將定義一個(gè)名為"CurrencyExchangeManager"的CurrencyExchangeManagerImpl實(shí)例,并建立它與CurrencyExchangeService之間的依賴關(guān)系。有關(guān)Spring 配置和JaxRpcPortProxyFactoryBean的使用細(xì)節(jié)請(qǐng)參見參考資料。
清單 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>
|
最后我們創(chuàng)建一個(gè)測試程序來驗(yàn)證我們的代碼,如清單6 所示:
清單 6:測試代碼
public class Main {
// For test only
public static void main(String[] args) {
// Spring Framework將根據(jù)配置文件創(chuàng)建并配置CurrencyExchangeManager實(shí)例
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
// 獲取CurrencyExchangeManager實(shí)例
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();
}
}
}
|
此時(shí)運(yùn)行測試客戶端,等待片刻將會(huì)看見測試結(jié)果,如清單 7 所示:
清單 7:測試結(jié)果。
注:該結(jié)果會(huì)隨著匯率的變化而出現(xiàn)不同的值。
該程序的類圖和順序圖如圖3及圖4所示:
圖 3:示例程序的類圖
從上面的類圖我們可以看到,我們的測試程序(Main.java)通過Spring框架獲取了BusinessDelegate的實(shí)例。而且Spring 框架還會(huì)根據(jù)配置中的依賴關(guān)系,在運(yùn)行時(shí)將Web Service的客戶端代理" 注射"到CurrencyExchangeManagerImpl實(shí)例中,這就是依賴注入(Dependency Injection)。通過這種方式解決了應(yīng)用邏輯和BusinessDelegate之間的依賴關(guān)系,以及BusinessDelegate的實(shí)現(xiàn)與遠(yuǎn)程服務(wù)之間的依賴關(guān)系,如圖 4 所示。
圖 4: 示例程序的順序圖
Spring框架提供的ApplicationContext實(shí)現(xiàn)會(huì)根據(jù)配置文件中的描述信息來實(shí)現(xiàn)對(duì)象生命周期管理,配置管理以及依賴管理等功能。這一切對(duì)于應(yīng)用程序是透明的,應(yīng)用程序代碼只依賴接口進(jìn)行編程,而無需考慮其它復(fù)雜問題。無論是Web Service的配置發(fā)生變化,或是改用不同的服務(wù)實(shí)現(xiàn)時(shí),都不會(huì)對(duì)客戶端應(yīng)用代碼的產(chǎn)生影響。這很好地實(shí)現(xiàn)了業(yè)務(wù)邏輯與Web Service調(diào)用之間的解耦。
3.3 構(gòu)建自己的 Web Service代理工廠
Spring所提供的JaxRpcPortProxyFactoryBean封裝了構(gòu)造Web Service客戶端代理的細(xì)節(jié),可以通過參數(shù)配置來創(chuàng)建Dynamic Proxy和DII類型的Web Service客戶端代理。(如果您希望深入了解其實(shí)現(xiàn)細(xì)節(jié)可以參考o(jì)rg.springframework.remoting.jaxrpc包下的源代碼。)但由于JaxRpcPortProxyFactoryBean需要使用者對(duì)WSDL中Port,Service,名空間等概念有深入的了解;而且如果Web Service使用了復(fù)雜數(shù)據(jù)類型,開發(fā)人員需要手工定義類型映射代碼。所以JaxRpcPortProxyFactoryBean并不適合Web Service的初學(xué)者來使用。
為了進(jìn)一步簡化Web Service代理的創(chuàng)建,并幫助讀者更好地理解類工廠在Spring框架下的作用。我們提供了一個(gè)基于靜態(tài)Stub的Web Service客戶端代理工廠實(shí)現(xiàn)。其核心代碼非常簡單,就是通過ServiceLocator提供的方法來創(chuàng)建Web Service客戶端代理。
其主要代碼如清單8所示:
清單8:靜態(tài)代理工廠的代碼
public class WebServiceStubFactoryBean implements FactoryBean,
InitializingBean {
private Class serviceInterface;
private Class serviceLocator;
private Object stub;
…
public void afterPropertiesSet() throws Exception {
//利用serviceLocator和服務(wù)接口創(chuàng)建Web Service客戶端代理
stub = ((javax.xml.rpc.Service)
serviceLocator.newInstance()).getPort(serviceInterface);
//為Stub設(shè)定endpointAddress,usernam, 超時(shí)等參數(shù)
preparePortStub((javax.xml.rpc.Stub) stub);
}
public Object getObject() {
// 返回客戶端代理
return stub;
}
public Class getObjectType() {
// 返回服務(wù)接口
return serviceInterface;
}
public boolean isSingleton() {
return true;
}
}
|
我們需要修改配置文件bean.xml中有關(guān)Web Service代理創(chuàng)建的部分,讓新的Web Service 代理工廠發(fā)揮作用。如清單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框架,雖然我們已經(jīng)替換了對(duì)象的類工廠,卻并不需要更改應(yīng)用代碼。通過Spring框架的IoC機(jī)制,我們可以完全使用面向接口的編程方式,而將實(shí)現(xiàn)的創(chuàng)建、配置和依賴管理交由Spring在運(yùn)行時(shí)完成。即使實(shí)現(xiàn)發(fā)生了變化,也不需要改變應(yīng)用程序結(jié)構(gòu)。
4.新的思考
故事并沒有結(jié)束,在開發(fā)過程中,我們又遇到了一系列關(guān)于Web Service調(diào)用的問題。
4.1性能
系統(tǒng)性能是分布式應(yīng)用中的一個(gè)重要問題。許多用戶都擔(dān)心由Web Service技術(shù)所引入的額外開銷是否會(huì)影響到產(chǎn)品的性能。隨著技術(shù)的不斷發(fā)展,Web Service引擎性能已經(jīng)有了很大提高,一般來說使用Web Service的系統(tǒng)的性能可以滿足絕大部分應(yīng)用的需求。但在特定情況下,如果系統(tǒng)性能無法滿足客戶需求,我們首先需要對(duì)系統(tǒng)性能進(jìn)行科學(xué)地分析和測定才能定位真正的性能瓶頸。這個(gè)問題在上文簡單的示例中并不難解決,只需要在Web Service調(diào)用前后加入日志代碼記錄調(diào)用時(shí)間即可實(shí)現(xiàn)。但在實(shí)際系統(tǒng)中,比如一個(gè)產(chǎn)品目錄的Web Service可能提供數(shù)十種查詢方法,而程序中很多組件都會(huì)依賴于該服務(wù)提供的查詢功能。如果在系統(tǒng)中所有的地方加入性能測定代碼,這個(gè)工作就變得非常繁瑣和困難。我們需要用一種更加優(yōu)雅的解決方式,在增添新功能的同時(shí)并不影響系統(tǒng)代碼或結(jié)構(gòu)。
4.2緩存
在項(xiàng)目實(shí)踐中,一個(gè)有效的改善Web Service系統(tǒng)性能的方法就是利用緩存來減少Web Service的重復(fù)調(diào)用。在具體實(shí)現(xiàn)中我們可以采用客戶端緩存和服務(wù)器端緩存等不同方式,他們具有不同的特點(diǎn)和適用范圍。在本文例子中,我們希望實(shí)現(xiàn)客戶端緩存來提高系統(tǒng)性能。但由于Web Service業(yè)務(wù)邏輯的差別,我們希望能夠?yàn)樘囟ǖ腤eb Service提供特定的緩存策略,而且這些策略應(yīng)該是能夠被靈活配置的,它們不應(yīng)于應(yīng)用程序的邏輯代碼耦合在一起。
4.3故障恢復(fù):
對(duì)于Web Service應(yīng)用,系統(tǒng)的可用性也是一個(gè)需要考慮的重要問題。在運(yùn)行時(shí)由于網(wǎng)絡(luò)運(yùn)行環(huán)境的復(fù)雜性和不確定性,用戶希望能夠?qū)eb Service訪問提供一定的故障恢復(fù)機(jī)制:比如重試或者訪問備份服務(wù)(當(dāng)系統(tǒng)在調(diào)用Web Service失敗后,使用備份Web Service的服務(wù)地址來繼續(xù)訪問)。這些故障恢復(fù)策略應(yīng)該是可配置的,對(duì)應(yīng)用邏輯透明的。
5.使用AOP解決SOA應(yīng)用中的Crosscutting Concern
通過對(duì)上邊一系列問題的分析,讀者也許會(huì)發(fā)現(xiàn)這些問題并不是Web Service訪問的核心問題,但會(huì)影響系統(tǒng)中許多不同的組件。而且其中一些問題需要我們能夠靈活配置不同的實(shí)現(xiàn)策略,因此我們不應(yīng)該將處理這些問題的代碼與應(yīng)用代碼混合。
下面我們將利用AOP(Aspect-Oriented Programming)提供的方法來解決上述的問題。AOP是一種新興的方法學(xué),它最基本的概念就是關(guān)注隔離(Separation of Concern)。AOP提供了一系列的技術(shù)使得我們能夠從代碼中分離那些影響到許多系統(tǒng)模塊的crosscutting concerns,并將他們模塊化為Aspects。AOP的主要目的仍然是解耦,在分離關(guān)注點(diǎn)后,才能將關(guān)注點(diǎn)的變更控制一定范圍內(nèi),增加程序的靈活性,才能使得關(guān)注能夠根據(jù)需求和環(huán)境作出隨時(shí)調(diào)整。
我們將利用Spring所提供的AOP功能支持來解決以上問題。這里我們只簡單地介紹涉及到的AOP基本概念以及實(shí)現(xiàn),如果您希望更好地了解AOP的概念以及Spring AOP支持的細(xì)節(jié)請(qǐng)參見參考資料。
- Joinpoint 是程序的運(yùn)行點(diǎn)。在Spring AOP中,一個(gè)Joinpoint對(duì)應(yīng)著一個(gè)方法調(diào)用。
- Advice 定義了AOP框架在特定的Joinpoint的處理邏輯。Spring AOP框架通過interceptor方式實(shí)現(xiàn)了advice,并且提供了多種advice類型。其中最基本的"around advice"會(huì)在一個(gè)方法調(diào)用之前和之后被執(zhí)行。
下面我們將利用Spring提供的MethodInterceptor來為Web Service調(diào)用實(shí)現(xiàn)我們的定義的處理邏輯。
5.1 PerformanceMonitorInterceptor
性能測量是AOP最簡單的例子之一,我們可以直接利用Spring提供的實(shí)現(xiàn)在bean.xml中聲明我們的WebServicePerformanceMonitorInterceptor。
5.2 CacheInterceptor
為了不引入緩存策略的復(fù)雜性,我們只提供了一個(gè)利用HashMap的簡單實(shí)現(xiàn):它利用 Web Service的調(diào)用參數(shù)列表作為HashMap鍵值。在Web Service調(diào)用之前,首先檢查緩存中是否擁有與現(xiàn)在參數(shù)列表相同的項(xiàng),如果有則返回緩存的結(jié)果,否則調(diào)用Web Service并將<參數(shù)列表,結(jié)果>記錄在HashMap中。在實(shí)際應(yīng)用中,您應(yīng)該根據(jù)具體情況來選擇、構(gòu)造適合Web Service的業(yè)務(wù)特性的Cache實(shí)現(xiàn),也可以采用成熟的Cache實(shí)現(xiàn)。
在下面代碼實(shí)現(xiàn)中有一個(gè)生成Web Service調(diào)用主鍵的小技巧。因?yàn)閃eb Service引擎要求所有調(diào)用參數(shù)必須是可序列化的,所以我們可以利用Java提供的序列化功能來實(shí)現(xiàn)對(duì)象的克隆。如清單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) {
// 通過序列化/反序列化來克隆對(duì)象
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;
}
//基于參數(shù)列表數(shù)組,生成用于HashMap的鍵值
public Object generateKey(Object[] args) throws Exception {
Object[] newArgs = (Object[]) cloneObject(args);
List key = Arrays.asList(newArgs);
return key;
}
//實(shí)現(xiàn)使用緩存技術(shù)的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中沒有緩存結(jié)果,調(diào)用服務(wù)執(zhí)行生成用于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
下面代碼提供了一個(gè)基于服務(wù)備份切換的故障恢復(fù)實(shí)現(xiàn),在運(yùn)行時(shí),如果Interceptor檢測到服務(wù)調(diào)用由于網(wǎng)絡(luò)故障拋出異常時(shí),它將使用備份服務(wù)的端點(diǎn)地址并重新調(diào)用。如清單11所示:
清單 11: SimpleFailoverInterceptor的代碼示例
public class SimpleFailoverInterceptor implements MethodInterceptor { …
…
//實(shí)現(xiàn)支持端點(diǎn)運(yùn)行時(shí)切換的invoke方法
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
try {
result = methodInvocation.proceed();
} catch (Throwable ex) {
if (isNetworkFailure(ex)) {
//切換服務(wù)端點(diǎn)地址
switchEndPointAddress((Stub) methodInvocation.getThis());
result = methodInvocation.proceed();
} else {
throw ex;
}
}
return result;
}
}
|
為了支持備份服務(wù)切換的功能,我們在WebServicePortProxyFactoryBean中為填加了配置參數(shù)"endpointAddress2",它會(huì)在創(chuàng)建的Web Service客戶端代理對(duì)象中記錄備份URL。
我們可以在CurrencyExchangeService加入下列參數(shù)來試驗(yàn)SimpleFailoverInterceptor的功能。其中第一個(gè)端點(diǎn)地址為一個(gè)錯(cuò)誤的URL。在第一次調(diào)用服務(wù)時(shí),SimpleFailoverInterceptor會(huì)偵測到網(wǎng)絡(luò)故障的發(fā)生,并自動(dòng)切換使用第二個(gè)端點(diǎn)地址繼續(xù)訪問。如清單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配置文件和運(yùn)行結(jié)果
現(xiàn)在我們需要在Spring配置文件中,為所有interceptor添加定義,并描述如何為CurrencyExchangeService構(gòu)建AOP Proxy。需要指出的是,我們要在interceptorName列表中聲明interceptor鏈的調(diào)用順序,還要將原有CurrencyExchangeManager引用的stub對(duì)象替換為新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創(chuàng)建了一個(gè)AOP代理,并且建立了一個(gè)Interceptor鏈。這樣在調(diào)用Web Service時(shí),Spring框架會(huì)依次調(diào)用Interceptor執(zhí)行。實(shí)例執(zhí)行的順序圖將如圖5所示:
圖5系統(tǒng)運(yùn)行順序圖
5.5 Interceptor與JAX-RPC Handler的關(guān)系與區(qū)別
SOAP Message Handler是JAX-RPC為用戶自定義Web Service處理過程提供的一種擴(kuò)展機(jī)制。在處理Web Service請(qǐng)求/響應(yīng)過程中,Web Service 引擎會(huì)根據(jù)部署描述中的定義,按照一定的次序調(diào)用Handler的處理代碼。用戶編寫的Handler實(shí)現(xiàn)可以截獲并修改Web Service消息和處理流程,從而實(shí)現(xiàn)對(duì)Web Service引擎處理行為的定制和增強(qiáng)。
比如,我們可以實(shí)現(xiàn)一個(gè)服務(wù)器端Handler,記錄Web Service在受到請(qǐng)求消息和發(fā)出響應(yīng)消息之間的時(shí)間間隔來實(shí)現(xiàn)對(duì)服務(wù)器端業(yè)務(wù)性能的測定。而且我們只需在部署描述中增加Handler聲明即可,無需修改任何服務(wù)器端代碼。
從此可以看出,JAX-RPC Handler與我們在上文中所提供的AOP Interceptor都可以幫助我們的SOA應(yīng)用程序?qū)崿F(xiàn)關(guān)注分離(Separate Concern)的目標(biāo),在不改變應(yīng)用代碼的同時(shí),增強(qiáng)或改變Web Service服務(wù)訪問的功能。雖然我們可以利用它們實(shí)現(xiàn)一些類似的功能,但它們具有著不同的特點(diǎn)和適用范圍。
JAX-RPC Handler是Web Service引擎的擴(kuò)展機(jī)制。如果我們需要實(shí)現(xiàn)對(duì)SOAP消息進(jìn)行的修改和處理,加入自定義的SOAP Header或?qū)ο?nèi)容進(jìn)行加密,Handler是我們的最佳選擇。而AOP是針對(duì)對(duì)象級(jí)別的擴(kuò)展機(jī)制,它更適合對(duì)應(yīng)用層邏輯進(jìn)行操作。
比如,我們在上文展示的利用AOP實(shí)現(xiàn)的CacheInterceptor,它緩存的是Web Service調(diào)用參數(shù)和結(jié)果。而我們也可以通過JAX-RPC Handler實(shí)現(xiàn)一個(gè)面向SOAP消息的實(shí)現(xiàn),它將緩存Web Service的請(qǐng)求消息和響應(yīng)消息。這兩個(gè)實(shí)現(xiàn)相比,基于AOP的實(shí)現(xiàn)更加簡單、直觀、快速、對(duì)資源消耗也比較小。而面向SOAP消息的實(shí)現(xiàn)則更加靈活,對(duì)于不采用RPC方式的Web Service訪問也能提供支持。
所以在具體的實(shí)踐過程中,開發(fā)人員應(yīng)該根據(jù)具體的需求選擇合適的技術(shù),也可以將這兩種技術(shù)結(jié)合使用。
6.總結(jié)
"分而治之"的方法是人們解決復(fù)雜問題的一種常見做法。而IoC、AOP等技術(shù)都體現(xiàn)了這種思想。通過更好的切分程序邏輯,使得程序結(jié)構(gòu)更加良好,更加富有彈性,易于變化。也使得開發(fā)人員可以更加專注于業(yè)務(wù)邏輯本身,而將一部分其他邏輯交給容器和框架進(jìn)行處理。
在本文中,我們通過一個(gè)Web Service訪問的實(shí)例,具體描述了SOA應(yīng)用中所遇到的一系列具體問題,并描述如何利用IoC和AOP等技術(shù)進(jìn)行代碼重構(gòu),構(gòu)建更加結(jié)構(gòu)良好、靈活的SOA應(yīng)用。綜上所述,我們可以看到:
1使用IoC框架來實(shí)現(xiàn)對(duì)象的生命周期管理、配置管理和依賴管理,可以解除業(yè)務(wù)邏輯對(duì)服務(wù)調(diào)用的依賴關(guān)系;
2 使用AOP方法來解決Web Service調(diào)用中的crosscutting concerns,將為系統(tǒng)增加新的功能而不必更改應(yīng)用程序。
3通過IoC和AOP來屏蔽Web Service訪問的復(fù)雜性,使得開發(fā)人員可以更加專注于業(yè)務(wù)邏輯本身,也使得系統(tǒng)更加穩(wěn)定和富有彈性。
代碼:下載
由于項(xiàng)目需要,需要做一些類似javascript中eval的方法的計(jì)算。
找到beanshell感覺很好用,跟大家分享!
請(qǐng)參考beanshell:
http://www.beanshell.org以下是demo:
/**
? * gf測試宏計(jì)算
? * @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)
就像已經(jīng)討論的那樣,自動(dòng)和透明的 對(duì)象/關(guān)系 映射(object/relational mapping)關(guān)注于管理對(duì)象的狀態(tài)。 這就意味著對(duì)象的狀態(tài)存在于內(nèi)存,因此直接更新或者刪除 (使用 SQL 語句 UPDATE 和 DELETE) 數(shù)據(jù)庫中的數(shù)據(jù)將不會(huì)影響內(nèi)存中的對(duì)象狀態(tài)和對(duì)象數(shù)據(jù)。 不過,Hibernate提供通過Hibernate查詢語言來執(zhí)行大批 量SQL風(fēng)格的(UPDATE)和(DELETE) 語句的方法。
UPDATE 和 DELETE語句的語法為: ( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?。 有幾點(diǎn)說明:
-
在FROM子句(from-clause)中,F(xiàn)ROM關(guān)鍵字是可選的
-
在FROM子句(from-clause)中只能有一個(gè)類名,并且它不能有別名
-
不能在大批量HQL語句中使用連接(顯式或者隱式的都不行)。不過在WHERE子句中可以使用子查詢。
-
整個(gè)WHERE子句是可選的。
舉個(gè)例子,使用Query.executeUpdate()方法執(zhí)行一個(gè)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();
執(zhí)行一個(gè)HQL DELETE,同樣使用 Query.executeUpdate() 方法 (此方法是為 那些熟悉JDBC PreparedStatement.executeUpdate() 的人們而設(shè)定的)
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()方法返回的整型值表明了受此操作影響的記錄數(shù)量。 注意這個(gè)數(shù)值可能與數(shù)據(jù)庫中被(最后一條SQL語句)影響了的“行”數(shù)有關(guān),也可能沒有。一個(gè)大批量HQL操作可能導(dǎo)致多條實(shí)際的SQL語句被執(zhí)行, 舉個(gè)例子,對(duì)joined-subclass映射方式的類進(jìn)行的此類操作。這個(gè)返回值代表了實(shí)際被語句影響了的記錄數(shù)量。在那個(gè)joined-subclass的例子中, 對(duì)一個(gè)子類的刪除實(shí)際上可能不僅僅會(huì)刪除子類映射到的表而且會(huì)影響“根”表,還有可能影響與之有繼承關(guān)系的joined-subclass映射方式的子類的表。
摘要: 第一部分、SQL&PL/SQL[Q]怎么樣查詢特殊字符,如通配符%與_[A]select * from table where name like 'A_%' escape ''[Q]如何插入單引號(hào)到數(shù)據(jù)庫表中[A]可以用ASCII碼處理,其它特殊字符如&也一樣,如insert into t values('i'||chr(39)||'m'); -- chr(39)代表字符'或者用...
閱讀全文
先記下,以后有用得著的時(shí)候也好找。
所見即所得(WYSIWYG?-?What?You?See?Is?What?You?Get)
1、
eWebEditor[Mode?In?China]
簡介:
eWebEditor是eWebSoft.com旗下eWeb團(tuán)隊(duì)開發(fā)的基于網(wǎng)頁的、所見即所得的在線HTML編輯器。她能夠在網(wǎng)頁上實(shí)現(xiàn)許多桌面編輯軟件(如:Word)所具有的強(qiáng)大可視編輯功能;她是一個(gè)真正的綠色軟件,不需要在計(jì)算機(jī)上安裝任何的客戶端軟件;她的易用使得WEB開發(fā)人員只要一行代碼即可完成調(diào)用。
當(dāng)前最新版本:Version?3.8?(ASP版、PHP版、JSP版、ASP.NET版)?
對(duì)于一般站點(diǎn)來說,eWebEditor已經(jīng)很不錯(cuò)了,簡單易上手。
使用方法/接口:
在線演示:
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
這篇文章對(duì)主流的在線WYSIWYG編輯器做了個(gè)詳細(xì)的比較:
http://www.geniisoft.com/showcase.nsf/WebEditors
?????
物化視圖是包括一個(gè)查詢結(jié)果的數(shù)據(jù)庫對(duì)像,它是遠(yuǎn)程數(shù)據(jù)的的本地副本,或者用來生成基于數(shù)據(jù)表求和的匯總表。物化視圖存儲(chǔ)基于遠(yuǎn)程表的數(shù)據(jù),也可以稱為快照。
??????
物化視圖可以查詢表,視圖和其它的物化視圖。
??????
通常情況下,物化視圖被稱為主表(在復(fù)制期間)或明細(xì)表(在數(shù)據(jù)倉庫中)。
??????
對(duì)于復(fù)制,物化視圖允許你在本地維護(hù)遠(yuǎn)程數(shù)據(jù)的副本
,
這些副本是只讀的。如果你想修改本地副本,必須用高級(jí)復(fù)制的功能。當(dāng)你想從一個(gè)表或視圖中抽取數(shù)據(jù)時(shí),你可以用從物化視圖中抽取。
??????
對(duì)于數(shù)據(jù)倉庫,創(chuàng)建的物化視圖通常情況下是聚合視圖,單一表聚合視圖和連接視圖。
本文我們將會(huì)看到怎樣創(chuàng)建物化視圖并且討論它的刷新選項(xiàng)。
??????
在復(fù)制環(huán)境下,創(chuàng)建的物化視圖通常情況下主鍵,
rowid,
和子查詢視圖。
1.
主鍵物化視圖:
??????
??????
下面的語法在遠(yuǎn)程數(shù)據(jù)庫表
emp
上創(chuàng)建主鍵物化視圖
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.
???
注意:當(dāng)用
FAST
選項(xiàng)創(chuàng)建物化視圖,必須創(chuàng)建基于主表的視圖日志
,
如下
:
??????SQL> CREATE MATERIALIZED VIEW LOG ON emp;Materialized view log created.
2.
Rowid
物化視圖
??????
??????
下面的語法在遠(yuǎn)程數(shù)據(jù)庫表
emp
上創(chuàng)建
Rowid
物化視圖
SQL> CREATE MATERIALIZED VIEW mv_emp_rowid
REFRESH WITH ROWID
???? AS SELECT * FROM emp@remote_db;
Materialized view log created.
3.
子查詢物化視圖
??????
??????
下面的語法在遠(yuǎn)程數(shù)據(jù)庫表
emp
上創(chuàng)建基于
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
選項(xiàng)說明
:
a.
?????
oracle
用刷新方法在物化視圖中刷新數(shù)據(jù)
.
b.
?????
是基于主鍵還是基于
rowid
的物化視圖
c.
??????
物化視圖的刷新時(shí)間和間隔刷新時(shí)間
?
Refresh
方法
-FAST
子句
??????
增量刷新用物化視圖日志(參照上面所述)來發(fā)送主表已經(jīng)修改的數(shù)據(jù)行到物化視圖中
.
如果指定
REFRESH FAST
子句,那么應(yīng)該對(duì)主表創(chuàng)建物化視圖日志
SQL> CREATE MATERIALIZED VIEW LOG ON emp;
Materialized view log created.
??????
對(duì)于增量刷新選項(xiàng),如果在子查詢中存在分析函數(shù),則物化視圖不起作用。
?
Refresh
方法
-COMPLETE
子句
??????
完全刷新重新生成整個(gè)視圖,如果請(qǐng)求完全刷新,
oracle
會(huì)完成
??????
完全刷新即使增量刷新可用。
?
Refresh Method – FORCE
子句
??????
當(dāng)指定
FORCE
子句,如果增量刷新可用
Oracle
將完成增量刷新,否則將完成完全刷新
,
如果不指定刷新方法
(FAST, COMPLETE, or FORCE),Force
選項(xiàng)是默認(rèn)選項(xiàng)
?
主鍵和
ROWD
子句
?????? WITH PRIMARY KEY
選項(xiàng)生成主鍵物化視圖
,
也就是說物化視圖是基于主表的主鍵,而不是
ROWID(
對(duì)應(yīng)于
ROWID
子句
). PRIMARY KEY
是默認(rèn)選項(xiàng)
,
為了生成
PRIMARY KEY
子句,應(yīng)該在主表上定義主鍵,否則應(yīng)該用基于
ROWID
的物化視圖
.
??????
主鍵物化視圖允許識(shí)別物化視圖主表而不影響物化視圖增量刷新的可用性。
??????
Rowid
物化視圖只有一個(gè)單一的主表,不能包括下面任何一項(xiàng)
:
n
????????
Distinct
或者聚合函數(shù)
.
n
????????
Group by
,子查詢,連接和
SET
操作
?
刷新時(shí)間
??????
??????
START WITH
子句通知數(shù)據(jù)庫完成從主表到本地表第一次復(fù)制的時(shí)間
,
應(yīng)該及時(shí)估計(jì)下一次運(yùn)行的時(shí)間點(diǎn)
, NEXT
子句說明了刷新的間隔時(shí)間
.
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.
在上面的例子中,物化視圖數(shù)據(jù)的第一個(gè)副本在創(chuàng)建時(shí)生成,以后每兩天刷新一次
.
?
總結(jié)
物化視圖提供了可伸縮的基于主鍵或
ROWID
的視圖
,
指定了刷新方法和自動(dòng)刷新的時(shí)間。