發(fā)布于:2021-01-25 09:50:26
0
236
0
我們面臨以下問題:雖然基于SOAP的web服務(wù)方法目前不受歡迎,但基于SOAP的web服務(wù)肯定不是不存在的,而且用于為SOAP服務(wù)構(gòu)建簡(jiǎn)單客戶機(jī)的工具是內(nèi)置在Java中的。創(chuàng)建一個(gè)簡(jiǎn)單的客戶機(jī)幾乎是一項(xiàng)微不足道的工作。您所需要的只是wsimport工具(JDK安裝的一部分)和對(duì)Web服務(wù)描述語(yǔ)言(WSDL)文件的訪問。wsimport腳本讀取WSDL文件,并生成構(gòu)建WSDL所需的所有存根客戶。自從Groovy和Java可以自由混合,在Groovy中構(gòu)建一個(gè)使用生成的Java存根的客戶機(jī)非常容易。
這里將使用一個(gè)Spock測(cè)試用例來檢查web服務(wù)的行為。將訪問用于計(jì)算貨幣匯率的免費(fèi)Microsoft web服務(wù)。希望使用microsoftweb服務(wù)不會(huì)讓這篇文章的讀者望而卻步,因?yàn)橐坏┦褂昧薙OAP這個(gè)術(shù)語(yǔ),他們就不會(huì)離開。服務(wù)不重要-最好是Gradle的東西。目標(biāo)是使用Gradle自動(dòng)化從存根生成到測(cè)試的整個(gè)過程。
Microsoft支持許多簡(jiǎn)單的web服務(wù)。其中一個(gè)服務(wù)是貨幣轉(zhuǎn)換器(他們甚至把它錯(cuò)拼成“convertor”)。不要讓我開始。),其WSDL文件位于此處。按照約定,服務(wù)位于同一個(gè)URL,同時(shí)刪除了WSDL參數(shù)。因?yàn)檫@里肯定不是討論WSDL變幻莫測(cè)的地方,所以只需注意WSDL文件的源中服務(wù)名是CurrencyConvertor,端口類型(即接口)是CurrencyConvertorSoap。這意味著一旦生成存根,訪問web服務(wù)就如同編寫:
CurrencyConvertorSoap stub = new CurrencyConvertor().getCurrencyConvertorSoap()
然后只需使用存根來調(diào)用WSDL文件中定義的任何操作。唯一需要的操作是getConversionRate,它接受在WSDL文件內(nèi)的XML模式中定義的兩個(gè)貨幣實(shí)例。例如,典型的請(qǐng)求如下所示:
double rate = stub.getConversionRate(Currency.USD, Currency.INR)
計(jì)算美元和印度盧比之間的匯率。soapweb服務(wù)的關(guān)鍵好處(如果有的話)是存根生成。Java附帶了wsimport工具,其用法如下:
c:> wsimport -d buildDir -s srcDir -keep http://...path.to.WSDL.file...
其中-d標(biāo)志指定用于編譯存根的目錄,-s標(biāo)志表示將生成的源代碼放在何處,-keep標(biāo)志表示保存生成的源代碼,最后一個(gè)參數(shù)是WSDL文件的位置。
這很容易從命令行運(yùn)行,但是如何使它成為自動(dòng)構(gòu)建的一部分呢?幸運(yùn)的是,為它定義了一個(gè)Ant任務(wù)?,F(xiàn)在的任務(wù)是(1)添加適當(dāng)?shù)拇鎯?chǔ)庫(kù),以便Gradle可以為Ant任務(wù)找到所需的jar,(2)為wsimport定義一個(gè)自定義Gradle任務(wù),以及(3)使任務(wù)的執(zhí)行成為常規(guī)構(gòu)建過程的一部分。本文的其余部分將展示如何執(zhí)行這些任務(wù)。
因?yàn)檫@最終將是Groovy/Java組合項(xiàng)目的一部分,所以從構(gòu)建.gradle文件包含:
apply plugin: 'groovy'
repositories {
mavenCentral()
}
dependencies {
groovy 'org.codehaus.groovy:groovy-all:1.8.4'
}
此文件將隨著附加任務(wù)和依賴項(xiàng)的添加而增長(zhǎng)。定義web服務(wù)Ant任務(wù)(如wsimport和wsgen)的jar文件是JAX-WS工具項(xiàng)目的一部分。好消息是,JAX-WS模塊位于Maven Central,Gradle與之無(wú)縫集成。Maven Central中的模塊至少由三個(gè)組件組成的“向量”定義:組ID、工件ID和版本。JAX-WS工具模塊的組id是com.sun.xml.ws,工件id為jaxws tools,版本號(hào)為2.1.4。我們想告訴Gradle下載這個(gè)jar文件和它所依賴的其他jar,我們想把這些jar保存在一個(gè)名為配置的命名容器中。要實(shí)現(xiàn)這一點(diǎn),請(qǐng)?zhí)砑訕?gòu)建.gradle文件:
configurations {
jaxws
}
dependencies {
... from before ...
jaxws 'com.sun.xml.ws:jaxws-tools:2.1.4'
}
但是,這個(gè)庫(kù)不存儲(chǔ)在Maven中央存儲(chǔ)庫(kù)中,因此必須添加其他存儲(chǔ)庫(kù)。從Gradle 1.0 milestone 6開始,這樣做的語(yǔ)法是:
repositories {
mavenCentral()
maven { url 'http://download.java.net/maven/1' }
maven { url 'http://download.java.net/maven/2' }
}
有趣的部分來了!清單1顯示了向構(gòu)建中添加wsimport任務(wù)的初始嘗試。
task wsimport {
doLast {
destDir = file("${buildDir}/generated")
ant {
sourceSets.main.output.classesDir.mkdirs()
destDir.mkdirs()
taskdef(name:'wsimport',
classname:'com.sun.tools.ws.ant.WsImport',
classpath:configurations.jaxws.asPath)
wsimport(keep:true,
destdir: sourceSets.main.output.classesDir,
sourcedestdir: destDir,
wsdl:'http://www.webservicex.net/CurrencyConvertor.asmx?
wsdl')
}
}
}
compileJava.dependsOn(wsimport)
此塊定義Gradle中的自定義任務(wù)。對(duì)doLast方法的調(diào)用定義了wsimport方法運(yùn)行時(shí)要執(zhí)行的步驟。這是Gradle中命令式任務(wù)定義的一個(gè)示例。當(dāng)任務(wù)運(yùn)行時(shí),它會(huì)生成Java存根,然后需要對(duì)其進(jìn)行編譯。為了確保此任務(wù)在內(nèi)置compileJava任務(wù)之前運(yùn)行,在定義wsimport之后,compileJava被聲明為依賴于wsimport。
這也帶來了一點(diǎn)復(fù)雜性,因?yàn)榫幾g代碼的輸出目錄是在編譯任務(wù)運(yùn)行之前創(chuàng)建的。這就是為什么在生成存根之前,必須在目標(biāo)目錄上運(yùn)行mkdirs()。里程碑6中的語(yǔ)法也發(fā)生了變化。它現(xiàn)在需要“main”和“classesDir”之間的“output”屬性。
下一行在生成的Java源文件的build目錄下定義一個(gè)“generated”目錄,之后的一行創(chuàng)建這個(gè)目錄。
現(xiàn)在已經(jīng)定義了wsimport任務(wù)所需的所有屬性,是時(shí)候調(diào)用實(shí)際的WSDL生成代碼了。在doLast閉包中,“ant”是指每個(gè)Gradle構(gòu)建文件中的AntBuilder實(shí)例。在ant閉包中,taskdef和wsimport任務(wù)來自它們的ant對(duì)應(yīng)任務(wù)。唯一的微妙之處是任務(wù)的類路徑配置,它引用了前面定義的jaxws配置。將Gradle的可傳遞依賴關(guān)系管理與Ant任務(wù)定義結(jié)合使用,對(duì)于將正確的依賴關(guān)系引入到項(xiàng)目中以定義自定義Ant任務(wù)這一不方便的過程來說,是一個(gè)顯著的改進(jìn)。
到目前為止,這個(gè)過程運(yùn)行良好,但效率低下。按照配置,wsimport任務(wù)在每個(gè)構(gòu)建上運(yùn)行,如果web服務(wù)沒有更改,那么這當(dāng)然不是必需的。(實(shí)際的web服務(wù)已經(jīng)好幾年沒變了?。┯袔追N方法可以防止每次重新運(yùn)行任務(wù)。一種是利用Gradle任務(wù)的onlyIf屬性,如下所示:
wsimport.onlyIf { !(new File(buildDir, 'generated').exists()) }
現(xiàn)在,僅當(dāng)生成的源目錄不存在時(shí),任務(wù)才會(huì)運(yùn)行。通過將生成的目錄放在build目錄下,clean任務(wù)將消除它,wsimport任務(wù)將在下一個(gè)build期間運(yùn)行。
這一切都很好,并且提醒我們構(gòu)建文件仍然是Groovy文件,因此可以向其中添加任意Groovy表達(dá)式,但是還有更好的替代方法。每個(gè)Gradle任務(wù)都有稱為“inputs”和“outputs”的屬性,這些屬性使用增量編譯引擎。這兩個(gè)字段的目的是確定相對(duì)于任務(wù)作為輸入讀取和作為輸出寫入的文件,任務(wù)是否是最新的。這里唯一的問題是輸入和輸出的參數(shù)必須基于文件,而且WSDL文件位于URL。
沒有完美的方法來解決這個(gè)問題。構(gòu)建總是可以在web上查找WSDL,這意味著在發(fā)布WSDL時(shí),它總是獲得WSDL的更新版本。然而,Gradle的增量編譯引擎只能處理本地文件,而不能處理網(wǎng)絡(luò)資源;此外,這種方法將強(qiáng)制構(gòu)建的用戶始終具有網(wǎng)絡(luò)連接。更好的方法是通過將WSDL文件保存為項(xiàng)目中的文件,在本地緩存它。將另一個(gè)任務(wù)添加到構(gòu)建中以從其規(guī)范URI下載WSDL文件并將其緩存在項(xiàng)目目錄中是很簡(jiǎn)單的。為簡(jiǎn)潔起見,此處省略此步驟。清單2中以粗體顯示了結(jié)果更改。
task wsimport {
destDir = file("${buildDir}/generated")
wsdlSrc = file('currency_convertor.wsdl')
inputs.file wsdlSrc
outputs.dir destDir
doLast{
ant {
sourceSets.main.output.classesDir.mkdirs()
destDir.mkdirs()
taskdef(name:'wsimport',
classname:'com.sun.tools.ws.ant.WsImport',
classpath:configurations.jaxws.asPath)
wsimport(keep:true,
destdir: sourceSets.main.output.classesDir,
sourcedestdir: destDir,
wsdl: wsdlSrc)
}
}
}
WSDL文件存儲(chǔ)在項(xiàng)目的根目錄中。inputs屬性使用file方法連接到WSDL文件,outputs屬性使用dir方法連接到目標(biāo)目錄。如果輸入文件或輸出目錄中的任何文件發(fā)生更改,任務(wù)將再次執(zhí)行。如果在構(gòu)建的調(diào)用之間輸入和輸出都沒有改變,那么任務(wù)將不會(huì)執(zhí)行。
目前的構(gòu)建還不錯(cuò),但Gradle還有一個(gè)未被公開的特性值得說明。Gradle假設(shè)項(xiàng)目布局符合標(biāo)準(zhǔn)布局,包括src/main/groovy、src/main/java、src/test/groovy等子目錄。如果您不想使用這種結(jié)構(gòu),那么它非常容易更改。
考慮一個(gè)具有兩個(gè)源文件夾的備用項(xiàng)目布局,一個(gè)稱為src,另一個(gè)稱為tests。很容易將此結(jié)構(gòu)映射到Gradle域模型:
sourceSets {
main {
java { srcDir "$buildDir/generated" }
groovy { srcDir 'src' }
}
test {
java { srcDirs = [] }
groovy { srcDir 'tests' }
}
}
sourceSets閉包將標(biāo)準(zhǔn)源目錄結(jié)構(gòu)映射到項(xiàng)目所需的任何內(nèi)容。這里的布局表示Java文件只有一個(gè)源目錄,即wsimport任務(wù)填充的生成目錄?!皊rc”中的所有內(nèi)容,無(wú)論是用Java還是Groovy編寫的,都是由groovyc編譯的。測(cè)試閉包更為明確——javac沒有可使用的目錄?!皌ests”目錄下的所有內(nèi)容都由groovyc編譯。這實(shí)際上是一個(gè)很好的整合原則。groovyc編譯器完全了解Java源代碼,所以讓它同時(shí)編譯Java和Groovy源代碼。這樣就可以為您解決任何潛在的交叉編譯問題。到目前為止,本文中的所有代碼都來自Gradle構(gòu)建文件。為了完整起見,清單3顯示了一個(gè)定義轉(zhuǎn)換率服務(wù)的類。
import net.webservicex.Currency;
import net.webservicex.CurrencyConvertor
import net.webservicex.CurrencyConvertorSoap
class ConversionRate {
CurrencyConvertorSoap stub =
new CurrencyConvertor().getCurrencyConvertorSoap()
double getConversionRate(Currency from, Currency to) {
return from == to ? 1.0 : stub.conversionRate(from, to)
}
}
下面的清單4顯示了一個(gè)簡(jiǎn)單的Spock測(cè)試來檢查實(shí)現(xiàn)。
import net.webservicex.Currency;
import spock.lang.Specification;
class ConversionRateSpec extends Specification {
ConversionRate cr = new ConversionRate()
def "same currency should be rate of 1"() {
when:
double rate = cr.getConversionRate(Currency.USD, Currency.USD)
then:
rate == 1.0
}
def "rate from USD to INR is > 1"() {
expect:
cr.getConversionRate(Currency.USD, Currency.INR) >= 1
}
}
即使你以前從未見過斯波克測(cè)試,這應(yīng)該是相當(dāng)直觀的。該類從Spock擴(kuò)展了Specification類,使其成為Spock測(cè)試類。每個(gè)測(cè)試都有一個(gè)def返回類型,后跟一個(gè)解釋其目標(biāo)的字符串和空括號(hào)。第一個(gè)測(cè)試使用when/then配對(duì)作為刺激/反應(yīng)?!皌hen”塊包含自動(dòng)計(jì)算的布爾條件,因此不需要基于斷言的關(guān)鍵字。
由于實(shí)際匯率一直在變化,第二個(gè)測(cè)試選擇兩種保證滿足條件的貨幣。在撰寫本文時(shí),1美元約合51盧比。布爾測(cè)試位于expect塊中,其工作方式與前一個(gè)測(cè)試中的then塊相同。要使測(cè)試正常工作,需要對(duì)構(gòu)建文件進(jìn)行最后一次更改。將以下行添加到dependencies塊。
testCompile 'org.spockframework:spock-core:0.5-groovy-1.8'
這將下載Spock的正確版本及其依賴項(xiàng)(如JUnit),現(xiàn)在構(gòu)建也將執(zhí)行測(cè)試。在撰寫本文時(shí),版本0.5是最新版本??梢試L試將版本號(hào)更新為運(yùn)行示例時(shí)的當(dāng)前版本號(hào)。
雖然激發(fā)本文的項(xiàng)目涉及Microsoft web服務(wù)上的一個(gè)簡(jiǎn)單Groovy/Java客戶機(jī),但真正的目標(biāo)是說明Gradle開發(fā)的幾個(gè)方面。其中包括創(chuàng)建自定義任務(wù)、使用基于外部Ant jar的配置、使用多個(gè)存儲(chǔ)庫(kù)、定義和配置Ant任務(wù)、將其插入正常的構(gòu)建過程、確保任務(wù)僅在必要時(shí)運(yùn)行,以及顯示如何將替代項(xiàng)目布局映射到Gradle所期望的內(nèi)容。希望這些任務(wù)中的一部分或全部對(duì)您將來有所幫助。
作者介紹
熱門博客推薦