概覽
Smooks是一個開源的Java框架,用于處理“數據事件流”。它常常被認為是一個轉換框架并以此被用于好幾個產品和項目中,包括JBoss ESB(以及其它ESB)。然而究其核心,Smooks未提及“轉換”或者其它相關的詞匯。它的應用遠不只這一點!
Smooks的工作是將結構化/層次化的數據流轉變成“事件”流,然后交與“訪問者邏輯(Visitor Logic)”進行分析,生成結果(可選的)。
源 ->結構化事件流(訪問者邏輯) ->結果
那么,有哪些工作是這個工具可以完成,而sax和dom等工具不能完成的呢?鑒于Smooks構建于這些技術之上,所以簡單的回答是“沒有”。Smooks真正的價值在于能更方便地消費SAX和DOM(Smooks現在還不支持基于StAX的過濾器)。它提供了一個訪問者API,以及一個配置模型,允許你輕易地將訪問者邏輯的目標設定為具體的SAX事件(如果使用的是SAX過濾器)或DOM元素(如果使用的是DOM過濾器)。Smooks同時還以一種標準方式簡化了對非XML源數據格式(EDI,CSV,JSON,Java等等)的消費,即由數據源產生的標準事件流變成了所有這些不同源數據格式的事實上的規范形式。這正是Smooks工作的關鍵!
使用Smooks的方式有兩種,你可以使用其中之一或結合使用它們:
- 模式一:你可以完全投入到Smooks中,編寫你自己的定制訪問者邏輯事件處理器,將其用于處理一個數據源事件流中特定事件。使用這一模式,你必須熟悉核心的API。
- 模式二:你可以重用由Smooks發行版提供的開箱即用解決方案,其數目正在不斷的增長中。在這種模式下,你只需要重用別人創建的組件即可,重新配置它們來處理你的數據源,例如,通過配置一些參數就可以由EDI數據源生成Java對象模型。
在這篇文章中,我們會快速地瀏覽一遍Smooks v1.1發行版提供的一些開箱即用的功能,即那些你不需要編寫任何代碼就可加以利用的功能(即模式二)。這包括:
- 多源數據格式:“輕易”地消費諸多流行的數據格式,包括XML,EDI,CSV,JSON和Java(是的,你可以以一種聲明性的方式完成java到java的轉換)。
- 轉換:利用諸多選項消費由數據源產生的事件流,產生其它格式的結果(即,對源進行“轉換”)。這包括能在過濾源數據流時對Smooks所捕獲的數據模型應用FreeMarker和XSL模板。鑒于這些模板資源能被運用于源數據事件流內部的事件,因此它們能被用來執行“基于片斷的轉換(fragment based transforms)”。這意味著Smooks能被用于對數據源的片斷執行轉換,而不僅限于將數據源作為一個整體來施行轉換。
- Java綁定:以一種標準方式由所支持的數據格式(即不僅限于XML)創建和生成Java對象模型/圖。由EDI數據源 生成某對象模型的配置與由XML數據源或JSON數據源生成對象模型所進行的配置幾乎一模一樣。唯一區別在于綁定配置的“事件選擇器(event selector)”取值不同。
- 大型消息處理:Smooks支持多種處理大型消息(GBs)的方式,它是通過基于SAX的過濾器完成的。由于綜合了 基于片斷轉換、Java綁定,以及使用節點模型混合DOM和SAX模型所帶來的能力,Smooks可以使用較低的內存空間處理大型消息。這些能力允許你執 行直接的1對1轉換,同時也支持對大型消息數據流執行1對多的分解、轉換和路由。
- 消息修飾:使用數據庫數據修飾消息。這可以按片斷來完成,即你可以按片斷來管理在一個片斷上的修飾。與此相關的一個用例是一個包含了消費者ID列表的消息在發往另一個流程前需要從數據庫提取消費者地址和概要數據來豐富。
- 基于可擴展XSD的配置模型:從Smooks v1.1開始,你可以用自己的可重用定制訪問者邏輯配置模型來擴展Smooks XSD配置名字空間。創建這些定制配置擴展只是一項簡單的配置工作,這個工作極大的增進了這些重用組件的可用性。所有的現有Smooks預置組件都利用了這一工具。
處理不同數據格式
Smooks的一個關鍵特性就是能很容易地將其配置成用標準方式處理不同數據格式。這意味著如果你為Smooks開發了一些定制的訪問者邏輯,這些代碼可以立即用于處理任何受支持的數據格式,就仿佛是Smooks的預置組件(Java綁定等)一樣。與之相關的,如果你為某種非預置支持的數據格式(例如 YAML)開發了一個定制的閱讀器實現,你立即就具備了一個能力:使用所有可獲得的預置訪問者邏輯(例如,Java綁定組件)來處理該類型數據產生的數據事件。使這成為可能的原因在于Smooks組件處理的是標準化的事件流(即規范形式)。
Smooks提供對處理XML、EDI、CSV、JSON和Java對象的開箱即用的支持。默認情況下,Smooks將源數據流當作XML來讀取(除非另有配置)。一個例外是Java對象源,它將被自動識別。對于其它所有數據格式類型,在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配置模型得以實現。
配置好的閱讀器負責將源數據流翻譯成結構化的數據事件流(即規范形式——目前基于SAX2)。Smooks監聽這一事件流,在合適的時候觸發配置好的訪問者邏輯(例如模板或綁定資源)。
執行一個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項目同樣定義了諸多新實現。
可視化非XML的結構化數據事件流
XML是最簡單的“由源數據流產生事件流”的可視化方案。所以對于一個XML源而言,不存在真正的問題。而對非XML源(如CSV),事情就不那么簡單了。該源一般與XML毫無共通之處。為了解決這個問題,Smooks提供了執行報告產生器(Execution Report Generator)工具。這一工具的一大用途就是幫助你對非XML數據源產生的事件流進行可視化,格式為XML。它同樣是很好的調試工具。
這一報告產生工具被注入到了Smooks的執行上下文中:
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開發一個Eclipse編輯器,它將作為JBoss Tools的一部分。這些工具將進一步簡化可視化并操作非XML數據源事件流的過程。
拆分、轉換與路由
這一用例很好地示范了如何組合幾個Smooks功能來執行一個更復雜的任務。
繼續CSV的例子,我們有以下的基本需求:
- CSV流可能會很大,因此我們需要使用SAX過濾器。
- 我們需要將每個CSV記錄路由到一個JMS端點,格式是XML。這意味著我們需要拆分,轉換和路由這些消息。
Smooks使用諸如XSL和FreeMarker這樣的流行模板技術來對使用基于片斷的轉換提供支持。Smooks同時還提供了從源事件流(同樣可能是非XML的)抓取DOM模型的能力,就算在使用SAX過濾器的情況下也能實現。有了這一功能,Smooks能夠從源數據片斷中構建“迷你”DOM模型并讓其它Smooks資源(如FreeMarker模板和Groovy腳本資源)可以利用它們。采取這種方式,你能在保持以流式環境進行處理的同時,得到某些DOM處理模型的好處。對于此處所說的用例,我們將使用FreeMarker作為模板技術。
Smooks同樣對將數據片斷(由源數據片斷所產生)路由到多個不同的端點類型(即JMS,文件或數據庫)提供了開箱即用的支持。如同Smooks中的所有事物一樣,這種能力總是可以被擴展或被其他用例重復使用,例如,可以輕而易舉的插入一個定制的電子郵件訪問者組件。JBoss ESB(以及其它的ESB)從運行于ESB之上的Smooks過濾流程內部提供定制的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為記錄片斷(參見上述執行報告)創建節點模型。每個記錄的節點模型都將覆蓋前一個片斷產生的節點模型,所以任一已知時刻內存中都絕不會出現一個以上(作為一個節點模型)的CSV記錄。
- 配置(4)指示Smooks在每個片斷結束時將beanId“csv_record_as_xml”的內容路由到指定的JMS目的地。
- 配置(5)指示Smooks在每個片斷結束時運用指定的FreeMarker模板(5.a)。該模板操作的結果綁定到beanId“csv_record_as_xml”(5.b)。
FreeMarker模板(5.a)也可以直接在Smooks配置中定義(在<ftl:template></ftl:template>元素內部),但在這個例子中我們將其定義在外部文件:
<#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”片斷節點模型。(譯注:由于原文編輯錯誤,導致HTML代碼中雖有csv-record字樣,但在展示到瀏覽器中卻沒有出現)
Java綁定
Smooks可以有效地被用來從任意所支持的源數據格式來生成Java對象模型。生成后的對象模型本身可以作為最終結果使用,也可以被用作模板操作的模型,即生成后的對象模型(存儲在bean上下文里)可供模板技術(就像運用節點模型一樣)使用。
再次繼續我們的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在遇到消息的開始時(該元素)創建一個ArrayList的實例,并將其綁定到beanId“customerList”的bean上下文中。我們想要將(2.a)實例的“customer”bean(3)裝配到這一ArrayList。
- 配置(3)指示Smooks在遇到每一元素的開時時創建一個Customer類的實例。每個元素都定義了一個值綁定,從事件流中選擇數據并將這一數據解碼后的值綁定到當前Customer實例的指定屬性中。配置(3.a)告訴Smooks對Gender屬性使用Enum解碼器。
當然,上述分拆、轉換和路由用例的一個變體可能是將生成好的Customer對象路由到JMS隊列,而不是一個FreeMarker模板所產生的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記錄執行多路由操作,可以將Customer對象路由到JMS隊列同時路由到FreeMarker產生的XML消息以歸檔。
性能
這一問題不可避免地一次次被提起。我們對Smooks進行了許多次的隨機基準測試,以下的小節就是我們得到的普遍結果。
- Smooks核心過濾開銷: Smooks內核使用SAX過濾器(使用Xerces作為XML閱讀器)對XML進行處理,在沒有配置訪問者邏輯的情況下,較使用相同SAX解析器直接進行的SAX處理增加了大概百分之五到十的開銷。
- Smooks模板開銷: 在早期的Smooks版本中,為了對比“通過Smooks運用XSLT”和“單獨使用XSLT”的開銷,我們再次執行了一些基準測試以期對其進行確定。Smooks當時(以及現在)僅通過DOM過濾器來支持XSLT。與基于DOM的XSLT應用相比,Smooks增加了百分之五到十五的開銷,準確值取決于XSL處理器。
- Smooks Java綁定開銷:我們對于這一點的結果僅僅是基于跟一個主要的“XML到Java”開源綁定框架的對比。我們的發現是對于較小的消息(如小于10k),Smooks稍有些慢,但對于大一點的消息而言就快得多了。
今天,Smooks正被應用于好些個任務關鍵的生產環境。每當我們收到任何關于性能的詢問時,其原因總是歸咎于某種配置問題(比如使執行報告產生器一直保持開啟狀態)。一旦將其解決,用戶總會對性能非常滿意。這雖然并非一個十分恰當的證據,但是它告訴了我們Smooks在性能方面不是“軟蛋”。
總的來說,Smooks內核是相當高效的,較標準的基于SAX的XML處理而言只增加了相對較低的開銷。在這以后,性能取決于所配置的訪問者邏輯,它的目的和效率表現。
Smooks的下一步
Smooks v1.2的首要目標是提供更多處理EDI消息的工具。我們同樣希望對某些更流行的EDI消息類型提供開箱即用的支持。
如前所述,Smooks開發的另一重要工作將會是繼續JBoss Tools項目,構建一個Smooks的Eclipse編輯器。
總結
希望這一文章能讓讀者對Smooks及其核心功能有個更好的了解。我們希望人們能下載Smooks,瞧一瞧看一看,并提供些反饋。
查看英文原文:Structured Event Streaming with Smooks。
中文原文地址: http://www.infoq.com/cn/articles/event-streaming-with-smooks