在上一篇博客Apache Camel源碼研究之TypeConverter中,我們介紹了Apache Camel實現數據格式轉換的一種實現方式,本文中我們將介紹另外一種實現方式 —— DataFormat。
1. 概述
相較於前面博客介紹過的TypeConverter,DataFormat在平時應用中應該更容易被研發人員所感知,畢竟其所支持的json,yaml,xml等轉換現在已經成爲事實的數據傳輸格式。本文接下來的部分我們將就其實現原理以及如何進行自定義擴展作出一些筆者自己的理解。
2. 源碼解讀
首先是本次的測試用例:
@Test
public void marshal() throws Exception {
//
CamelTestUtil.defaultPrepareTest2(new RouteBuilder() {
@Override
public void configure() throws Exception {
from("stream:in?promptMessage=Enter something:")//
.setBody(constant(Collections.singletonList("123")))//
.marshal().json(JsonLibrary.Gson) // 將對象轉換爲JSON字符串
//.setBody(constant("{'name':'LQ'}"))//
//.unmarshal().json(JsonLibrary.Gson)// 將JSON字符串轉換爲對象
.to("stream:out");
}
});
}
接下來,我們首先探究的是Camel是如何將這段轉換邏輯插入到Route的執行流程中的:
跟隨上述Route中定義的.marshal().json(JsonLibrary.Gson)
可以看出 Apache Camel是將一個 MarshalDefinition
實例添加到RouteDefinition中,注意該MarshalDefinition
類型是間接繼承自ProcessorDefinition<Type>
類的,按照我們之前在博客Apache Camel源碼研究之ProcessorDefinition中討論的內容,則可以很輕鬆得找到如下內容:
// MarshalDefinition.createProcessor()
@Override
public Processor createProcessor(RouteContext routeContext) {
// 專門的類和靜態方法來根據用戶配置創建相應的DataFormat實例
// 這個過程牽扯到的細節較多,我們將在下面以專門的小節進行討論
DataFormat dataFormat = DataFormatDefinition.getDataFormat(routeContext, getDataFormatType(), ref);
// 將DataFormat實例以Processor的形式介入到Apache Camel的執行邏輯鏈條中
// === 這裏多說一句的是, 與DataFormat類似的是 Camel中的 Expression 概念也是以類似的方式介入到Camel的執行邏輯鏈條中的,更具體的我們放到下一篇博客中
return new MarshalProcessor(dataFormat);
}
接下來讓我們看看 MarshalProcessor
有哪些需要關注的:
// MarshalProcessor
@Override
protected void doStart() throws Exception {
// inject CamelContext on data format
if (dataFormat instanceof CamelContextAware) {
((CamelContextAware) dataFormat).setCamelContext(camelContext);
}
// add dataFormat as service which will also start the service
// (false => we and handling the lifecycle of the dataFormat)
getCamelContext().addService(dataFormat, false);
}
@Override
protected void doStop() throws Exception {
ServiceHelper.stopService(dataFormat);
getCamelContext().removeService(dataFormat);
}
以上可以看到正是因爲DataFormat被Processor封裝,在獲取併入到Apache Camel執行邏輯鏈條權利的同時,也通過MarshalProcessor
覆寫自基類的doStart
,doStop
方法享受到了共享生命週期的好處。DataFormat可以藉此做一些自定義的初始化和銷燬操作。
在瞭解完初始化時的邏輯之後,執行時候的邏輯也就一目瞭然了:DataFormat
接口所定義的marshal
,unmarshal
兩個方法,在執行時序上依然是藉助Apache Camel中的靈魂組件Processor
來實現的。通過在用戶指定的時機採用同樣的方式回調相應的Processor組件,來將原始數據格式轉換爲目標數據類型,這種組件化的實現方式隱藏了實現細節,增加了系統的彈性。感興趣的讀者可以參見 Apache Camel源碼研究之啓動 。
3. DataFormat擴展機制
在瞭解了DataFormat的基本載入和執行機制之後,我們來關注一點更加深入的內容 —— Apache Camel是如何實現DataFormat的可擴展性的。相比較之下,org.apache.camel.model.dataformat
PACKAGE下DataFormatDefinition
的子類,數量遠遠大於org.apache.camel.impl
PACKAGE下DataFormat
接口的實現類。而且Camel作爲一個久負盛名,被多款商業級ESB平臺作爲核心組件的框架,將所有的實現類和相應依賴硬編碼到自身的core JAR中肯定是不可能的。
經過一番探究,我們找到如下關鍵類和配置文件:
DataFormatDefinition
- 其構造函數約定了 dataFormatName,Camel將以此爲標識符從容器中或者相應的配置目錄下查找相應的DataFormat實現類。
createDataFormat()
,作爲一個protected
修飾級別的方法,絕大部分子類正是藉助這個方法的覆寫來實現自身DataFormat
實現類的構造的。getDataFormat(RouteContext routeContext)
。正是這個方法回掉了上面的createDataFormat()
方法,在保證對外接口統一穩定的前提下,實現了強大的可擴展性。
DefaultDataFormatResolver
,以DataFormatResolver
接口形式直接在DefaultCamelContext中實例化。內部定義了查找目錄路徑"META-INF/services/org/apache/camel/dataformat/",Apache Camel將該路徑加上前面提到的dataFormatName
指示的文件名,從中讀取到DataFormat
接口實現類,- 最終堆棧圖如下:
camel-core下內置的DataFormat。
4. 自定義擴展
下面讓我們來嘗試一個自定義DataFormat:
public static class EbcdicDataFormat implements DataFormat {
// US EBCDIC 037 code page
private String codepage = "CP037";
public EbcdicDataFormat() {
}
public EbcdicDataFormat(String codepage) {
this.codepage = codepage;
}
@Override
public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
final String str = ExchangeHelper.convertToMandatoryType(exchange, String.class, graph);
stream.write(str.getBytes(codepage));
}
@Override
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
final byte[] bytes = ExchangeHelper.convertToMandatoryType(exchange, byte[].class,
stream);
return new String(bytes, codepage);
}
}
相應的測試用例:
@Test
public void custom() throws Exception {
//
CamelTestUtil.defaultPrepareTest2(new RouteBuilder() {
@Override
public void configure() throws Exception {
DataFormat df = new EbcdicDataFormat("CP037");
from("stream:in?promptMessage=Enter something:")//
// 相較於TypeConverter, DataFormat的使用就必須顯式調用了
.marshal(df) //
.to("stream:err");
}
});
}
5. DataFormat實現
最後讓我們看看DataFormat
接口的定義,以構建一個全局的認識和理解:
public interface DataFormat {
/**
* Marshals the object to the given Stream.
*/
void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception;
/**
* Unmarshals the given stream into an object.
*/
Object unmarshal(Exchange exchange, InputStream stream) throws Exception;
}
正如上述定義,該接口只聲明瞭兩個契約marshal
,unmarshal
(命名上也可以看出,這基本屬於共識了,諸如Spring中的OXM, JAXB等等也是同樣的),通過這對正/反向操作完成完整數據格式轉換的閉環操作。
6. Links
- 《Camel In Action》P77
- 《Apache Camel Developer’s CookBook》P100
- Office Site