使用jboss netty 创建高性能webservice客户端及服务端

堆码时刻 2014-12-28

                                  使用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(){

publicvoid onTagEnd(String tagName, Map<String, Object> attrs) {

                  System.out.println(tagName+"---"+attrs);

              }

              publicvoidonTagStart(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

<!--[if !supportLists]-->(1)      <!--[endif]-->文本节点会被解析为:key为“@value”的键值对,所有的xml属性作为key时在末尾都会加上@attr后缀。

<!--[if !supportLists]-->(2)      <!--[endif]-->子节点会作为父节点属性存在(也是作为map的一个key-value对),其key值为子节点元素名,因为sax解析并不能处理命名空间及前缀,解析出来节点名和属性都带着命名空间前缀,但这样做的好处是提高了解析的效率。

<!--[if !supportLists]-->(3)      <!--[endif]-->如果同名子元素出现两次,它将被封装为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调用的接口是同步的,要想进一步提高性能需要做以下工作

<!--[if !supportLists]-->(1)    <!--[endif]-->将接口改成异步的,可以在高并发的情况下提高性能,节省资源消耗。

<!--[if !supportLists]-->(2)    <!--[endif]-->将消息格式改成非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>

 

下面就其特色简单说一下

<!--[if !supportLists]-->1.      <!--[endif]-->支持对wsdl的访问。Wsdl文件需要提前编写好放到项目的classpath中,最好跟业务类放到一起,便于实现不同的服务和维护。

<!--[if !supportLists]-->2.      <!--[endif]-->业务类需要实现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前端展现树状结构,现在只用里面一个载入资源的类,直接使用即可。

由于篇幅和时间所限讲的比较粗略,想研究具体工作原理的朋友请自行阅读源码并进行实验。

 

相关推荐