1. 程式人生 > >Apache mina2 使用者指南(五)過濾器

Apache mina2 使用者指南(五)過濾器

IoFilter 扮演著很重要角色,它是 MINA 的核心結構之一。它過濾 IoService 和 IoHandler 之間的所有 I/O 事件和請求。如果你有網路應用程式設計的經驗,你完全可以把它當成 Servlet 過濾器的表兄弟。許多開箱即用的過濾器通過使用類似以下的開箱即用過濾器簡化橫切注入用來提升網路應用的開發速度:
  • LoggingFilter 記錄所有事件和請求
  • ProtocolCodecFilter 將一個連入的 ByteBuffer 轉化為訊息 POJO,反之亦然
  • CompressionFilter 壓縮所有資料
  • SSLFilter 新增 SSL - TLS - StartTLS 支援
  • 不止如此!
        本文我們將瞭解如何為一個真實案例實現一個 IoFilter。通常實現一個 IoFilter 是很簡單的,但你也需要 MINA 的內部細節。本文將對這些相關內部細節進行解釋。
現有的過濾器
        我們已經有很多寫好的過濾器了。下表列出了所有現有的過濾器,並在他們的用途方面進行簡要說明。



選擇事件重寫
        你可以對 IoAdapter 重寫以取代直接實現 IoFilter 的做法。除非重寫,否則所有接收到的事件將被直接轉發給下一個過濾器。
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicclass MyFilter extends
     IoFilterAdapter {  
  2.     @Override
  3.     publicvoid sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {  
  4.         // Some logic here...
  5.         nextFilter.sessionOpened(session);  
  6.         // Some other logic here...
  7.     }  
  8. }  

轉換寫請求
        如果你要通過 IoSession.write() 對一個連入的寫請求進行轉換,事情可能會變得相當棘手。例如,假設你的過濾器在一個 HighLevelMessage 物件呼叫 IoSession.write() 時將 HighLevelMessage 轉換為 LowLevelMessage。你可以在你的過濾器的 filterWrite() 方法裡新增適當的轉換程式碼並認為一切就這樣了。但是你也需要注意 messageSent 事件,因為一個 IoHandler 或者任何之後的過濾器會期望 messageSent() 方法以 HighLevelMessage 作為引數呼叫,因為讓呼叫者在寫 HighLevelMessage 的時候被通知到 HighLevelMessage 已傳送是不合理的。因此,如果你的過濾器負責轉換時你最好同時實現 filterWrite() 和 messageSent()。

        另外還要注意的是,你仍舊需要實現類似的機制,即使輸入物件和輸出物件的型別是一樣的,因為 IoSession.write() 的呼叫者期望它的 messageSent() 處理器方法有一個具體物件。
        假定你在實現一個將字串轉換為字元陣列的過濾器。你的過濾器的 filterWrite() 將會類似於:
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicvoid filterWrite(NextFilter nextFilter, IoSession session, WriteRequest request) {  
  2.     nextFilter.filterWrite(  
  3.         session, new DefaultWriteRequest(  
  4.                 ((String) request.getMessage()).toCharArray(), request.getFuture(), request.getDestination()));  
  5. }  

        現在我們需要在 messageSent() 中做相反的事情:
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicvoid messageSent(NextFilter nextFilter, IoSession session, Object message) {  
  2.     nextFilter.messageSent(session, new String((char[]) message));  
  3. }  

        字串到位元組快取的轉換怎麼樣?這樣我們會更加高效,我們不在需要重建原始訊息 (字串)。但是,這比前面的例子複雜:
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicvoid filterWrite(NextFilter nextFilter, IoSession session, WriteRequest request) {  
  2.     String m = (String) request.getMessage();  
  3.     ByteBuffer newBuffer = new MyByteBuffer(m, ByteBuffer.wrap(m.getBytes());  
  4.     nextFilter.filterWrite(  
  5.             session, new WriteRequest(newBuffer, request.getFuture(), request.getDestination()));  
  6. }  
  7. publicvoid messageSent(NextFilter nextFilter, IoSession session, Object message) {  
  8.     if (message instanceof MyByteBuffer) {  
  9.         nextFilter.messageSent(session, ((MyByteBuffer) message).originalValue);  
  10.     } else {  
  11.         nextFilter.messageSent(session, message);  
  12.     }  
  13. }  
  14. privatestaticclass MyByteBuffer extends ByteBufferProxy {  
  15.     privatefinal Object originalValue;  
  16.     private MyByteBuffer(Object originalValue, ByteBuffer encodedValue) {  
  17.         super(encodedValue);  
  18.         this.originalValue = originalValue;  
  19.     }  
  20. }  

        如果你使用的是 MINA 2.0,這將跟 1.0 和 1.1 有所不同。同時也參考一下 CompressionFilter 和 RequestResponseFilter
過濾 sessionCreated 事件時須謹慎
        sessionCreated 是一個特殊事件,它必須在 I/O 處理程式中執行 (參考 執行緒模型的配置)。決不允許將 sessionCreated 事件轉發給其他執行緒。
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicvoid sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {  
  2.     // ...
  3.     nextFilter.sessionCreated(session);  
  4. }  
  5. // DON'T DO THIS!
  6. publicvoid sessionCreated(final NextFilter nextFilter, final IoSession session) throws Exception {  
  7.     Executor executor = ...;  
  8.     executor.execute(new Runnable() {  
  9.         nextFilter.sessionCreated(session);  
  10.         });  
  11.     }  

提防空快取!
        在一些案例中 MINA 使用了一個空的緩衝區作為一個內部訊號。空快取有時會成為一個問題,因為它可能會造成各種各樣的異常,比如 IndexOutOfBoundsException。這裡我們介紹如何避免類似於這種難以預料的情況。
        ProtocolCodecFilter 使用了一個空快取 (比如 buf.hasRemaining() = 0) 來標記訊息的結束部分。如果你的過濾器放在 ProtocolCodecFilter 之前,如果你的過濾器實現在快取為空時能丟擲一個異常的話,請確認你的過濾器將空快取轉發給了下一個過濾器:
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicvoid messageSent(NextFilter nextFilter, IoSession session, Object message) {  
  2.     if (message instanceof ByteBuffer && !((ByteBuffer) message).hasRemaining()) {  
  3.         nextFilter.messageSent(nextFilter, session, message);  
  4.         return;  
  5.     }  
  6.     ...  
  7. }  
  8. publicvoid filterWrite(NextFilter nextFilter, IoSession session, WriteRequest request) {  
  9.     Object message = request.getMessage();  
  10.     if (message instanceof ByteBuffer && !((ByteBuffer) message).hasRemaining()) {  
  11.         nextFilter.filterWrite(nextFilter, session, request);  
  12.         return;  
  13.     }  
  14.     ...  
  15. }  

        這樣的話,我們是否總是要為每個過濾器插入 if 塊?幸運的是,不需要。這是處理空快取的黃金法則:
  • 如果你的過濾器及時在快取為空時也能正常工作,你就完全不需要新增 if 塊了
  • 如果你的過濾器放在 ProtocolCodecFilter 之後,你也不需要新增 if 塊
  • 除此之外的話你就需要 if 塊了
        如果你需要加 if 塊,記著你不總是需要遵循上面例子所講的。你可以在任何地方檢查快取是否為空,只要你的過濾器不會丟擲異常。譯者注:之所以如此這般謹慎處理,是因為一旦在過濾器中丟擲異常,當前執行緒即消亡,負責該項業務處理的邏輯將會出席問題,因為後續過濾器無法對其進行繼續處理。
原文連結:http://mina.apache.org/mina-project/userguide/ch5-filters/ch5-filters.html