我在实际应用中遇到了这样的情况:需要调用一个web service,但web service的运行时间非常的长,可能需要好几个小时甚至几天才能运行结束返回结果。如果使用传统的调用方法,无论是同步调用还是异步调用都是无法得到结果的。因为无论是同步调用还是异步调用,服务端的运行都是同步的,即从接受请求开始一直到执行服务代码结束到返回结果都是一个线程,这个线程还牢记着当初接受请求时的那个socket,当运行完所有的代码时,当初的socket早就超时断开了,怎么能把运行结果返回到客户端?
为证明这一点,先做一个试验:
服务端代码:
public class HelloWorldAsyn {
/**
*
* @param name
* @return
*/
public String sayHello(String name) {
System.out.println("start sayHello");
try {
Thread.sleep(4 * 60 * 60 * 1000);
System.out.println("done");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, " + name;
}
}
客户端调用代码(已通过wsdl2java生成stub):
public class Client {
private static CountDownLatch cl = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
HelloWorldAsynServiceStub stub = new HelloWorldAsynServiceStub();
HelloWorldAsynServiceStub.SayHello sh = new HelloWorldAsynServiceStub.SayHello();
sh.setName("hyt");
stub.startsayHello(sh, new AsynCallBack(cl));
cl.await(); //阻塞
}
}
class AsynCallBack extends HelloWorldAsynServiceCallbackHandler {
private CountDownLatch cl;
public AsynCallBack(){}
public AsynCallBack(CountDownLatch cl) {
super();
this.cl = cl;
}
@Override
public void receiveResultsayHello(SayHelloResponse result) {
System.out.println(result.get_return());
cl.countDown();
}
}
运行Client调用web service,过了4小时你就会发现服务端报错了,查看tomcat 的日志就可以发现socket已经断开了
start sayHello
done
[ERROR] java.net.SocketException: 断开的管道
org.apache.axis2.AxisFault: java.net.SocketException: 断开的管道
at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430)
at org.apache.axis2.transport.http.SOAPMessageFormatter.writeTo(SOAPMessageFormatter.java:78)
at org.apache.axis2.transport.http.CommonsHTTPTransportSender.sendUsingOutputStream(CommonsHTTPTransportSender.java:364)
at org.apache.axis2.transport.http.CommonsHTTPTransportSender.invoke(CommonsHTTPTransportSender.java:241)
at org.apache.axis2.engine.AxisEngine.send(AxisEngine.java:443)
at org.apache.axis2.receivers.AbstractInOutMessageReceiver.invokeBusinessLogic(AbstractInOutMessageReceiver.java:43)
at org.apache.axis2.receivers.AbstractMessageReceiver.receive(AbstractMessageReceiver.java:114)
at org.apache.axis2.engine.AxisEngine.receive(AxisEngine.java:181)
at org.apache.axis2.transport.http.HTTPTransportUtils.processHTTPPostRequest(HTTPTransportUtils.java:172)
at org.apache.axis2.transport.http.AxisServlet.doPost(AxisServlet.java:146)
可见只是客户端的异步调用并不能解决我的需求。
查询了一些资料,解决方法也很简单粗暴,一个socket不行,来两个嘛。就是说当请求发给客户端的时候,只要请求成功,服务端发一个202的http报文给客户端,表明服务端已经收到请求,但尚未处理。等到服务端代码执行完毕后,服务端发送一个POST请求给客户端,其中就包含着执行结果,客户端收到后也返回202,这样一次web service调用就结束了。这个过程依赖axis2中的WS-Adressing模块功能。
Axis2也支持这样的方式来调用web service,配置也很简单
首先在服务对应的aar文件中的service.xml中添加
<parameter name="messageReceiver.invokeOnSeparateThread">true</parameter>
然后确保在tomcat里的axis配置文件,axis2.xml中启用了WS-Addressing模块(默认是启用的)
<module ref="addressing"/>
服务端的配置就算完了
在客户端调用时加上
ConfigurationContext myConfigContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(
"%AXIS2_HOME%/repository", "%AXIS2_HOME%/conf/axis2.xml");
HelloWorldAsynServiceStub stub = new HelloWorldAsynServiceStub(myConfigContext, "web serivce url", true);
stub._getServiceClient().engageModule("addressing");
第一行是读配置文件,我用的就是默认的配置文件,里面的内容没有修改
第二行创建stub对象,构造函数的第三个参数就等于是stub._getServiceClient().getOptions().setUseSeparateListener(true),这是告诉客户端发送和接受和发送使用两个socket
第三行是在客户端启用WS-Addressing模块,需要将addressing-1.x.x.mar文件引入客户端项目的classpath中
接下来的客户端代码无论是同步调用还是异步调用都和之前的一样了
这样就完成了axis2传输级异步的配置方式。
这是我采用这个配置方式调用远端的CalculatorService服务的http通信过程:
可见传输级异步就已经配置好了