使用Fast Infoset提高Java Web Service性能
源代码
【IT168 专稿】
众所周知,XML信息传输和处理是Web Service编程模型的基石。为了更有效地改善Web Service的性能,我们需要尽可能是减少和分析、序列化和传输XML数据无关的信息。快速信息集(Fast InfoSet)就是这种理念的一种开源的,基于标准的解决方案。这种技术为我们提供了一些用于最小化XML数据量和使处理XML数据的速度最大化的技术。使用这些技术,我们可以通过调整快速信息集的编码来满足不同的需要,如通过压缩来减小数据量。
一般情况下,快速信息集文档比普通的XML文档要小,因此,在处理速度上也会比相应的XML文档更快。这样对一些XML文档的处理时间和尺寸关系紧密的情况下是非常有用的。如W3C的XML二进制描述工作组已经确定了如下的两种情况:
1. 在有带宽限制的small devices上使用Web Service
一般情况下,快速信息集文档比普通的XML文档要小,因此,在处理速度上也会比相应的XML文档更快。这样对一些XML文档的处理时间和尺寸关系紧密的情况下是非常有用的。如W3C的XML二进制描述工作组已经确定了如下的两种情况:
1. 在有带宽限制的small devices上使用Web Service
2. 在一个有高吞吐量的企业中使用Web Service
本文主要介绍了快速信息集的基本概念,并通过举例来演示了一个基于JAX-WS的实现,在这个例子中提供了一些典型的数据来显示快速信息集和MTOM/XOP(另一种用于优化XML数据传输和处理的技术)在优化Web Service性能上午效率。
本文主要介绍了快速信息集的基本概念,并通过举例来演示了一个基于JAX-WS的实现,在这个例子中提供了一些典型的数据来显示快速信息集和MTOM/XOP(另一种用于优化XML数据传输和处理的技术)在优化Web Service性能上午效率。
一、XML信息集和快速信息集的比较
XML信息集是一种以XML文档格式存储的信息的抽象模型。XML信息集是W3C所制定的规范,分析一个XML文档的结果叫做XML信息集,识别不同的信息集组件叫做信息项和属性。还有更多的技术,如SOAP1.2和MTOM/XOP等,也是根据XML信息集定义的,这是因为大多数的涉及到信息存储的应用案例都是以XML文档而不是其它的编码存储的。
快速信息集规范提供了一个使用ASN1.1二进制编码规范的信息集实例的描述。这种规范可以作为W3C信息集语法的一种选择。快速信息集文档保留了XML信息集的层次结构。并可根据我们的选择,快速信息集文档可以是自包含的,也可以不是。自包含形式的快速信息集文档可以和传统的XML文档互相转换,而且在这个转换过程中不会丢失任何信息,因此,我们可以认为自包含的快速信息集和XML信息集是等价的。
二、在JAX-WS2.0 Web Service中使用快速信息集
在这一部分,我们将开发和测试一个基于JAX-WS的Web service的例子,在这个例子中将演示快速文档集的使用方法。
在这个例子中,Web Service模拟了一个图书目录管理系统。Web service接口CatalogPort定义了三个操作:echo、upload和retrieve。我们可以使用echo()方法来使客户端初始化一个服务请求,并为其他的信息交换创建快速信息集文档。upload()和retrieve()方法可以分别让客户端来注册(update)图书信息以及获得被存储在管理系统中图书的例子章节。
在CatalogImpl.java的实现中,upload()方法将保存以下四种信息:
1. 图书的基本信息,以及保存在book_info目录中的${isbn}.properties文件中的例子章节名。
2. 保存在book_cover目录中的封面图象。
3. 保存在book_desc目录的${isbn}.txt中的描述字符串。
4. 保存在book_sample目录的${isbn}.pdf文件中的章节目录。
upload()方法简单地返回一个Boolean类型值,这个值确定了这个方法是否成功地完成了处理工作。upload方法的部分代码如下(如果想查看服务端完整的代码,请下载本文提供的源代码):retrieve()方法则从属性文件和图书相关目录(所有的目录都是基于输入参数isbn的)中获得章节名,并向客户端返回一个章节对象。在一个完整的信息交换过程中,涉upload()方法的调用,XML的处理和传输主要和请求有关,请求信息的大小主要依赖于图书封面图象的尺寸以及章节内容。而retrieve()方法的调用、XML的处理和传输主要和响应有关。响应信息的大小主要依赖于章节内容的尺寸。我们在以后可以考虑这些内容来更进一步地优化数据传输性能。retrieve方法的实现代码如下:public boolean upload(Book bookInfo) { Properties info = new Properties(); if (bookInfo.getIsbn() == null || bookInfo.getIsbn().length() == 0) return false; if (bookInfo != null) { info.setProperty("title", bookInfo.getTitle()); info.setProperty("author", bookInfo.getAuthor()); info.setProperty("pageNumber", new Integer(bookInfo.getPageNumber()).toString()); info.setProperty("price", bookInfo.getPrice()); Chapter sample = bookInfo.getSample(); if (sample != null) info.setProperty("sample", sample.getName()); File bookInfoFile = new File(getDataDir("book_info") + bookInfo.getIsbn() + ".properties"); FileOutputStream infoOut = null; try { infoOut = new FileOutputStream(bookInfoFile); info.store(infoOut, null); infoOut.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (infoOut != null) try { infoOut.close(); } catch (IOException e) { e.printStackTrace(); } } File descFile = new File(getDataDir("book_desc") + bookInfo.getIsbn() + ".txt"); PrintWriter pw = null; try { pw = new PrintWriter(new FileWriter(descFile)); pw.write(bookInfo.getDesc()); pw.flush(); } catch (IOException e1) { e1.printStackTrace(); } finally { if (pw != null) pw.close(); } Image coverImage = bookInfo.getCoverImage(); FileOutputStream coverOut = null; ImageOutputStream stream = null; ImageWriter writer = null; if (coverImage != null) { BufferedImage bufImage; try { bufImage = convertToBufferedImage(coverImage); File bookCoverFile = new File(getDataDir("book_cover") + bookInfo.getIsbn() + ".jpg"); coverOut = new FileOutputStream( bookCoverFile); Iterator i = ImageIO.getImageWritersBySuffix("jpg"); if (i.hasNext()) { writer = (ImageWriter) i.next(); } if (writer != null) { stream = ImageIO.createImageOutputStream(coverOut); writer.setOutput(stream); writer.write(bufImage); stream.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { if (coverOut != null) try { coverOut.close(); } catch (IOException e) { e.printStackTrace(); } if (stream != null) try { stream.close(); } catch (IOException e) { e.printStackTrace(); } } } FileOutputStream sampleOut = null; if (sample != null) { File bookCoverFile = new File(getDataDir("book_sample") + bookInfo.getIsbn() + ".pdf"); try { sampleOut = new FileOutputStream(bookCoverFile); sampleOut.write(sample.getContent()); sampleOut.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (sampleOut != null) try { sampleOut.close(); } catch (IOException e) { e.printStackTrace(); } } } } return true; }
public Chapter retrieve(String isbn) { if ((isbn == null) || isbn.length() == 0) return null; Chapter ch = new Chapter(); File bookInfoFile = new File(getDataDir("book_info") + isbn + ".properties"); Properties book_info = new Properties(); FileInputStream fis = null; try { fis =new FileInputStream(bookInfoFile); book_info.load(fis); ch.setName(book_info.getProperty("sample")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } File sampleCh = new File(getDataDir("book_sample") + isbn + ".pdf"); FileInputStream sampleIn = null; try { sampleIn = new FileInputStream(sampleCh); int availableLength = sampleIn.available(); byte[] totalBytes = new byte[availableLength]; sampleIn.read(totalBytes); ch.setContent(totalBytes); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (sampleIn != null) try { sampleIn.close(); } catch (IOException e) { e.printStackTrace(); } } return ch; }
为了更好地实验本文所提供的例子,我们可以从源代码的rar包中将web service WSDL和相关的XML文件解出来。为了使用JAX-WS2.0来实现Web Service,我们需要使用apt工具来自动产生CatalogPortType.java接口类型文件,这个类在com.company.mtom.catalog.book包中,服务类Catalog.java在客户端将获得并使用CatalogPort。
在创建服务实现类之后,我们就要为web service的部署和运行做准备了。我们首先需要建立Web Service发布表述文件(sun-jaxws.xml)和Web应用程序发布描述文件(web.xml),然后通过war包将Web Service发布的相应的容器中(在本文的例子中使用的是Tomcat)。
在客户端,我们可以在一个叫isbn的文件中输入图书信息和封面图,例子章节内容以及一些描述文本。在本例的客户端中,private方法getBook()创建一个Book对象,然后使用这个对象的upload()来上传信息。getBook方法的代码如下:
private Book getBook(Properties prop) { Chapter ch = new Chapter(); Book book = new Book(); ch.setName(prop.getProperty("sample")); //Get the sample chapter content File sampleCh = new File("./etc/" + prop.getProperty("content") + ".pdf"); FileInputStream sampleIn = null; try { sampleIn = new FileInputStream(sampleCh); int availableLength = sampleIn.available(); byte[] totalBytes = new byte[availableLength]; sampleIn.read(totalBytes); ch.setContent(totalBytes); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (sampleIn != null) try { sampleIn.close(); } catch (IOException e) { e.printStackTrace(); } } //Get cover image Image cover = getImage("./etc/" + prop.getProperty("coverImage") + ".jpg"); //Read the desc from the text file String desc = readText("./etc/" + prop.getProperty("desc") + ".txt"); book.setAuthor(prop.getProperty("author")); book.setDesc(desc); book.setPageNumber(new Integer(prop.getProperty("pageNumber"))); book.setPrice(prop.getProperty("price")); book.setTitle(prop.getProperty("title")); book.setSample(ch); book.setCoverImage(cover); return book; }
然后使用compareSample()方法可以将使用retrieve()从远程获得的图书目录和内容和本地的相应内容进行比较。要注意的是,在客户端使用的是开源的JAMon库来收集相应时的信息。compareSample方法的实现代码如下:
private void compareSample(Chapter retrieved, Properties prop){ File sampleCh = new File("./etc/" + prop.getProperty("content") + ".pdf"); FileInputStream sampleIn = null; byte[] totalBytes = null; try { sampleIn = new FileInputStream(sampleCh); int availableLength = sampleIn.available(); totalBytes = new byte[availableLength]; sampleIn.read(totalBytes); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (sampleIn != null) try { sampleIn.close(); } catch (IOException e) { e.printStackTrace(); } } boolean isTitleTheSame = (prop.getProperty("sample").equals(retrieved.getName())); boolean isContentTheSame = Arrays.equals(totalBytes, retrieved.getContent()); System.out.println("The sample chapter title is as expected:" + isTitleTheSame); System.out.println("The sample chapter content is as expected:"+ isContentTheSame); }
在本例子中提供了三本例子图书来演示这个过程。为了在传输过程中查看SOAP信息,我们可以完装用于截获TCP信息的工具,tcpmon来查看TCP信息。在后面的章节,我们将看到快速信息集什么时候进行数据交互。这些都可以通过tcpmon观察到。在下节中,我们将看到在JAX-WS中如何使用快速信息集来传输Web Service数据。
三、在JAX-WS中的快速信息集
JAX-WS通过检查标准的HTTP头的Accept和Content-Type字段来支持快速信息集。在默认情况下,所有的JAX-WS Web Services的快速信息集都是打开的(通过服务运行时实现)。为了在信息交换中使用快速信息集,客户端必须明确指出客户端运行时有支持快速信息集文档的能力。我们可以通过向服务端发送一个以标准的XML格式编码的初始化请求,这个请求包括了HTTP Accept字段,它的值为MIME类型的application/fastinfoset。在发送初始化请求后,所有的客户端和服务端的数据规范都将以快速信息集格式编码,只要客户端继续使用同一个BindingProvider对象实例就会一直保持这种状态。
在JAX-WS中,我们可以用两种方法来完成上述的约定:
1. 在客户端的JVM中设置系统属性。
2. 在binding provider对象(一个端口协议和委派)中设置属性。
2. 在binding provider对象(一个端口协议和委派)中设置属性。
不管使用哪种方法,都需要指定属性名" com.sun.xml.ws.client.ContentNegotiation"和它的值"pessimistic"。在JAX-WS2.0中为了描述属性名,还在com.sun.xml.ws.developer.JAXWSProperties接口中定义了一个常量,CONTENT_NEGOTIATION_PROPERTY。但这个常量在以后的JAX-WS版本中并不建议使用。
在这里我们使用第一种方法(也就是设置系统属性),我们可以通过如下的命令行来运行Web service客户端:
java –Dcom.sun.xml.ws.client.ContentNegotiation=pessimistic ...
下面的代码段显示了如何使用Ant来运行Web Sevice客户端:
我们还可以通过第二种方法(也就是设置binding provider对象的方法)来达到同样的目的:<target name="run"> <java fork="true" classname="${client}"> <classpath> <path refid="client.classpath"/> <pathelement location="${build.classes.home}"/> <pathelement location="${basedir}/conf"/> </classpath> <sysproperty key="com.sun.xml.ws.client.ContentNegotiation" value="pessimistic"/> <arg value="${isbn}"/> </java> </target>
上面的例子使用了一个端口协议。如果我们正在使用一个Dispatch实例,我们可以再次转换它,以得到一个请求上下文,并设置相应的属性来开启数据传输规范。这是因为在JAX-WS中的Dispatch实例也是一个BindingProvider。CatalogPortType port = service.getCatalogPort(); ((BindingProvider) port).getRequestContext().put(JAXWSProperties.CONTENT_NEGOTIATION_PROPERTY, "pessimistic"); //((BindingProvider) port).getRequestContext().put("com.sun.xml.ws.client.ContentNegotiation", "pessimistic");
四、Web Service的性能比较: Fast Infoset PK. MTOM/XOP
MTOM(SOAP Message Transmission Optimization Mechanism)是一种W3C推荐使用的将SOAP协议打包成二进制格式以取代Base64编码的方式。这种技术依赖于XOP(XML-binary Optimized Packaging),这是一种将二进制技术嵌入到XML信息集中的机制。XOP只支持对大量字符信息项的嵌入。而我们前面所讲到的快速信息集除了可以保存字符信息,还可以保存属性项信息。从这方面上看,快速信息集比MTOM/XOP更有弹性。
JAX-WS2.0和它的相关实现同时支持快速信息集和MTOM/XOP。那么我们怎么选择这两种技术呢?它们的性能如可呢?下面让我们看一下本文提供的例子在这种技术下的测试结果。
我们假设有下面三种类型程序,图1和图2分别显示了这三种类型的程序在调用update()和retrieve()的平均相应时间(ART):
MTOM(SOAP Message Transmission Optimization Mechanism)是一种W3C推荐使用的将SOAP协议打包成二进制格式以取代Base64编码的方式。这种技术依赖于XOP(XML-binary Optimized Packaging),这是一种将二进制技术嵌入到XML信息集中的机制。XOP只支持对大量字符信息项的嵌入。而我们前面所讲到的快速信息集除了可以保存字符信息,还可以保存属性项信息。从这方面上看,快速信息集比MTOM/XOP更有弹性。
JAX-WS2.0和它的相关实现同时支持快速信息集和MTOM/XOP。那么我们怎么选择这两种技术呢?它们的性能如可呢?下面让我们看一下本文提供的例子在这种技术下的测试结果。
我们假设有下面三种类型程序,图1和图2分别显示了这三种类型的程序在调用update()和retrieve()的平均相应时间(ART):
1. 一个标准的JAX-WS Web Service程序
2. 客户端和服务端都基于MTOM(MTOM/XOP)的程序
3.基于快速信息集(FI) 的程序
图1显示了调用upload方法的结果。X轴的X+Y的格式表示了图书封面图和例子章节

图1 调用upload()方法的ART数据
图2是调用retrieve方法的ART数据,参数格式和图1相同。

图2 调用retrieve()方法的ART数据
从上面两个图所示的数据已经非常清楚了,Web Services尤其在处理和传输二进制数据时,使用MTOM和快速信息集可以获得相当大的性能提升。然而,但在JAX-WS2.0中的快速信息比MTOM更出色,因此,在JAX-WS中使用快速信息集应该是最好的选择。
五、快速信息集已经获得了更广泛的支持
快速信息集是一种应用非常广泛的技术,它已经被多种平台和框架所支持,如微软的.NET和.NET CF,Sun的GlassFish,BEA的WebLogic以及TMax的JEUS 6。还有在Linux、Solaris和Win32操作系统中也支持快速信息集。对于开发人员,尤其是从事SOA开发的技术人员,这项技术也许会是一个福音。也许我们正在使用一处非常有前途的技术,并应该更好地学习和应用它,以便利用它来建立基于XML的更高效的Web Service。
0
相关文章