教程-Gradle SOAP-功能展示

gradle gradle soap groovy java 教程
2021-01-25 09:50:26
106 1 0

我们面临以下问题:虽然基于SOAP的web服务方法目前不受欢迎,但基于SOAP的web服务肯定不是不存在的,而且用于为SOAP服务构建简单客户机的工具是内置在Java中的。创建一个简单的客户机几乎是一项微不足道的工作。您所需要的只是wsimport工具(JDK安装的一部分)和对Web服务描述语言(WSDL)文件的访问。wsimport脚本读取WSDL文件,并生成构建WSDL所需的所有存根客户。自从Groovy和Java可以自由混合,在Groovy中构建一个使用生成的Java存根的客户机非常容易。

这里将使用一个Spock测试用例来检查web服务的行为。将访问用于计算货币汇率的免费Microsoft web服务。希望使用microsoftweb服务不会让这篇文章的读者望而却步,因为一旦使用了SOAP这个术语,他们就不会离开。服务不重要-最好是Gradle的东西。目标是使用Gradle自动化从存根生成到测试的整个过程。

Web服务客户端

Microsoft支持许多简单的web服务。其中一个服务是货币转换器(他们甚至把它错拼成“convertor”)。不要让我开始。),其WSDL文件位于此处。按照约定,服务位于同一个URL,同时删除了WSDL参数。因为这里肯定不是讨论WSDL变幻莫测的地方,所以只需注意WSDL文件的源中服务名是CurrencyConvertor,端口类型(即接口)是CurrencyConvertorSoap。这意味着一旦生成存根,访问web服务就如同编写:

CurrencyConvertorSoap stub = new CurrencyConvertor().getCurrencyConvertorSoap()

然后只需使用存根来调用WSDL文件中定义的任何操作。唯一需要的操作是getConversionRate,它接受在WSDL文件内的XML模式中定义的两个货币实例。例如,典型的请求如下所示:

double rate = stub.getConversionRate(Currency.USD, Currency.INR)

计算美元和印度卢比之间的汇率。soapweb服务的关键好处(如果有的话)是存根生成。Java附带了wsimport工具,其用法如下:

c:> wsimport -d buildDir -s srcDir -keep http://...path.to.WSDL.file...

其中-d标志指定用于编译存根的目录,-s标志表示将生成的源代码放在何处,-keep标志表示保存生成的源代码,最后一个参数是WSDL文件的位置。

这很容易从命令行运行,但是如何使它成为自动构建的一部分呢?幸运的是,为它定义了一个Ant任务。现在的任务是(1)添加适当的存储库,以便Gradle可以为Ant任务找到所需的jar,(2)为wsimport定义一个自定义Gradle任务,以及(3)使任务的执行成为常规构建过程的一部分。本文的其余部分将展示如何执行这些任务。

创建生成文件

因为这最终将是Groovy/Java组合项目的一部分,所以从构建.gradle文件包含:

apply plugin: 'groovy'

repositories {
   mavenCentral()
}

dependencies {
   groovy 'org.codehaus.groovy:groovy-all:1.8.4'
}

