使用jboss netty 创建高性能webservice客户端及服务端
使用jboss netty 创建高性能webservice客户端及服务端
通过本文,读者将了解以下内容
(1)利用jboss netty创建一个高性能的web服务客户端
(2)不使用任何第三方框架,手工在web容器内创建webservice服务器端
在不依赖任何webservice框架的情况下,轻量级的实现这两个目的,并且使你拥有更多的控制及定制能力。甚至可以越过soap协议的限制,使用你自己喜欢的自定义的消息格式来传递xml消息。
想象这样一个情况:一个项目中使用apache cxf作为webservice使用的框架,消息的发送和接受要经过漫长的cxf处理管线。虽然cxf性能不错,但是如果项目要求webservice交互的数量是每天数以百万计呢?还有,在webservice的开发中总有一些令人烦恼的需求,比如同你交互的厂商技术不规范,而你又不得不迁就它的接口(国企实际情况,我必须和另外一家厂商的企业服务总线传递消息,而它的wsdl文件甚至无法通过schema验证),从而使你的cxf或axis不停报错。这时你要怎么办呢?
另外,还有一些特殊要求。比如有的服务端要求你加上一些特殊的soap协议头用来进行认证授权。虽然所有的开源框架都支持这样做,但无论怎样都是一件麻烦的事情。
有没有一个方法可以给web服务开发以更大的定制性及更简化的开发方式呢?
整篇文章分三个部分,首先介绍一个xml解析类,用于在xml数据模型和java类型之间进行转换。然后在这个基础上,介绍了如何使用netty创建高性能web服务客户端。最后介绍自定义webservice服务器端的方法,算是对j2ee初学者的一个教学和启示。
一.Xml解析器
自己实现webservice框架,少不了同xml打交道。我首先考虑是性能和灵活性。市面上的开源项目没有能够满足要求的。于是自己写了一个相对简单的解析器来解决问题。解析器有两种解析方式(1)将xml转换为java数据类型。为了提高速度只是解析为map和list, 并不能够解析为bean (2)提供一个回调接口,支持更灵活的从xml提取感兴趣的信息。
例如,解析如下报文
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://yourcompany.com">
<soapenv:Header/>
<soapenv:Body>
<com:requset>
<com:message>item1</com:message>
<com:message>item2</com:message>
<com:action>insert</com:action>
</com:requset>
<com:requset>
<com:action>delete</com:action>
</com:requset>
</soapenv:Body>
</soapenv:Envelope>
XMLTranslater.xmlToJava(xmlStr,new XmlCallback(){
public void onTagEnd(String tagName, Map<String, Object> attrs) {
System.out.println(tagName+"---"+attrs);
}
public void onTagStart(String tagName, Map<String, Object> attrs) {
}
});
经解析后结果如下,读者注意看以下的调试信息
soapenv:Header---{}
com:message---{@value=item1}
com:message---{@value=item2}
com:action---{@value=insert}
com:requset---{com:message=[item1, item2], com:action=insert}
com:action---{@value=delete}
com:requset---{com:action=delete}
soapenv:Body---{com:requset=[{com:message=[item1, item2], com:action=insert}, {com:action=delete}]}
soapenv:Envelope---{xmlns:soapenv@attr=http://schemas.xmlsoap.org/soap/envelope/, xmlns:com@attr=http://yourcompany.com, soapenv:Header={}, soapenv:Body={com:requset=[{com:message=[item1, item2], com:action=insert}, {com:action=delete}]}}
每个xml元素都会被解析为map
(1) 文本节点会被解析为:key为“@value”的键值对,所有的xml属性作为key时在末尾都会加上@attr后缀。
(2) 子节点会作为父节点属性存在(也是作为map的一个key-value对),其key值为子节点元素名,因为sax解析并不能处理命名空间及前缀,解析出来节点名和属性都带着命名空间前缀,但这样做的好处是提高了解析的效率。
(3) 如果同名子元素出现两次,它将被封装为list,而且如果其中某个子元素只有文本子节点,这个子元素将被它内嵌的文本子节点代替,如下所示:
<com:requset>
<com:message>item1</com:message>
<com:message>item2</com:message>
<com:action>insert</com:action>
</com:requset>
com:requset---{com:message=[item1, item2], com:action=insert}
另外,xmlToJava方法是有返回值的,返回xml的根元素解析出来的map对象
接口简明扼要,相信不需要我过多讲解,直接看示例和源代码即可。
现在,我们可以灵活高效的解析xml消息了,那么接下来我将介绍如何使用netty构建webservice客户端。
二.使用netty构建webservice客户端
通常,企业内部的消息传递是遵循“接口简单,业务复杂”的原则来规划的。也就是服务方法通常只有2,3个参数,每个参数都是字符串型,用来封装复杂的业务数据(xml或json格式)。这样在业务的变化的时候是不用修改接口的,这就使得不同厂家之间服务的对接,调试,测试的工作不用再进行一次,大大提高了对业务需求变化的响应速度。
从“模型(model)”生成后退一步是“模板(template)”生成,很多框架都是如此。
我们来看一个实际的例子,是从真实项目日志中截取出来的消息负载
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://yourcompany"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:Requset>
<in0>request…</in0>
</ns1:Requset>
</soapenv:Body>
</soapenv:Envelope>
响应如下:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<ns1:NotifyResponse xmlns:ns1="http://yourcompany">
<ns1:out>
Reponse…
</ns1:out>
</ns1:NotifyResponse>
</soap:Body>
</soap:Envelope>
因为篇幅的关系wsdl文件从略。
上面的例子采取的是所谓document-litaral-wrapped消息风格(注意最后的wrapped,如果是unwrapped,消息结构会不同,而且一般不用unwrapped风格)。这种风格也是一种推荐风格,大部分的厂商都是使用这种风格(其他风格当然也很容易支持)。从这种消息风格中,我们其实可以找出规律形成模板,具体参数通过对字符串模板替换即可。
模板如下:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="${nameSpaceUri}" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body>${message}</soapenv:Body></soapenv:Envelope>
可以看到具体消息的部分${message}是在代码里组装而成的,掌握了规律后,手工拼出soap请求报文不是什么难事,具体实现看源代码即可,这里不再赘述。
客户端使用的例子如下:
BootstrapHolder holder = new BootstrapHolder();
holder.initMethod();
try {
NettyClient client = new NettyClient();
client.setHolder(holder);
client.setEndpoint("http://someurl");
client.setNameSpaceUri("http://yourCompany");
//注意设置wrapped风格
client.setIsWrapped(true);
Object wsReturn = client.invoke("methodName", "paramName","paramValue");
} catch (Exception e) {
e.printStackTrace();
}
holder.destroyMethod();
这是调用一个单参数的webservice接口,返回值是soap:Body里中xml子节点转换成的java对象。当然框架还提供了一个多参数的方法,另外,可以对client设置一个xmlcallback回调函数,来更加灵活的从服务器端返回消息中提取有用数据。
注意在netty编程中要注意如下问题:
(1)BootstrapHolder对象是单例的,所有的client都应该共享这个对象。
(2)我一开始用的netty3.2.7final,同一个jetty server交互时出现了问题。出现了在连接真正关闭之前channel就已经关闭的情况。具体原因是使用了如下方式channel.getCloseFuture().await()来同步socket的关闭事件。现在HttpResponseHandler中,使用了一个CountDownLatch来进行同步解决了这个问题。
在webservice的项目开发中经常出现一些特殊要求。比如对方提出要加一个soap header来进行认证和授权,这个需求只需简单的修改模板(当然源码也得改,将字符串替换的逻辑加进去)就可以实现了,是不是很简单啊!
Client调用的接口是同步的,要想进一步提高性能需要做以下工作
(1) 将接口改成异步的,可以在高并发的情况下提高性能,节省资源消耗。
(2) 将消息格式改成非xml的,比如json或其他数据格式的.但是不同项目之间(不同厂家)的消息交互还是soap xml最通用。
三.在web容器内手工创建webservice服务器端
实际上就是在web容器内实现一个servlet即可,比如在web.xml中加如下配置
<servlet>
<servlet-name>xmlMessage</servlet-name>
<servlet-class>
xs.util.ws.server.XMLMessageServlet
</servlet-class>
<load-on-startup>100</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>xmlMessage</servlet-name>
<url-pattern>/xmlMessage/*</url-pattern>
</servlet-mapping>
下面就其特色简单说一下
1. 支持对wsdl的访问。Wsdl文件需要提前编写好放到项目的classpath中,最好跟业务类放到一起,便于实现不同的服务和维护。
2. 业务类需要实现XmlMessageService接口,在这个接口中有以下四个方法:
public abstract String invoke() throws Exception;
public abstract String error(Exception fault);
public abstract void before(InvokeContext invokecontext) throws Exception;
public abstract void after(InvokeContext invokecontext);
这四个接口都由XMLMessageServlet回调,其中在before中可以进行一些认证和授权的工作,在after中可以做日志,invoke只用来实现业务。这种方式对程序员编码来说会比较清爽。
请求参数由一个InvokeContext参数传递,减少耦合,增加扩展性
这一部分比较简单,建议j2ee初学者阅读源码,提高框架设计能力。
整个项目提供下载,由于上传文件大小的限制,请读者自行补全依赖包,清单如下
commons-lang.jar
commons-logging-1.1.1.jar
commons-collections-3.1.jar
commons-beanutils.jar
log4j-1.2.17.jar
spring3.x.jar
netty-3.x.jar
其中有一个webTreeViewer-092b.jar是我自己以前的一个包,用于在web前端展现树状结构,现在只用里面一个载入资源的类,直接使用即可。
由于篇幅和时间所限讲的比较粗略,想研究具体工作原理的朋友请自行阅读源码并进行实验。
下载