使用用于SBT和Scala的WSDL导入自动化SOAP客户端自动生成例程

使用SOAP通常会很棘手,而处理WSDL可能会大大增加此任务的复杂性。 确实,当您使用像Scala这样的现代语言时,这可能是最不希望面对的事情,它以其反应性和异步处理请求而闻名。 实际上,许多最近才进入行业的软件开发人员,甚至可能都不了解SOAP和WSDL协议,并且在首次尝试连接到这种传统服务时会很快感到烦恼甚至恼怒。 因此,我们是否应该完全不赞成使用此方法而采用现代技术堆栈,还是有一个更轻松的解决方案?


SOAP:旧版


很难说这几天SOAP听起来已经过时了,特别是与当前的技术状态相比。 使用Kotlin,Scala或其他现代语言从头开始编写WSDL客户端可能会很痛苦,并且缺少适当的文档资料并不能使工作变得更轻松。 但是我对您有个好消息,黑暗的SOAP王国里有很多亮点。 好吧,实际上WSDL本身就是其中之一。 尽管它很笨重,而且有些丑陋,但它具有一定的优势。 WSDL格式的过多性使得生成客户端(以及服务器)代码非常容易,这可能不是人类的,而是绝对是自动化系统的。


即使与现代API规范相比,它实际上也可以与OpenAPI或花哨的Swagger API概念保持一致,后者在与语言无关的规范中描述了所有内容。 这就为实现不同平台和语言之间的互操作性提供了巨大的机会。 例如,如果一个人公开了具有WSDL规范的.NET Web服务,则另一个人可以自动生成基于JVM的客户端以连接到它,而几乎不需要痛苦地转换数据格式或不兼容。


WSDL导入魔术


让我们进一步讨论自动代码生成。 您可能会感到惊讶,但是大多数企业级平台(主要是Java和.NET)都提供了WSDL代码生成工具。 例如,在JDK发行版中有wsimport 。 此类工具功能强大,应端到端涵盖自动生成任务。 剩下的唯一部分是将您的业务逻辑连接到客户端代码并加以利用。


因此,由于当前处于Scala主题,让我们更深入地研究Java的wsimport工具:


 wsimport -p stockquote http://stockquote.example.com/quote?wsdl 

该命令将WSDL模式作为必需参数,并且基本上就足以产生一整套POJO和接口,并用所有适当的注释进行标记。 后者实际上可以解决问题:从本质上讲,这使所有事情成为可能。 在执行时,JVM将您的客户端代码与内部Web服务客户端实现结合在一起,这是开箱即用的,因此您无需为低级网络和IO操心。 剩下的事情就是正确处理来龙去脉,并注意错误和异常。


借助SBT将自动化提升到一个新的水平


好了,是时候动手了。 想象一下,我们也需要连接一些SOAP Web服务,并且它们公开了WSDL。 我当然是出于科学和教育目的,故意带了一些测试。 运行代码生成器:


 wsimport -s ../src/main/java -extension -p your.package.wsdl.nl \ -XadditionalHeaders -Xnocompile \ http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL 

它在输出文件夹中生成许多原始Java代码。 如上所述,我们可以继续连接业务逻辑。 但是请稍等一下,如果服务器端发生了变化,我们将只在实际代码执行时(或在集成测试失败的时刻,如果有的话)才意识到这一点。 不漂亮 如果您考虑将所有这些样板Java Bean代码提交到原始的Scala存储库中,它很快就会变得很不漂亮。


当然,最好自动生成所有内容并保持内容简洁明了。 第一步是使用一个命令自动获取所有WSDL类,并从中制作Shell脚本。 我实际上为您做了一个,所以您可以看一下: wsdl_import.sh


然后,我们可以将其与构建任务一起包装:让我们以SBT为例,因为我们在Scala上,所以这样的事情应该起作用:


 lazy val wsdlImport = TaskKey[Unit]("wsdlImport", "Generates Java classes from WSDL") wsdlImport := { val wsdlSources = "./wsdl/src/main/java" val d = file(wsdlSources) if (d.isDirectory) { // don't forget to rename to your fav one in line with WSDL generating sh val gen = file(s"$wsdlSources/github/sainnr/wsdl") if (!gen.exists() || gen.listFiles().isEmpty) { import sys.process._ println("[wsdl_import] Importing Java beans from WSDL...") "./wsdl/bin/wsdl_import.sh" ! } else println("[wsdl_import] Looks like WSDL is already imported, skipping.") } else println(s"[wsdl_import] Make sure the directory ${d.absolutePath} exists.") } 

来源


现在,出于显而易见的原因,我们需要确保在Scala部件编译之前拥有所有这些代码。 轻而易举,我们有SBT,所以我们只需要像上面的SBT任务一样执行Shell脚本,并以正确的顺序运行事情,对吗? 好吧,在现实生活中它有点复杂。 如果不深入研究SBT的工作原理的细节,那么如果我们将WSDL-Java部分分成一个独立的子项目,并在主SBT配置中建立适当的依赖关系,事情就会变得容易得多。


 lazy val wsdl = (project in file("wsdl")) .settings ( publishSettings, sources in (Compile, doc) := Seq.empty ) lazy val root = (project in file(".")) .aggregate(wsdl) .dependsOn(wsdl) 

来源


编译主项目时,SBT首先确保子项目已被编译。 但是有一个陷阱:刚签出存储库时,可能未执行编译。 因此,当您第一次在编辑器中打开它时,当然会缺少某些依赖项。 希望,您唯一需要做的就是运行sbt compilecommand并可能在IDE中刷新项目。


如果您将Scala应用程序作为独立客户端或在精益Web容器中运行(例如,如果使用的是Play Framework,则为Netty),则可能需要另外警告。 在这种情况下,由于现代的JRE版本和Java Jigsaw项目,应用程序运行时很可能会缺少帮助JVM为您完成SOAP魔术的实现位。 不过,无需担心,只需将一些库添加到依赖项列表中,或从JRE分发中将单个rt.jar作为非托管依赖项抛出即可:


  unmanagedJars in Test += Attributed.blank( file(System.getenv("JAVA_HOME") + "/jre/lib") ) 

结论


好的,作为回顾:我们已经对SOAP和WSDL有所了解,并希望意识到,由于所有这些代码生成器和过多的WSDL规范,使用它并不是一场噩梦。 我们还想出了如何自动执行肮脏的工作,并找到了一种方法,可以使我们的存储库保持原始状态,并清除不需要的样板代码。 正确配置编译顺序和依赖项需要一些SBT知识,但是毕竟,它应该工作得相当顺利。 为了进一步简化操作,我制作了一个小的引导程序模板,该模板可以帮助您下​​次启动项目: https : //github.com/sainnr/sbt-scala-wsdl-template 。 希望您喜欢这个小小的过去之旅!


参考文献



如果您发现任何错别字或错误,请给我留言。


这篇文章最初发表在我的博客fullstackme.co.uk中 ,几乎没有修改。

Source: https://habr.com/ru/post/zh-CN470349/


All Articles