如今Restful Web Service很流行,介绍如何写Restful Web Service的文章也很多,自己也很喜欢Restful Web Service,用JSON传输,效率比XML好很多。但是,Restful Web Service不像SOAP Web Service可以用WSDL来作为Service的定义,在设计时需要写很多文档,来对服务接口进行说明。同时,在开发客户端的时候,并不能直接生成所需要的Bean,需要用户自己手工写代码,或者由服务端提供,这就给日后的管理和维护带来了很多潜在的问题。最近正好要用Java访问一个SOAP Web Service,自己搞了一遍后,发现其实SOAP Web Service也是挺好用的。如果不是传输很大Payload用XML效率太低的服务,完全可以不使用Restful Web Service。在这里我把自己写的访问web service的代码整理了一下,希望可以对有同样需要的朋友有些帮助。
在这个代码的例子中,我访问的是一个开放的天气SOAP服务,这个服务的具体信息我是在http://www.service-repository.com/service/overview/1132083200找到的。在这个例子中,我们只访问GetWeatherInformation这个服务。
首先我们先在IntelliJ里建立一个新的Java Project,在这个例子中我使用了Gradle作为build工具,所以需要添加build.gradle文件。接着我们从http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL下载这个SOAP服务的WSDL定义,并添加到resources里。下面,我们要配置Gradle来使用JAXB根据WSDL生成访问服务所需要的Bean:
task jaxb() {
jaxbTargetDir = file( "src/generated-sources/java" )
doLast {
jaxbTargetDir.mkdirs()
ant.taskdef(
name: 'xjc',
classname: 'com.sun.tools.xjc.XJCTask',
classpath: configurations.jaxb.asPath
)
ant.jaxbTargetDir = jaxbTargetDir
ant.xjc(
destdir: '${jaxbTargetDir}',
package: 'weblog.soap.client.example.model',
schema: 'src/main/resources/wsdl.xml'
) {
arg(value: "-wsdl")
}
}
}
compileJava.dependsOn jaxb
这段代码定义在Gradle编译Java代码前调用xjc来根据WSDL生成类,这些类会生成在generated-sources/java下,这样,我们可以在IDE中看到生成的源代码。
sourceSets.main.java.srcDirs "src/generated-sources/java"
clean.delete "src/generated-sources"
上面第一行代码把生成的类添加到sourceSets中,这样,在Gradle编译Java代码时,生成的代码也会被编译。第二行代码在gradle运行clean的时候会删除生成的类。
有了上面的Gradle配置,我们运行Gradle,就可以看到访问Web service需要的JAXB类自动根据WSDL生成好并添加到我们的代码中了。下面,我们看如何访问SOAP Web service。在这个例子中,我使用的是CXF JAX-WS Dispatch来访问服务。在Java的第三方库中,其实还有很多其他的包可以用,CXF本身也不止支持Dispatch的访问方法。我选择CXF JAX-WS Dispatch,是因为喜欢它的简洁,通过下面的很短的代码,我们就可以访问这个服务了:
public class TestClient {
public static void main(String[] args) throws Exception {
QName serviceName = new QName("http://ws.cdyne.com/WeatherWS/", "Weather");
QName portName = new QName("http://ws.cdyne.com/WeatherWS/", "WeatherSoap");
Service service = Service.create(new URL("http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL"),
serviceName);
JAXBContext context = JAXBContext.newInstance("weblog.soap.client.example.model");
Dispatch<Object> dispatch = service.createDispatch(
portName,
context,
Service.Mode.PAYLOAD);
dispatch.getRequestContext().put("find.dispatch.operation", Boolean.TRUE);
GetWeatherInformation input = new GetWeatherInformation();
System.out.println("Start querying the web service");
Object responseObj = dispatch.invoke(input);
System.out.println("Got response:");
GetWeatherInformationResponse response = (GetWeatherInformationResponse)responseObj;
for (WeatherDescription description : response.getGetWeatherInformationResult().getWeatherDescription())
{
System.out.println("Weather Id: " + description.getWeatherID());
System.out.println("Weather Description: " + description.getDescription());
System.out.println();
}
}
}
代码本身其实很好懂,唯一需要说明的就是:
dispatch.getRequestContext().put("find.dispatch.operation", Boolean.TRUE);
如果我们comment out这行代码的话我们会得到以下的异常:
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Server did not recognize the value of HTTP Header SOAPAction: .
完整的代码我已经上传到Github上了,可以在https://github.com/mcai4gl2/soap-client-example找到。最后,对这个代码唯一的遗憾是我还没有找到如何配置CXF Dispatch的timeout的时间。这对访问提供大数据量的SOAP Web service是一个很重要的功能,如果哪位朋友知道的话,请一定告诉我,先谢谢了!