概覽
Smooks是一個開源的Java框架,用于處理“數(shù)據(jù)事件流”。它常常被認為是一個轉(zhuǎn)換框架并以此被用于好幾個產(chǎn)品和項目中,包括JBoss ESB(以及其它ESB)。然而究其核心,Smooks未提及“轉(zhuǎn)換”或者其它相關的詞匯。它的應用遠不只這一點!
Smooks的工作是將結(jié)構(gòu)化/層次化的數(shù)據(jù)流轉(zhuǎn)變成“事件”流,然后交與“訪問者邏輯(Visitor Logic)”進行分析,生成結(jié)果(可選的)。
源 ->結(jié)構(gòu)化事件流(訪問者邏輯) ->結(jié)果
那么,有哪些工作是這個工具可以完成,而sax和dom等工具不能完成的呢?鑒于Smooks構(gòu)建于這些技術之上,所以簡單的回答是“沒有”。Smooks真正的價值在于能更方便地消費SAX和DOM(Smooks現(xiàn)在還不支持基于StAX的過濾器)。它提供了一個訪問者API,以及一個配置模型,允許你輕易地將訪問者邏輯的目標設定為具體的SAX事件(如果使用的是SAX過濾器)或DOM元素(如果使用的是DOM過濾器)。Smooks同時還以一種標準方式簡化了對非XML源數(shù)據(jù)格式(EDI,CSV,JSON,Java等等)的消費,即由數(shù)據(jù)源產(chǎn)生的標準事件流變成了所有這些不同源數(shù)據(jù)格式的事實上的規(guī)范形式。這正是Smooks工作的關鍵!
使用Smooks的方式有兩種,你可以使用其中之一或結(jié)合使用它們:
- 模式一:你可以完全投入到Smooks中,編寫你自己的定制訪問者邏輯事件處理器,將其用于處理一個數(shù)據(jù)源事件流中特定事件。使用這一模式,你必須熟悉核心的API。
- 模式二:你可以重用由Smooks發(fā)行版提供的開箱即用解決方案,其數(shù)目正在不斷的增長中。在這種模式下,你只需要重用別人創(chuàng)建的組件即可,重新配置它們來處理你的數(shù)據(jù)源,例如,通過配置一些參數(shù)就可以由EDI數(shù)據(jù)源生成Java對象模型。
在這篇文章中,我們會快速地瀏覽一遍Smooks v1.1發(fā)行版提供的一些開箱即用的功能,即那些你不需要編寫任何代碼就可加以利用的功能(即模式二)。這包括:
- 多源數(shù)據(jù)格式:“輕易”地消費諸多流行的數(shù)據(jù)格式,包括XML,EDI,CSV,JSON和Java(是的,你可以以一種聲明性的方式完成java到java的轉(zhuǎn)換)。
- 轉(zhuǎn)換:利用諸多選項消費由數(shù)據(jù)源產(chǎn)生的事件流,產(chǎn)生其它格式的結(jié)果(即,對源進行“轉(zhuǎn)換”)。這包括能在過濾源數(shù)據(jù)流時對Smooks所捕獲的數(shù)據(jù)模型應用FreeMarker和XSL模板。鑒于這些模板資源能被運用于源數(shù)據(jù)事件流內(nèi)部的事件,因此它們能被用來執(zhí)行“基于片斷的轉(zhuǎn)換(fragment based transforms)”。這意味著Smooks能被用于對數(shù)據(jù)源的片斷執(zhí)行轉(zhuǎn)換,而不僅限于將數(shù)據(jù)源作為一個整體來施行轉(zhuǎn)換。
- Java綁定:以一種標準方式由所支持的數(shù)據(jù)格式(即不僅限于XML)創(chuàng)建和生成Java對象模型/圖。由EDI數(shù)據(jù)源 生成某對象模型的配置與由XML數(shù)據(jù)源或JSON數(shù)據(jù)源生成對象模型所進行的配置幾乎一模一樣。唯一區(qū)別在于綁定配置的“事件選擇器(event selector)”取值不同。
- 大型消息處理:Smooks支持多種處理大型消息(GBs)的方式,它是通過基于SAX的過濾器完成的。由于綜合了 基于片斷轉(zhuǎn)換、Java綁定,以及使用節(jié)點模型混合DOM和SAX模型所帶來的能力,Smooks可以使用較低的內(nèi)存空間處理大型消息。這些能力允許你執(zhí) 行直接的1對1轉(zhuǎn)換,同時也支持對大型消息數(shù)據(jù)流執(zhí)行1對多的分解、轉(zhuǎn)換和路由。
- 消息修飾:使用數(shù)據(jù)庫數(shù)據(jù)修飾消息。這可以按片斷來完成,即你可以按片斷來管理在一個片斷上的修飾。與此相關的一個用例是一個包含了消費者ID列表的消息在發(fā)往另一個流程前需要從數(shù)據(jù)庫提取消費者地址和概要數(shù)據(jù)來豐富。
- 基于可擴展XSD的配置模型:從Smooks v1.1開始,你可以用自己的可重用定制訪問者邏輯配置模型來擴展Smooks XSD配置名字空間。創(chuàng)建這些定制配置擴展只是一項簡單的配置工作,這個工作極大的增進了這些重用組件的可用性。所有的現(xiàn)有Smooks預置組件都利用了這一工具。
處理不同數(shù)據(jù)格式
Smooks的一個關鍵特性就是能很容易地將其配置成用標準方式處理不同數(shù)據(jù)格式。這意味著如果你為Smooks開發(fā)了一些定制的訪問者邏輯,這些代碼可以立即用于處理任何受支持的數(shù)據(jù)格式,就仿佛是Smooks的預置組件(Java綁定等)一樣。與之相關的,如果你為某種非預置支持的數(shù)據(jù)格式(例如 YAML)開發(fā)了一個定制的閱讀器實現(xiàn),你立即就具備了一個能力:使用所有可獲得的預置訪問者邏輯(例如,Java綁定組件)來處理該類型數(shù)據(jù)產(chǎn)生的數(shù)據(jù)事件。使這成為可能的原因在于Smooks組件處理的是標準化的事件流(即規(guī)范形式)。
Smooks提供對處理XML、EDI、CSV、JSON和Java對象的開箱即用的支持。默認情況下,Smooks將源數(shù)據(jù)流當作XML來讀?。ǔ橇碛信渲茫?。一個例外是Java對象源,它將被自動識別。對于其它所有數(shù)據(jù)格式類型,在Smooks配置里必須配置一個“閱讀器”。下面是一個CSV閱讀器的配置實例:
<xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:csv="http://www.milyn.org/xsd/smooks/csv-1.1.xsd">
<csv:reader fields="firstname,lastname,gender,age,country" separator="|" quote="'" skipLines="1"/>
<smooks-resource-list>
EDI、JSON等閱讀器的配置方式類似,都是通過唯一的配置名字空間進行的,比如<edi:reader></edi:reader>、<json:reader></json:reader>等。這些名字空間式的配置通過前面所講到的基于可擴展的XSD配置模型得以實現(xiàn)。
配置好的閱讀器負責將源數(shù)據(jù)流翻譯成結(jié)構(gòu)化的數(shù)據(jù)事件流(即規(guī)范形式——目前基于SAX2)。Smooks監(jiān)聽這一事件流,在合適的時候觸發(fā)配置好的訪問者邏輯(例如模板或綁定資源)。
執(zhí)行一個Smooks過濾器流程
這很簡單:
private Smooks smooks = new Smooks("/smooks-configs/customer-csv.xml");
publicvoid transCustomerCSV(Reader csvSourceReader, Writer xmlResultWriter) {
smooks.filter(new StreamSource(csvSourceReader), new StreamResult(xmlResultWriter));
}
Smooks.filter()方法消費標準的javax.xml.transform.Source和javax.xml.transform.Result類型。Smooks項目同樣定義了諸多新實現(xiàn)。
可視化非XML的結(jié)構(gòu)化數(shù)據(jù)事件流
XML是最簡單的“由源數(shù)據(jù)流產(chǎn)生事件流”的可視化方案。所以對于一個XML源而言,不存在真正的問題。而對非XML源(如CSV),事情就不那么簡單了。該源一般與XML毫無共通之處。為了解決這個問題,Smooks提供了執(zhí)行報告產(chǎn)生器(Execution Report Generator)工具。這一工具的一大用途就是幫助你對非XML數(shù)據(jù)源產(chǎn)生的事件流進行可視化,格式為XML。它同樣是很好的調(diào)試工具。
這一報告產(chǎn)生工具被注入到了Smooks的執(zhí)行上下文中:
private Smooks smooks = new Smooks("/smooks-configs/customer-csv.xml");
publicvoid transCustomerCSV(Reader csvSourceReader, Writer xmlResultWriter) {
ExecutionContext executionContext = smooks.createExecutionContext();
executionContext.setEventListener(new HtmlReportGenerator("target/report/report.html"));
smooks.filter(new StreamSource(csvSourceReader), new StreamResult(xmlResultWriter), executionContext);
}
(在Smooks v1.1中)其輸出是如下的一個HTML頁面:
JBoss正在為Smooks開發(fā)一個Eclipse編輯器,它將作為JBoss Tools的一部分。這些工具將進一步簡化可視化并操作非XML數(shù)據(jù)源事件流的過程。
拆分、轉(zhuǎn)換與路由
這一用例很好地示范了如何組合幾個Smooks功能來執(zhí)行一個更復雜的任務。
繼續(xù)CSV的例子,我們有以下的基本需求:
- CSV流可能會很大,因此我們需要使用SAX過濾器。
- 我們需要將每個CSV記錄路由到一個JMS端點,格式是XML。這意味著我們需要拆分,轉(zhuǎn)換和路由這些消息。
Smooks使用諸如XSL和FreeMarker這樣的流行模板技術來對使用基于片斷的轉(zhuǎn)換提供支持。Smooks同時還提供了從源事件流(同樣可能是非XML的)抓取DOM模型的能力,就算在使用SAX過濾器的情況下也能實現(xiàn)。有了這一功能,Smooks能夠從源數(shù)據(jù)片斷中構(gòu)建“迷你”DOM模型并讓其它Smooks資源(如FreeMarker模板和Groovy腳本資源)可以利用它們。采取這種方式,你能在保持以流式環(huán)境進行處理的同時,得到某些DOM處理模型的好處。對于此處所說的用例,我們將使用FreeMarker作為模板技術。
Smooks同樣對將數(shù)據(jù)片斷(由源數(shù)據(jù)片斷所產(chǎn)生)路由到多個不同的端點類型(即JMS,文件或數(shù)據(jù)庫)提供了開箱即用的支持。如同Smooks中的所有事物一樣,這種能力總是可以被擴展或被其他用例重復使用,例如,可以輕而易舉的插入一個定制的電子郵件訪問者組件。JBoss ESB(以及其它的ESB)從運行于ESB之上的Smooks過濾流程內(nèi)部提供定制的Smooks訪問者組件來完成基于片斷的ESB端點路由。
那么配置Smooks來完成上述用例就十分簡單了:
<xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:csv="http://www.milyn.org/xsd/smooks/csv-1.1.xsd"
xmlns:jms="http://www.milyn.org/xsd/smooks/jms-routing-1.1.xsd"
xmlns:ftl="http://www.milyn.org/xsd/smooks/freemarker-1.1.xsd">
<params>
(1) <param name="stream.filter.type">SAXparam>
params>
(2) <csv:reader fields="firstname,lastname,gender,age,country" separator="|" quote="'" skipLines="1"/>
(3) <resource-config selector="csv-record">
<resource>org.milyn.delivery.DomModelCreatorresource>
resource-config>
(4) <jms:router routeOnElement="csv-record" beanId="csv_record_as_xml" destination="xmlRecords.JMS.Queue"/>
(5) <ftl:freemarker applyOnElement="csv-record">
(5.a) <ftl:template>/templates/csv_record_as_xml.ftlftl:template>
<ftl:use>
(5.b) <ftl:bindTo id="csv_record_as_xml"/>
<ftl:use>
<ftl:freemarker>
<smooks-resource-list>
- 配置(1)指示Smooks使用SAX過濾器。
- 配置(2)指示Smooks對所提供的配置使用CSV閱讀器。
- 配置(3)指示Smooks為記錄片斷(參見上述執(zhí)行報告)創(chuàng)建節(jié)點模型。每個記錄的節(jié)點模型都將覆蓋前一個片斷產(chǎn)生的節(jié)點模型,所以任一已知時刻內(nèi)存中都絕不會出現(xiàn)一個以上(作為一個節(jié)點模型)的CSV記錄。
- 配置(4)指示Smooks在每個片斷結(jié)束時將beanId“csv_record_as_xml”的內(nèi)容路由到指定的JMS目的地。
- 配置(5)指示Smooks在每個片斷結(jié)束時運用指定的FreeMarker模板(5.a)。該模板操作的結(jié)果綁定到beanId“csv_record_as_xml”(5.b)。
FreeMarker模板(5.a)也可以直接在Smooks配置中定義(在<ftl:template></ftl:template>元素內(nèi)部),但在這個例子中我們將其定義在外部文件:
<#assign csvRecord = .vars["csv-record"]> <#-- special assignment because csv-record has a hyphen -->
<customer fname='${csvRecord.firstname}' lname='${csvRecord.lastname}' >
<gender>${csvRecord.gender}<gender>
<age>${csvRecord.age}<age>
<nationality>${csvRecord.country}<nationality>
<customer>
以上的FreeMarker模板引用了“csv-record”片斷節(jié)點模型。(譯注:由于原文編輯錯誤,導致HTML代碼中雖有csv-record字樣,但在展示到瀏覽器中卻沒有出現(xiàn))
Java綁定
Smooks可以有效地被用來從任意所支持的源數(shù)據(jù)格式來生成Java對象模型。生成后的對象模型本身可以作為最終結(jié)果使用,也可以被用作模板操作的模型,即生成后的對象模型(存儲在bean上下文里)可供模板技術(就像運用節(jié)點模型一樣)使用。
再次繼續(xù)我們的CSV實例。我們有一個消費者Java類,以及一個性別枚舉類型(已省略getter/setter):
publicclass Customer {
private String firstName;
private String lastName;
private Gender gender;
privateint age;
}
publicenum Gender {
Male,
Female
}
從CSV流中生成一組這一消費者對象的Smooks配置會像是這樣:
<xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.1.xsd">
(1) <csv:reader fields="firstname,lastname,gender,age,country" separator="|" quote="'" skipLines="1"/>
(2) <jb:bindings beanId="customerList" class="Java.util.ArrayList" createOnElement="csv-set">
(2.a) <jb:wiring beanIdRef="customer"/>
<jb:bindings>
(3) <jb:bindings beanId="customer" class="com.acme.Customer" createOnElement="csv-record">
<jb:value property="firstName" data="csv-record/firstName"/>
<jb:value property="lastName" data="csv-record/lastName"/>
<jb:value property="gender" data="csv-record/gender" decoder="Enum">
(3.a) <jb:decodeParam name="enumType">com.acme.Genderjb:decodeParam>
<jb:value>
<jb:value property="age" data="csv-record/age" decoder="Integer"/>
<jb:bindings>
<smooks-resource-list>
- 配置(1)指示Smooks對所提供的配置使用CSV閱讀器。
- 配置(2)指示Smooks在遇到消息的開始時(該元素)創(chuàng)建一個ArrayList的實例,并將其綁定到beanId“customerList”的bean上下文中。我們想要將(2.a)實例的“customer”bean(3)裝配到這一ArrayList。
- 配置(3)指示Smooks在遇到每一元素的開時時創(chuàng)建一個Customer類的實例。每個元素都定義了一個值綁定,從事件流中選擇數(shù)據(jù)并將這一數(shù)據(jù)解碼后的值綁定到當前Customer實例的指定屬性中。配置(3.a)告訴Smooks對Gender屬性使用Enum解碼器。
當然,上述分拆、轉(zhuǎn)換和路由用例的一個變體可能是將生成好的Customer對象路由到JMS隊列,而不是一個FreeMarker模板所產(chǎn)生的XML:
<xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:csv="http://www.milyn.org/xsd/smooks/csv-1.1.xsd"
xmlns:jms="http://www.milyn.org/xsd/smooks/jms-routing-1.1.xsd"
xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.1.xsd">
<params>
<param name="stream.filter.type">SAXparam>
params>
<csv:reader fields="firstname,lastname,gender,age,country" separator="|" quote="'" skipLines="1"/>
<jms:router routeOnElement="csv-record" beanId="customer" destination="xmlRecords.JMS.Queue"/>
<jb:bindings beanId="customer" class="com.acme.Customer" createOnElement="csv-record">
<jb:value property="firstName" data="csv-record/firstName"/>
<jb:value property="lastName" data="csv-record/lastName"/>
<jb:value property="gender" data="csv-record/gender" decoder="Enum">
<jb:decodeParam name="enumType">com.acme.Genderjb:decodeParam>
jb:value>
<jb:value property="age" data="csv-record/age" decoder="Integer"/>
jb:bindings>
<smooks-resource-list>
并且事情再復雜一點也沒關系,可以對每個csv記錄執(zhí)行多路由操作,可以將Customer對象路由到JMS隊列同時路由到FreeMarker產(chǎn)生的XML消息以歸檔。
性能
這一問題不可避免地一次次被提起。我們對Smooks進行了許多次的隨機基準測試,以下的小節(jié)就是我們得到的普遍結(jié)果。
- Smooks核心過濾開銷: Smooks內(nèi)核使用SAX過濾器(使用Xerces作為XML閱讀器)對XML進行處理,在沒有配置訪問者邏輯的情況下,較使用相同SAX解析器直接進行的SAX處理增加了大概百分之五到十的開銷。
- Smooks模板開銷: 在早期的Smooks版本中,為了對比“通過Smooks運用XSLT”和“單獨使用XSLT”的開銷,我們再次執(zhí)行了一些基準測試以期對其進行確定。Smooks當時(以及現(xiàn)在)僅通過DOM過濾器來支持XSLT。與基于DOM的XSLT應用相比,Smooks增加了百分之五到十五的開銷,準確值取決于XSL處理器。
- Smooks Java綁定開銷:我們對于這一點的結(jié)果僅僅是基于跟一個主要的“XML到Java”開源綁定框架的對比。我們的發(fā)現(xiàn)是對于較小的消息(如小于10k),Smooks稍有些慢,但對于大一點的消息而言就快得多了。
今天,Smooks正被應用于好些個任務關鍵的生產(chǎn)環(huán)境。每當我們收到任何關于性能的詢問時,其原因總是歸咎于某種配置問題(比如使執(zhí)行報告產(chǎn)生器一直保持開啟狀態(tài))。一旦將其解決,用戶總會對性能非常滿意。這雖然并非一個十分恰當?shù)淖C據(jù),但是它告訴了我們Smooks在性能方面不是“軟蛋”。
總的來說,Smooks內(nèi)核是相當高效的,較標準的基于SAX的XML處理而言只增加了相對較低的開銷。在這以后,性能取決于所配置的訪問者邏輯,它的目的和效率表現(xiàn)。
Smooks的下一步
Smooks v1.2的首要目標是提供更多處理EDI消息的工具。我們同樣希望對某些更流行的EDI消息類型提供開箱即用的支持。
如前所述,Smooks開發(fā)的另一重要工作將會是繼續(xù)JBoss Tools項目,構(gòu)建一個Smooks的Eclipse編輯器。
總結(jié)
希望這一文章能讓讀者對Smooks及其核心功能有個更好的了解。我們希望人們能下載Smooks,瞧一瞧看一看,并提供些反饋。
查看英文原文:Structured Event Streaming with Smooks。
中文原文地址: http://www.infoq.com/cn/articles/event-streaming-with-smooks