此文件将随着附加任务和依赖项的添加而增长。定义web服务Ant任务(如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保存在一个名为配置的命名容器中。要实现这一点,请添加构建.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显示了向构建中添加wsimport任务的初始尝试。

清单1

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中的自定义任务。对doLast方法的调用定义了wsimport方法运行时要执行的步骤。这是Gradle中命令式任务定义的一个示例。当任务运行时,它会生成Java存根,然后需要对其进行编译。为了确保此任务在内置compileJava任务之前运行,在定义wsimport之后,compileJava被声明为依赖于wsimport。

这也带来了一点复杂性,因为编译代码的输出目录是在编译任务运行之前创建的。这就是为什么在生成存根之前,必须在目标目录上运行mkdirs()。里程碑6中的语法也发生了变化。它现在需要“main”和“classesDir”之间的“output”属性。

下一行在生成的Java源文件的build目录下定义一个“generated”目录,之后的一行创建这个目录。

现在已经定义了wsimport任务所需的所有属性,是时候调用实际的WSDL生成代码了。在doLast闭包中,“ant”是指每个Gradle构建文件中的AntBuilder实例。在ant闭包中,taskdef和wsimport任务来自它们的ant对应任务。唯一的微妙之处是任务的类路径配置,它引用了前面定义的jaxws配置。将Gradle的可传递依赖关系管理与Ant任务定义结合使用,对于将正确的依赖关系引入到项目中以定义自定义Ant任务这一不方便的过程来说,是一个显著的改进。

到目前为止,这个过程运行良好,但效率低下。按照配置,wsimport任务在每个构建上运行,如果web服务没有更改,那么这当然不是必需的。(实际的web服务已经好几年没变了!)有几种方法可以防止每次重新运行任务。一种是利用Gradle任务的onlyIf属性,如下所示:

wsimport.onlyIf { !(new File(buildDir, 'generated').exists()) }

现在,仅当生成的源目录不存在时,任务才会运行。通过将生成的目录放在build目录下,clean任务将消除它,wsimport任务将在下一个build期间运行。

这一切都很好,并且提醒我们构建文件仍然是Groovy文件,因此可以向其中添加任意Groovy表达式,但是还有更好的替代方法。每个Gradle任务都有称为“inputs”和“outputs”的属性,这些属性使用增量编译引擎。这两个字段的目的是确定相对于任务作为输入读取和作为输出写入的文件,任务是否是最新的。这里唯一的问题是输入和输出的参数必须基于文件,而且WSDL文件位于URL。

没有完美的方法来解决这个问题。构建总是可以在web上查找WSDL,这意味着在发布WSDL时,它总是获得WSDL的更新版本。然而,Gradle的增量编译引擎只能处理本地文件,而不能处理网络资源;此外,这种方法将强制构建的用户始终具有网络连接。更好的方法是通过将WSDL文件保存为项目中的文件,在本地缓存它。将另一个任务添加到构建中以从其规范URI下载WSDL文件并将其缓存在项目目录中是很简单的。为简洁起见,此处省略此步骤。清单2中以粗体显示了结果更改。

清单2

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方法连接到目标目录。如果输入文件或输出目录中的任何文件发生更改,任务将再次执行。如果在构建的调用之间输入和输出都没有改变,那么任务将不会执行。

目前的构建还不错,但Gradle还有一个未被公开的特性值得说明。Gradle假设项目布局符合标准布局,包括src/main/groovy、src/main/java、src/test/groovy等子目录。如果您不想使用这种结构,那么它非常容易更改。

考虑一个具有两个源文件夹的备用项目布局,一个称为src,另一个称为tests。很容易将此结构映射到Gradle域模型:

sourceSets {
   main {
       java { srcDir "$buildDir/generated" }
       groovy { srcDir 'src' }
   }
   test {
       java { srcDirs = [] }
       groovy { srcDir 'tests' }
   }
}

sourceSets闭包将标准源目录结构映射到项目所需的任何内容。这里的布局表示Java文件只有一个源目录,即wsimport任务填充的生成目录。“src”中的所有内容,无论是用Java还是Groovy编写的,都是由groovyc编译的。测试闭包更为明确——javac没有可使用的目录。“tests”目录下的所有内容都由groovyc编译。这实际上是一个很好的整合原则。groovyc编译器完全了解Java源代码,所以让它同时编译Java和Groovy源代码。这样就可以为您解决任何潜在的交叉编译问题。到目前为止,本文中的所有代码都来自Gradle构建文件。为了完整起见,清单3显示了一个定义转换率服务的类。

清单3

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测试来检查实现。

清单4

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
   }
}

即使你以前从未见过斯波克测试,这应该是相当直观的。该类从Spock扩展了Specification类,使其成为Spock测试类。每个测试都有一个def返回类型,后跟一个解释其目标的字符串和空括号。第一个测试使用when/then配对作为刺激/反应。“then”块包含自动计算的布尔条件,因此不需要基于断言的关键字。

由于实际汇率一直在变化,第二个测试选择两种保证满足条件的货币。在撰写本文时,1美元约合51卢比。布尔测试位于expect块中,其工作方式与前一个测试中的then块相同。要使测试正常工作,需要对构建文件进行最后一次更改。将以下行添加到dependencies块。

testCompile 'org.spockframework:spock-core:0.5-groovy-1.8'

这将下载Spock的正确版本及其依赖项(如JUnit),现在构建也将执行测试。在撰写本文时,版本0.5是最新版本。可以尝试将版本号更新为运行示例时的当前版本号。

摘要

虽然激发本文的项目涉及Microsoft web服务上的一个简单Groovy/Java客户机,但真正的目标是说明Gradle开发的几个方面。其中包括创建自定义任务、使用基于外部Ant jar的配置、使用多个存储库、定义和配置Ant任务、将其插入正常的构建过程、确保任务仅在必要时运行,以及显示如何将替代项目布局映射到Gradle所期望的内容。希望这些任务中的一部分或全部对您将来有所帮助。

作者介绍

用微信扫一扫

收藏