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