PC的blog

          Finding... Thinking... Solving...

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            9 Posts :: 0 Stories :: 54 Comments :: 0 Trackbacks

          2008年8月4日 #

          本文緊接使用重構移除丑陋的if else代碼(4)

          上篇文章談到如何能夠徹底把這個switch也移除掉呢?很簡單,我們只需要在getSystemStatePerformer()方法被調用之前先創建所有 performer匿名類的實例,然后在該方法被調用時直接返回對應的實力。 如何具體實現呢? 用Map, 請看代碼:

          package de.jingge.refactoring;

           

          import static de.jingge.refactoring.SystemState.*;

          import java.awt.Image;

          import java.awt.image.BufferedImage;

          import java.lang.reflect.Method;

          import java.util.Collections;

          import java.util.HashMap;

          import java.util.Map;

           

          /**

           *

           * 
          @author gejing@gmail.com

           
          */

          public class SystemStatePerformerFactory {

           

          private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();

             

              
          private Map<SystemState, SystemStatePerformer> performers;

           

              
          private SystemStatePerformerFactory() {

          }

           

              
          public static SystemStatePerformerFactory getInstance() {

                  
          return INSTANCE;

              }

             

              
          private synchronized Map<SystemState, SystemStatePerformer> getPerformers()

                      
          throws Exception {

                  
          if (performers == null) {

                      performers 
          = new HashMap<SystemState, SystemStatePerformer>();

                      
          // call all @FactoryMethod using reflection

                      
          for (Method m : getClass().getDeclaredMethods()) {

                          
          if (m.getAnnotation(FactoryMethod.class!= null) {

                              SystemStatePerformer p 
          = (SystemStatePerformer) m.invoke(

                                      
          thisnew Object[]{});

                              performers.put(p.getState(), p);

                          }

                      }

                      
          // make it readonly

                      performers 
          = Collections.unmodifiableMap(performers);

                  }

                  
          return performers;

              }

           

              
          public SystemStatePerformer getSystemStatePerformer(SystemState state) throws Exception{

                  
          return getPerformers().get(state);

              }

           

          @FactoryMethod

              
          private SystemStatePerformer createLoggedInPerformer() {

                  
          return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging in is successful,

                          
          // for example: show welcome dialog, open the last edit document, etc.

                      }

                  };

              }

           

          @FactoryMethod

              
          private SystemStatePerformer createLoggedOutPerformer() {

                  
          return new SystemStatePerformer(LOGGEDOUT, getImage("loggedout.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging out is successful,

                          
          // for example: free used resource, dispose GUI components, etc.            }

                      }

                  };

              }

           

          @FactoryMethod

              
          private SystemStatePerformer createIdlePerformer() {

                  
          return new SystemStatePerformer(IDLE, getImage("idle.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after the user is idle,

                          
          // for example: save the application state temporarily, lock the application, etc.

                      }

                  };

              }

           

              
          private Image getImage(String string) {

                  
          return new BufferedImage(1010, BufferedImage.TYPE_4BYTE_ABGR);

              }

          }

          從代碼中可以看出,當getPerformers()方法被第一次調用時,我們會為每一個performer匿名類創建一個實例,并且將它們納入Map的管 理之中,以后每次調用的時候,直接從Map里面提取對應某個狀態的performer就可以了, switch可以舍棄了。 @FactoryMethod這個注釋是我自己寫的,使用它主要是為了避免每次新增加一個create***Performer()方法后,都必須修改 getSystemStatePerformer()。

          @FactoryMethod的代碼如下:

          package de.jingge.refactoring;

           

          import java.lang.annotation.ElementType;

          import java.lang.annotation.Retention;

          import java.lang.annotation.RetentionPolicy;

          import java.lang.annotation.Target;

           


          @Retention(RetentionPolicy.RUNTIME)

          @Target({ElementType.METHOD})

          public @interface FactoryMethod {


          }

          到這里整個重構已經結束了, 我們已經將if else, switch完全從代碼里剔除了。

          讀過Refactoring to Patterns這本書的朋友可能會覺得,這里所作的一些和書中第七章最后一節Replace Conditional Dispatcher with Command完全一樣。 Well,第一眼看上去確實很像,但是看完我寫的所有代碼后,再仔細想一想,兩者還是有區別的(Refactoring to Patterns這本書寫的非常好,對此書,我可以說是愛不釋手,還曾經寫過一篇書評。事實上,我這篇文章正式基于這本書的):

          1. Factory + annonymous類而不是每一個狀態一個具體的實體類。

              這樣處理問題, 類的數量大大減少,類關聯的復雜程度也大大減少,維護起來很方便。

          2. performer并不單單是一個command,它擁有狀態,并且可以處理更多的邏輯。


          全文完。
          posted @ 2008-08-04 03:48 polygoncell 閱讀(4620) | 評論 (37)編輯 收藏

          本文緊接使用重構移除丑陋的if else代碼(3)

          OK, 到目前為止,所有的邏輯代碼已經從SystemManager重構到了SystemStatePerformer。下一步應該繼續重構SystemManager, 將SystemState替換為performer:

          1, 使用IDE的重構功能,將變量SystemState改為SystemStatePerformer

          2. 在updateState()方法中調用SystemStatePerformerFactory

          3. 在測試代碼里面,調用manager.statePerformer.getState()

          重構后的代碼如下:

          package de.jingge.refactoring;

           

          import static de.jingge.refactoring.SystemState.*;


          public class SystemManager {

           

              SystemStatePerformer statePerformer;

           

              
          public void login() {

                  
          // call service#login()

                  updateState(LOGGEDIN);

              }

           

              
          public void logout() {

                  
          // call service#logout()

                  updateState(LOGGEDOUT);

              }

           

              
          public void idle() {

                  
          // call some other services

                  updateState(IDLE);

              }

           

              
          public void updateState(SystemState state) {

                  
          this.statePerformer = SystemStatePerformerFactory.getInstance()

                          getSystemStatePerformer(state);

                  statePerformer.perform();

              }

          }

          可以看到if else已經消失了。


          測試代碼也要做相應修改:
          package de.jingge.refactoring;

           

          import org.junit.AfterClass;

          import org.junit.BeforeClass;

          import org.junit.Test;

          import static org.junit.Assert.*;

          import static de.jingge.refactoring.SystemState.*;


          public class SystemManagerTest {

              
          private static SystemManager manager;

              @BeforeClass
              
          public static void setUpClass() throws Exception {

                  manager 
          = new SystemManager();

                  
          // add some service mock objects

              }

              @AfterClass
              
          public static void tearDownClass() throws Exception {

              }

              @Test
              
          public void login() {

                  manager.login();

                  assertEquals(manager.statePerformer.getState(), LOGGEDIN);

              }

              @Test
              
          public void logout() {

                  manager.logout();

                  assertEquals(manager.statePerformer.getState(), LOGGEDOUT);

              }

              @Test
              
          public void idle() {

                  manager.idle();

                  assertEquals(manager.statePerformer.getState(), IDLE);

              }

          }

          到這里重構已經差不多完成了,代碼已經更加面向對象了。這里還有一個小問題,在factory里面還有一個switch,這個和if else其實是沒有本質區別的,也就是說if else并沒有被完全移除掉。


          那么如何能夠徹底把這個switch也移除掉呢?很簡單,我們只需要在getSystemStatePerformer()方法被調用之前先創建所有 performer匿名類的實例,然后在該方法被調用時直接返回對應的實力。 那么具體如何實現呢,請看下一篇文章使用重構移除丑陋的if else代碼(5)
          posted @ 2008-08-04 03:08 polygoncell 閱讀(1935) | 評論 (1)編輯 收藏

          本文緊接使用重構移除丑陋的if else代碼(2)

          移除if else

          首先仔細觀察一 下updateState()方法,我們會發現,導致該方法內存在大量if else的原因是它的參數僅僅是一個enum。由于enum本身并不含有任何邏輯代碼,因此導致處理enum的方法需要使用if else來分析enum然后調用相應的邏輯。明白了這個道理之后,重構的方向就明了了。簡單的說,我們需要要將方法參數由enum替換成一個更加強壯的抽 象類,每一個繼承該類的子類將具體負責處理一個enum實例,之后再將updateState()方法中相應的邏輯代碼轉移到這些子類中。這樣處理之后, 令人討厭的if else就會消失了。


          我們將這個替換enum的抽象類命名為SystemStatePerformer,代碼如下:

          package de.jingge.refactoring;

           

          import java.awt.Image;


          public abstract class SystemStatePerformer {

              
          private final SystemState state;

              
          private Image image;

              
          public SystemStatePerformer(SystemState state, Image image) {

                  
          this.state = state;

                  
          this.image = image;

              }

              
          public SystemState getState() {

                  
          return state;

              }

              
          public Image getImage() {

                  
          return image;

              }
              
              
          public abstract void perform();

          }

          從代碼中可以看出,每 一個performer都含義有一個SystemState,這個SystemState屬性,將只能通過構建器映射方式射入一個performer的對 象實例。換句話說SystemState只是一個只讀屬性,而且每一個performer實體類都只負責處理一個enum的實例(下面馬上會解釋如何實現 的)。這里使用的Image作為一個例子,它表示用戶的每一個狀態都可以使用一個圖標來表示。performer()方法將負責處理具體的邏輯。這個 SystemStatePerformer的實體子類可以引用任何類型的對象,然后在perform()方法里面進行調用。




          下 一步就是編寫SystemStatePerformer的實體子類。我首先想到的是為每一個enum實例編寫一個實際的子類,理論上來說是沒問題的,但是 這樣做必須編寫一大堆的子類,不便于管理。所以我決定使用Factory + annonymous classes來構建具體的實體子類,讓Factory來管理所有的實體子類。 代碼如下:

          package de.jingge.refactoring;

           

          import static de.jingge.refactoring.SystemState.*;

          import java.awt.Image;

          import java.awt.image.BufferedImage;


          public class SystemStatePerformerFactory {

           

          private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();

             

              
          private SystemStatePerformerFactory() {

          }

           

              
          public static SystemStatePerformer getSystemStatePerformer(SystemState state) {

                  
          switch (state) {

                      
          case LOGGEDIN:

                          
          return createLoggedInPerformer();

                      
          case IDLE:

                          
          return createIdlePerformer();

                      
          case LOGGEDOUT:

                          
          return createLoggedOutPerformer();

                      
          default:

                          
          throw new IllegalAccessError("Unkonw status");

                  }

              }

           

              
          private static SystemStatePerformer createLoggedInPerformer() {

                  
          return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging in is successful,

                          
          // for example: show welcome dialog, open the last edit document, etc.

                      }

                  };

              }

           

              
          private static SystemStatePerformer createLoggedOutPerformer() {

                  
          return new SystemStatePerformer(LOGGEDOUT, getImage("loggedout.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after logging out is successful,

                          
          // for example: free used resource, dispose GUI components, etc.            }

                      }

                  };

              }

           

              
          private static SystemStatePerformer createIdlePerformer() {

                  
          return new SystemStatePerformer(IDLE, getImage("idle.gif")) {

           

                      @Override

                      
          public void perform() {

                          
          // do something after the user is idle,

                          
          // for example: save the application state temporarily, lock the application, etc.

                      }

                  };

              }

           

              
          private static Image getImage(String string) {

                  
          return new BufferedImage(1010, BufferedImage.TYPE_4BYTE_ABGR);

              }

          }

          從 代碼中可以看到,針對每一個enum狀態都有一個創建performer的方法,該方法返回一個匿名類。邏輯代碼將會被轉移至個匿名類的 perform()方法之內。整個Factory只有一個公開的方 法:getSystemStatePerformer(SystemState),SystemManager可以調用這個方法來獲得相應的 Performer實例。


          在 這篇文章中,我希望專屬于if else的問題。對于其他設計方面的問題,我采取的態度是能省略就省略。實際開發中,還有有很多問題需要處理,例如,使用static方法會導致系統的可 測試性下降,在實際開發中應該盡量避免,解決這類問題的方法之一是使用DI框架,例如Google Guice。

          下一篇文章使用重構移除丑陋的if else代碼(4)繼續講解。
          posted @ 2008-08-04 02:54 polygoncell 閱讀(2244) | 評論 (4)編輯 收藏

          本文緊接使用重構移除丑陋的if else代碼(1)

          使用Enum替換int常量

          這一步比較簡單,先創建一個enum類:

          package de.jingge.refactoring;

          public enum SystemState {

              LOGGEDIN,

              LOGGEDOUT,

              IDLE;

          }


          然后開始重構SystemManager, 使用SystemState代替SystemManager里的int狀態:

             1. 添加 import static de.jingge.refactoring.SystemState.*;
             2. 刪除所有的integer常量  
             3. 將變量state的類型改為SystemState.

          代碼如下:



          package de.jingge.refactoring;

          import static de.jingge.refactoring.SystemState.*;

          public class SystemManager {

              SystemState state;

              
          public void login() {
                  
          // call service#login()
                  updateState(LOGGEDIN);
              }
             
              
          public void logout() {
                  
          // call service#logout()
                  updateState(LOGGEDOUT);
              }
             
              
          public void idle() {
                  
          // call some other services
                  updateState(IDLE);
              }
             
              
          public void updateState(SystemState state) {
                  
          if (state == LOGGEDIN) {
                      
          // do something after logging in is successful,
                      
          // for example: show welcome dialog, open the last edit document, etc.
                  } else if (state == LOGGEDOUT) {
                      
          // do something after logging out is successful,
                      
          // for example: free used resource, dispose GUI components, etc.
                  } else if (state == IDLE) {
                      
          // do something after the user is idle,
                      
          // for example: save the application state temporarily, lock the application, etc.
                  } else {
                      
          throw new IllegalArgumentException("unknown state");
                  }
                  
          this.state = state;
              }
          }

          然后重構測試類:

          1.    添加import static de.jingge.refactoring.SystemState.*;
          2.    刪除所有常量前引用的SystemManager.

          package de.jingge.refactoring;

          import org.junit.AfterClass;
          import org.junit.BeforeClass;
          import org.junit.Test;
          import static org.junit.Assert.*;
          import static de.jingge.refactoring.SystemState.*;


          public class SystemManagerTest {

              
          private static SystemManager manager;

              @BeforeClass
              
          public static void setUpClass() throws Exception {
                  manager 
          = new SystemManager();
                  
          // add some service mock objects
              }
             
              @AfterClass
              
          public static void tearDownClass() throws Exception {
              }
             
              @Test
              
          public void login() {
                  manager.login();
                  assertEquals(manager.state, LOGGEDIN);
              }
            
              @Test
              
          public void logout() {
                  manager.logout();
                  assertEquals(manager.state, LOGGEDOUT);
              }

              @Test
              
          public void idle() {
                  manager.idle();
                  assertEquals(manager.state, IDLE);
              }
          }


          運行這個測試類->通過

          下一篇文章使用重構移除丑陋的if else代碼(3)開始處理if else hell
          posted @ 2008-08-04 02:45 polygoncell 閱讀(2092) | 評論 (0)編輯 收藏

          我們知道因為編程語言的限制,歷史遺留下來的系統總是有很多的毛病,不夠面向對象,尤其是很多系統濫用if else。我曾經見過一個項目,大家基本上就是寫一個方法,然后在里面if else套if esle得嵌套了好幾層,難看就不必說了,這種代碼根本就沒法維護。

          今天我就使用從實際項目中提煉出來的例子來講解一下如何將這類代碼變得更加面向對象 - 重構成模式并且添加測試代碼,

          先來看一個丑陋的類:

          package de.jingge.refactoring;

           

          public class SystemManager {

           

              
          public static final int LOGGEDIN = 0;

              
          public static final int LOGGEDOUT = 1;

              
          public static final int IDLE = 2;

              
          int state;

           

              
          public void login() {

                  
          // call service#login()

                  updateState(LOGGEDIN);

              }

             

              
          public void logout() {

                  
          // call service#logout()

                  updateState(LOGGEDOUT);

              }

             

              
          public void idle() {

                  
          // call some other services

                  updateState(IDLE);

              }

              
          public void updateState(int state) {

                  
          if (state == LOGGEDIN) {

                      
          // do something after logging in is successful,

                      
          // for example: show welcome dialog, open the last edit document, etc.

                  } 
          else if (state == LOGGEDOUT) {

                      
          // do something after logging out is successful,

                      
          // for example: free used resource, dispose GUI components, etc.

                  } 
          else if (state == IDLE) {

                      
          // do something after the user is idle,

                      
          // for example: save the application state temporarily, lock the application, etc.

                  } 
          else {

                      
          throw new IllegalArgumentException("unknown state");

                  }

                  
          this.state = state;

              }

          }


          這里我們展示了一個 SystemManager,它負責處理用戶在系統中的狀態:登入(logged in),登出(logged out),以及空閑(idle)。從代碼中可以看到,這個類用了int來定義狀態并且因此導致了updatteState()方法里面出現大量if else。從目前看來這些if else是無法避免的,應為這個類需要針對不同的狀態作出反應。隨著狀態的增加,if else的數量也會繼續增加。這個解決方案顯然很差。

          那么怎么樣才能讓這個類更加地面向對象呢?

          在處理面向對象之前,我們首先要編寫一個測試類,這也是處理這類歷史遺留下來代碼所必需做的第一步,只有在測試代碼的保護下,我們才能放心大膽地進行重構。

          初步的測試代碼如下:

          package de.jingge.refactoring;

          import org.junit.AfterClass;
          import org.junit.BeforeClass;
          import org.junit.Test;
          import static org.junit.Assert.*;



          public class SystemManagerTest {
              
          private static SystemManager manager;

              @BeforeClass
              
          public static void setUpClass() throws Exception {
                  manager 
          = new SystemManager();
                  
          // add some service mock objects
              }

              @AfterClass
              
          public static void tearDownClass() throws Exception {

              }

              @Test
              
          public void login() {

                  manager.login();

                  assertEquals(manager.state, SystemManager.LOGGEDIN);

              }

              @Test
              
          public void logout() {

                  manager.logout();

                  assertEquals(manager.state, SystemManager.LOGGEDOUT);

              }

              @Test
              
          public void idle() {

                  manager.idle();

                  assertEquals(manager.state, SystemManager.IDLE);

              }

          }

          運行測試代碼->通過。

          在下一篇文章我們將正式開始重構。地址:使用重構移除丑陋的if else代碼(2)
          posted @ 2008-08-04 02:36 polygoncell 閱讀(2700) | 評論 (3)編輯 收藏

          2008年7月29日 #

          陸陸續續又看了一些技術書,加上最近工作任務繁重,實在沒時間寫技術類的博文,計劃斷斷續續寫一些書的閱讀評論。今天先寫一本,

          最近閱讀的書并不都是最新的,因為些書的確是需要花時間反復研讀,仔細思考的,例如關于設計模式的書。

          Refactoring to Patterns就是這樣一本書,一本相對來說不算太新的書,一本關于設計模式的書,一本讓人從新開始思考模式的書。我本人強烈推舉大家閱讀。

          這本書剛出來的時候,我就一直想好好靜下心來讀一讀,無奈工作忙碌,一直沒有找到機會。而且說實話,自己對設計模式也已經浸淫數載,大部分模式都已經在實 際項目中歷練過,不說爐火純青,也算得上是熟能生巧。雖然知道這是一本好書,但是心里依然會泛起陣陣漣漪:just another design pattern book,給我一天時間,我就能把它拿下。沒想到從開始讀到現在已經2個多月了,現在依然會抽時間出來翻看某個章節,然后結合實際問題仔細思考一番。說實 話,設計模式真的是個好東西,是前輩經驗的積累,但是當我們熟練掌握了各種模式之后,就會遇到兩個瓶頸性質的問題:

          1. 如何將各類模式融匯貫通。感覺就像是武俠里面的如何將任督二脈打通,功夫再好,任督二脈不通也非高手,不同的模式相互作用會產生不同的結果,不同的模式組合會產生不同的結構,或相互補充相互促進,或互相影響互相抵制。如何選擇,是個難題。

          2. 如何避免過度設計。模式用熟的朋友可能會有這種感覺,編程的時候會不知不覺中使用模式進行開發,模式再好,泛濫使用,過猶不及。好像武功一般,招式繁多,固然耍起來好看,但是高手最后的境界往往是無招勝有招,只要能目的達到就行了。

          第一個問題由于模式之間的組合千變萬化,而且很多需要結合實際問題進行考慮,大家只能是在實際項目開發中慢慢體會,慢慢積累經驗。或許再過幾年會有某個人或者某些人把這些經驗收集整理,編排出書。

          今天主要想說說第二個問題:過度設計。 Refactoring to Patterns這本書就是幫助我們盡量避免過度設計的,這也是我推薦大家看這本書的初衷。

          事實上,這本書完全可以看成是極限編程系列里面的一部巨頭著作,其他的重要著作包括TDD,Refactoring,和continuous integration,合起來應該稱為XP四大金剛。

          使用XP開發強調keep it simple,在編碼的時候首先用最簡單的辦法實現功能(當然最好是測試驅動,我會在評論下面一本書是繼續談測試驅動),然后在測試代碼的保護下對初級代 碼進行大刀闊斧地重構,這時候這些代碼自然最好能夠重構成設計模式,因為針對某些特定的問題,模式基本上是最佳解決方案。這個時候就大家就需要了解如何將 現有的代碼重構成為標準的設計模式代碼,說的嚴重點,這一步關系到整個代碼的質量,直至引申到真個項目的質量。這本書就針對這個關鍵步驟進行了講解,是所 有采用XP方法的開發團隊的必修課之一。


          至于書的內容我就盡量簡單概括一下:本書基本上覆蓋了重構項目中會遇到的大部分問題,并且針對這些問題提供了實例講解和具體的解決方案。這些方案不單單適 用于使用TDD開發的項目,它們更適用于那些希望通過重構改善現有代碼質量的項目。當然,重構前,千萬別嘗試避開編寫測試代碼,要牢記,沒有測試代碼保 護,最好不要對代碼做大手術。書中的很多例子都值得大家讀完以后結合自己的實際項目仔細思考一番。我個人很喜歡第7章,已經在項目中采用了書中提到的很多 解決方案,并且對于某些實際例子,開始嘗試優化書中的方案,希望以后能夠抽出時間來寫一篇詳細的博文。
          posted @ 2008-07-29 19:17 polygoncell 閱讀(1400) | 評論 (0)編輯 收藏

          這段時間真是忙得要死,一方面要開發公司項目的系統框架,要將項目分成不同的子項目,編寫核心代碼;另一方面要將極限編程(XP)引入團隊開發,部署各類 XP需要的服務例如subversion啦,ant+ivy啦,Hudson啦等等。順便說句題外話,ubuntu還真是不是一般的好用,建議有能力的全 部轉到ubuntu上去開發。

          我目前開發的這個框架的客戶端是具肥的客戶端,也就是Swing客戶端了。Swing應用相對于Web應用有很多優勢,因為它更肥。數據驗證就是 其中一個。當然現在的Web應用通過使用Ajax也要比以前強很多了,但是還是避免不了在驗證數據時向服務段發出請求,至少你無法避免驗證結果從Web服 務器傳輸到用戶瀏覽器上這段過程。而Swing這類肥客戶端可以實現完全在本地對數據進行驗證,甚至可以斷網繼續工作(這也是Web應用目前在研發的一個 重要課題)。

          前段時間開發出了一個可以應用于所有Swing應用的通用數據驗證模塊,發現它在項目中使用后,對于普通的數據驗證,程序員幾乎不需要編碼,效率提高了不少,就寫了一篇博文拿出來和大家分享。原文是用英文寫的,在這里:http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-application.html。英文好的朋友可以直接去那里看。

          編寫這個模塊使用了很多不同的開源框架和類庫,其中很重要的一個就是JXLayer。文章寫完后,我就跑去邀請JXLayer的作者Alexp來指點一下,然后就在我的文章后面開始了一段討論,挺有意思的,他不愧為是Swing team里面的牛人啊!厲害啊!呵呵。

          ok,回到今天這篇文章的正題。今天的主要目的是將我的英文博文翻譯成中文(自己的文章,我就不逐字逐句翻譯了,意思到了就行了,可能還會隨興展 開一番討論)在這里展示給大家,與大家分享開發經驗,希望大家能夠從中獲益,也希望能夠以文會友,廣交朋友。廢話少說,切入正題。

          數據驗證(Validation)一直是軟件開發中非常重要的一環,有了它,你的系統會讓客戶感到更加友善,同時你的系統也得到了一定程度的保 護。一般來說,數據驗證既可以在客戶端也可以在服務端。默認的JSF數據驗證就是在服務端,數據只能在被提交以后才能夠被驗證,然后把錯誤信息傳遞回用戶 的瀏覽器。后來大規模使用Ajax后,基本可以實現對修改的數據“即時”驗證,注意這里是個打了引號的即時,數據事實上還是要在瀏覽器和服務端之間進行傳 遞的,只不過Ajax將這種傳遞改為隱式了而已,理論上并沒有真正實現(斷網)即時驗證。而在Swing應用上就能夠達成這種愿望。

          事實上,開發Swing應用時,數據驗證一直比較棘手,需要手工編碼的地方太多,效率不高。后來出了JGoodies Validation 結合JGoodies binding后,好了一些。這個JGoodies Validation既可以實現model層面的驗證,也可以實現Bean層面的驗證,但是多年使用下來,發現其實它比較適用于中小項目,而且要編寫的代 碼其實一點不比自己手動編寫的少。

          JGoodies流行了一段時間后,sun開始推出自己的bean綁定方案:beansbinding(JSR 295),我個人感覺要比JGoodies binding好用(JGoodies的作者Karsten也在專家組里,這個人我以前和他一起共事過,我的msn space里面還有跟他的合影,絕對是Swing界的牛人)。這個beansbinding也提供數據驗證,但是它的這個數據驗證只是在target被改 動后,數據被同步回source之前才會起作用,使用起來局限性比較大,而且編碼量也不小。

          由于目前絕大部分項目是基于POJO的,Hibernate validator已經提供了一個很好的數據驗證框架,我們完全沒必要再重復發明輪子,我們應該努力站在巨人的肩膀上,這樣我們才能站得更高,看得更遠。 于是我考慮結合beansbinding和Hibernate Validator開發數據驗證。還有一個重要的問題,那就是數據錯誤的時候,需要在用戶界面上展示相應的信息,例如Error icon和錯誤提示,這部分我考慮使用JXLayer。

          你可以在如下鏈接中找到相關框架的具體信息:

          1. Hibernate Validator: http://www.hibernate.org/hib_docs/validator/reference/en/html_single/
          2. Beansbinding: https://beansbinding.dev.java.net/
          3. JXlayer: http://weblogs.java.net/blog/alexfromsun/

          閱讀這篇文章,不需要你熟悉這些類庫,不過了解這些類庫能夠幫助你更好地理解這篇文章。

          我的這個通用模塊是參考JXLayer里面的一個demo類TextValidationDemo的,這個JXlayer是由Alexander Potochkin開發的,我很喜歡,使用起來很順手,強烈推薦使用。

          下面開始介紹代碼。首先是建立一個java項目,對于這個小項目,我使用netbeans。這里說句題外話,中型和大型的Swing應用,建議最 好還是不要使用netbeans的GUI Builder,一方面它生成的代碼超級爛,另一方面很難測試。目前市面上有很多好用的layout的框架,例如 JGoodies form和MigLayout,開發效率絕對不比netbeans的GUI builder差,你還不需要面對令人頭疼的機器成的代碼。

          項目創建好后,加入類庫:



          然后寫一個persistence bean:
          package de.jingge.domain;

          import javax.persistence.Entity;
          import javax.persistence.GeneratedValue;
          import javax.persistence.GenerationType;
          import javax.persistence.Id;
          import org.hibernate.validator.Length;
          import org.hibernate.validator.NotEmpty;


          @Entity
          public class Country extends AbstractBean {

          private static final long serialVersionUID = 5341382564159667599L;
          public static final String PROPERTYNAME_NAME = "name";
          public static final String PROPERTYNAME_CODE = "code";
          private String name;
          private String code;
          private Long id;

          public Country() {
          }

          public Country(String code, String name) {
              
          super();
              setCode(code);
              setName(name);
          }

          @Id
          @GeneratedValue(strategy 
          = GenerationType.AUTO)
          public Long getId() {
              
          return id;
          }

          public void setId(Long id) {
              
          this.id = id;
          }

          @NotEmpty
          public String getName() {
              
          return name;
          }

          public void setName(String name) {
              firePropertyChange(PROPERTYNAME_NAME, 
          this.name, this.name = name);
          }

          @Length(min
          =2, max= 2, message="Code length must be 2")
          @NotEmpty
          public String getCode() {
              
          return code;
          }

          public void setCode(String code) {
              firePropertyChange(PROPERTYNAME_CODE, 
          this.code, this.code = code);
          }
          }


          這里我為了強調可以在Swing客戶端直接使用和驗證persistence bean,故意寫了一個persistence bean,實際應用中,這個類只需要是一個pojo就行了。

          這個Country類代表一個國家,它有兩個屬性,code和name,我給他們分別加上個各自的驗證限制。code不能為空,且必須正好是兩個 字符,例如CN,DE,US。name不能為空。這些annotaion均出自Hibernate Validator。那個父類AbstractBean出自SwingX類庫,我們的Country類繼承了它之后就可以支持property change event了。

          ok, 下面可以開始編寫這個模塊的核心代碼了。前面說過,我會使用JXlayer。使用它的好處是:所有JXlayer的painting event都會被轉到UI類來,我們只需要編寫一個集成Hibernate Validator的UI類就可以了,我稱這個類為HibernateValidationUI,代碼如下:

          package de.jingge.view;

          import java.awt.Graphics2D;
          import java.awt.image.BufferedImage;

          import javax.swing.BorderFactory;
          import javax.swing.JComponent;
          import javax.swing.text.JTextComponent;

          import org.hibernate.validator.ClassValidator;
          import org.hibernate.validator.InvalidValue;
          import org.jdesktop.beansbinding.ELProperty;
          import org.jdesktop.beansbinding.PropertyStateEvent;
          import org.jdesktop.beansbinding.PropertyStateListener;
          import org.jdesktop.jxlayer.JXLayer;
          import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

          /**
          * Header:
          * Description: A layerUI which will validate the referenced property value of
          * the object each time when the paint() method is called.

          * The value of the given object property will be observed.
          * Note: This UI works only with {
          @link JXLayer}. Any change of the property
          * will force repainting the UI. The work process looks like: property changed ->
          * jxlayer will be repainted -> the paint() method of this UI will be called.
          * The logic of validation will be handled by the Hibernate validator
          * framework.
          *
          */
          public class HibernateValidationUI extends AbstractLayerUI<jTextComponent> {

          private Object object;
          private String propertyName;
          private ClassValidator validator;
          private ELProperty elProperty;
          private PropertyStateListener propertyChangeHandler;

          public HibernateValidationUI(Object obj, String propertyName) {
              
          this.object = obj;
              
          this.propertyName = propertyName;
              propertyChangeHandler 
          = new PropertyChangeHandler();
              validator 
          = new ClassValidator(obj.getClass());

              elProperty 
          = ELProperty.create("${" + propertyName + "}");
          }

          public void installUI(JComponent c) {
              
          super.installUI(c);
              elProperty.addPropertyStateListener(object, propertyChangeHandler);
          }

          public void uninstallUI(JComponent c) {
              
          super.uninstallUI(c);
              elProperty.removePropertyStateListener(object, propertyChangeHandler);
          }

          protected void paintLayer(Graphics2D g2, JXLayer<jTextComponent> l) {
              
          super.paintLayer(g2, l);
              InvalidValue[] validationMessages 
          = validator.getInvalidValues(object,
                      propertyName);
              
          if (validationMessages.length > 0) {
                  BufferedImage image 
          = Java2DIconFactory.createErrorIcon();
                  g2.drawImage(image, l.getWidth() 
          - image.getWidth() - 1,
                          l.getHeight() 
          - 8null);
                  l.getView().setToolTipText(validationMessages[
          0].getMessage());

                  
          return;
              }
              l.getView().setToolTipText(
          null);
          }

          boolean isValid() {
              
          return validator.getInvalidValues(object, propertyName).length == 0;
          }

          class PropertyChangeHandler implements PropertyStateListener {

              @Override
              
          public void propertyStateChanged(PropertyStateEvent pse) {
                  setDirty(
          true);
              }
          }
          }



          這個HibernateValidationUI類只有一個構建器,它接收兩個參數,一個是source object,也就是我們要修改的那個Bean類的實例,另外一個是這個bean的一個屬性,這個HibernateValidationUI就負責驗證這個屬性。

          在installUI()方法中,我們啟動對屬性變化的觀察類,而在uninstallUI()方法里面,我們需要卸載這個觀察類。

          當給定對象的屬性值發生變化時,PropertyChangeHandler的propertyStateChanged()方法就會被調用,這 個功能是通過elProperty和PropertzChangeHandler相結合來實現的。在propertyStateChangeed()方法 里UI類的方法setDirty()會被調用,該方法的調用會導致UI類的狀態變化,進而引發(re)painting,之后經過一系列的方法調用傳 遞,paintLayer(Graphics2D g2, JXLayer<jTextComponent> l)這個方法將會被調用,這個方法要做的就是我們這個數據驗證模塊的核心功能:

          1. 調用Hibernate Validator驗證該屬性。
          2. 如果數據不正確,則在GUI上顯示一個error icon,并且將錯誤信息作為tooltip展示給用戶。

          在第二點里面產生了一個問題,謝謝Alexp對我的指點。Swing team里面有一些規定,其中之一就是,在paint()方法里面最好不要改變Component的狀態,而setTooltip()方法將會改變 component的狀態,因此需要在paint()方法之外調用。我目前使用下來,還沒有發現什么嚴重的錯誤,決定暫時不改了,回頭有時間在將這個代碼 翻新一下。

          類中用到的Java2DIconFactory代碼如下:

          package de.jingge.view;

          import java.awt.Color;
          import java.awt.Graphics2D;
          import java.awt.RenderingHints;
          import java.awt.image.BufferedImage;


          public class Java2DIconFactory {

          public static BufferedImage createErrorIcon() {
              
          return createErrorIcon(78);
          }

          public static BufferedImage createErrorIcon(int width, int height) {
              BufferedImage icon 
          = new BufferedImage(width, height,
                      BufferedImage.TYPE_INT_ARGB);
              Graphics2D g2 
          = (Graphics2D) icon.getGraphics();
              g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                      RenderingHints.VALUE_ANTIALIAS_ON);
              g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                      RenderingHints.VALUE_STROKE_PURE);
              g2.setColor(Color.RED);
              g2.fillRect(
          00, width, height);
              g2.setColor(Color.WHITE);
              g2.drawLine(
          00, width, height);
              g2.drawLine(
          0, height, width, 0);
              g2.dispose();
              
          return icon;
          }
          }


          沒什么太多好解釋的,就是使用Java 2D畫一個Error icon。

          接著,我們需要編寫一個Factory類,構建一個JTextField,盡量把復雜技術封裝起來,這樣程序員開發起來可以提高效率,代碼如下:

          package de.jingge.view;

          import javax.swing.JTextField;
          import javax.swing.text.JTextComponent;
          import org.jdesktop.beansbinding.AutoBinding;
          import org.jdesktop.beansbinding.BeanProperty;
          import org.jdesktop.beansbinding.BindingGroup;
          import org.jdesktop.beansbinding.Bindings;
          import org.jdesktop.beansbinding.ELProperty;
          import org.jdesktop.jxlayer.JXLayer;
          import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;


          public class GuiComponentFactory {

          public static JXLayer<jTextComponent> createTextField(
                  BindingGroup bindingGroup, Object sourceObject,
                  String sourceProperty) {
              JTextField field 
          = new JTextField();
              AutoBinding binding 
          = Bindings.createAutoBinding(READ_WRITE,
                      sourceObject, ELProperty.create(
          "${" + sourceProperty + "}"),
                      field, BeanProperty.create(
          "text"));
              bindingGroup.addBinding(binding);
              bindingGroup.bind();
              
          return new JXLayer<jTextComponent>(field, new HibernateValidationUI(
                      sourceObject, sourceProperty));
          }
          }


          createTextField()方法主要將給定對象屬性的值與JTextField的text綁定,然后將JTextField納入到 JXLayer的管理之下。這樣一來,一旦用戶在JTextField里面修改數據,這個改變就會同步到該對象屬性上,然后就引發了前面描述的一系列邏 輯,最終改變的數據就會被Hiberante Validator加以驗證。

          最后,我們可以編寫一個Demo application來看看效果如何,代碼如下:

          package de.jingge.main;

          import de.jingge.domain.Country;
          import java.awt.BorderLayout;
          import java.awt.Dimension;
          import java.awt.Toolkit;
          import javax.swing.JFrame;
          import javax.swing.JLabel;
          import javax.swing.JPanel;
          import javax.swing.UIManager;
          import javax.swing.UnsupportedLookAndFeelException;
          import javax.swing.text.JTextComponent;
          import net.miginfocom.swing.MigLayout;
          import org.jdesktop.beansbinding.BindingGroup;
          import org.jdesktop.jxlayer.JXLayer;
          import static de.jingge.view.GuiComponentFactory.*;


          public class ValidationApplicaton {

          private BindingGroup bg;
          private Country country;
          private JXLayer<jTextComponent> codeField;
          private JXLayer<jTextComponent> nameField;

          /**
           * 
          @param args the command line arguments
           
          */
          public static void main(String[] args) {
              
          try {
                  UIManager.setLookAndFeel(
                          
          "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
              } 
          catch (UnsupportedLookAndFeelException ex) {
                  System.err.println(
                          
          "Nimbus L&F does not support. Default L&F will be used.");
              } 
          catch (ClassNotFoundException e) {
                  
          // TODO Auto-generated catch block
                  e.printStackTrace();
              } 
          catch (InstantiationException e) {
                  
          // TODO Auto-generated catch block
                  e.printStackTrace();
              } 
          catch (IllegalAccessException e) {
                  
          // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              ValidationApplicaton app 
          = new ValidationApplicaton();
              JFrame frame 
          = new JFrame("Demo Validation Application");
              frame.setPreferredSize(
          new Dimension(360150));
              frame.getContentPane().add(app.buildPanel(), BorderLayout.CENTER);
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              setCenter(frame);
              frame.setVisible(
          true);
              frame.pack();

          }

          private static void setCenter(JFrame frame) {
              Toolkit toolkit 
          = Toolkit.getDefaultToolkit();
              Dimension screenSize 
          = toolkit.getScreenSize();

              
          // Calculate the frame location
              int x = (screenSize.width - (int) frame.getPreferredSize().getWidth()) / 2;
              
          int y = (screenSize.height - (int) frame.getPreferredSize().getHeight()) / 2;

              
          // Set the new frame location
              frame.setLocation(x, y);
          }

          public ValidationApplicaton() {
              country 
          = new Country();
              bg 
          = new BindingGroup();
          }

          private JPanel buildPanel() {

              codeField 
          = createTextField(bg, country, Country.PROPERTYNAME_CODE);
              nameField 
          = createTextField(bg, country, Country.PROPERTYNAME_NAME);
              JPanel panel 
          = new JPanel(new MigLayout("",
                      
          "[50px, right]10[200px:250px:300px]""[center]"));
              panel.add(
          new JLabel("Code:"), "cell 0 0");
              panel.add(codeField, 
          "cell 1 0, w 200px:250px:300px");
              panel.add(
          new JLabel("Name:"), "cell 0 1");
              panel.add(nameField, 
          "cell 1 1, w 200px:250px:300px");
              
          return panel;
          }
          }


          這個類比較簡單了,我簡單解釋一下:

          在main()方法里面,我們創建了一個JFrame,然后放入一個JPanel

          setCenter()方法負責將窗口至于屏幕的正中間。

          在構建器里面,我們創建了Country和BindingGroup的對象實例。

          在buildPanel()方法里面,我們使用MigLayout構建了一個Panel,其中codeField和nameField對應各自的對象屬性。更多關于MigLayout的信息看這里:http://www.miglayout.com/。這也是一個例子,大家可以看到使用MigLayout開發Swing真的是非常方便。

          從這個Demo里面也可以看出,編寫好pojo后,程序員只需要調用createTextField(bg, country, Country.PROPERTYNAME_CODE); 就可以創建一個支持數據驗證的JTextField,編碼量已經可以說是最大限度的降低了。

          運行程序,你會看到:



          這個code和name的數據都不合法,用戶看到了error icon。

          將鼠標移到Text field上,你會看到:



          填好合法數據后,Error icon就不見了:



          總結:

          使用這個通用數據驗證模塊有很多好處:

          1. 如果項目使用ORM,例如Hibernate,這個方案應該是解決數據驗證的最好方案之一。
          2. 對于普通的數據驗證,例如非空,email,長度等等,程序員根本不需要編碼,只要在POJO上使用相應的Hibernate Validator annotation就可以了。
          3. 對于復雜的數據驗證,Hibernate Validator提供了很好的擴展機制,只要寫一個annotation外加一個Validator就可以了。Swing應用這邊仍然不需要編寫任何代碼。

          綜上所述,可以看出通過使用這個通用數據驗證模塊,開發效率會提高很多。
          posted @ 2008-07-29 18:27 polygoncell 閱讀(2345) | 評論 (5)編輯 收藏

          2008年3月29日 #

          在上一篇博客里,我編寫了一個UserType,實現了持久化自定義的enum類,其實那個例子中存在兩個缺點。這兩個缺點是關于如何正確使用enum以及generic的,她們已經和hibernate userType無關了,因此另起一個主題討論。

          在上一篇博客中,我的enum是這么寫的:

          public enum Status implements DescriptionID {

              ACTIVATED(
          5"This object is activated"),  
              DEACTIVATED(
          9"This object is deactivated");

              
          private Integer id;
              
          private String description;
              
          private static List<Status> list;

              
          static {
                  list 
          = new ArrayList<Status>(2);
                  list.add(ACTIVATED);
                  list.add(DEACTIVATED);
              }

              
          private Status(int statusNr, String description) {
                  
          this.id = statusNr;
                  
          this.description = description;
              }

              
          public String getDescription() {

                  
          return this.description;
              }

              
          public Integer getId() {
                  
          return id;
              }

              
          public static List<Status> getAll() {
                  
          return list;
              }

              
          public static Status findById(Integer id) {
                  
          for (Status status : getAll()) {
                      
          if (id == status.getId()) {
                          
          return status;
                      }
                  }
                  
          return null;
              }

          }

          其中兩個static方法是為了方便使用。

          缺點一:

                   所有的enum實例必須手動納入list集合中。

          解決方法:
           
                  解決方法非常簡單,是用Class類提供的方法getEnumConstants(), 代碼如下:

              public static List<Status> getAll() {
                  
          return Arrays.asList(Status.class.getEnumConstants());
              }

          我個人比較討厭數組,因此這里特意將數組轉換成List。如果你們不介意使用數組的話,getAll()方法完全可以省略。

          缺點二:

                     findById(Integer id) 方法名并不貼切,叫getEnumById(Integer id)會更好些。另外一模一樣的方法必須在每一個enum類中重復編寫,如果某個地方需要改動,那就需要改動所有相關的enum類,這是一個很明顯的bad smell。

          解決方法:

                  編寫一個util類,將邏輯轉移到util類中,getEnumById(Integer id)方法調用util類中的相關方法,代碼如下:

              public static Status getEnumById(Integer id) {
                  
          return EnumUtils.getEnum(Status.class, id);
              }

          public class EnumUtils {

              
          public static <extends DescriptionID> I getEnum(Class<I> type, int id) {
                  I[] types 
          = type.getEnumConstants();
                  
          for (I t : types) {
                      
          if (t.getId() == id)
                          
          return t;
                  }
                  return null;

              }
          }

          這里getEnum(Class<I> type, int id)方法利用Java 5的新特性generic,利用給定的Class和enum id,返回對應的enum實例。這樣處理好處很明顯,獲取enum實例的邏輯代碼只存在與util類中,日后修改十分方便。

          相關內容請參閱我寫的《Hibernate 3和Java Persistence API 程序開發從入門到精通》一書。
          posted @ 2008-03-29 00:58 polygoncell 閱讀(1160) | 評論 (0)編輯 收藏

          2008年3月21日 #

          前段時間寫了本書《Hibernate 3和Java Persistence API 程序開發從入門到精通》,書中著重介紹了在Hibernate/JPA中使用Annotation。最近有讀者來信詢問UserType,再加上最近看到有的人在項目中濫用Hibernate的user type,想在這里說幾句。

          使用UserType首先要弄清楚它的目的。大家知道Hibernate解決的主要是對象數據庫阻抗失衡的問題,也就是如何將一個或多個對象保存到一個或多個數據庫表格中。這其中有很多方法,其實大部分情況下采用@Embeddable和@Embedded就可以解決問題了,只有嵌入對象方式無法滿足要求時,或者是Hibernate默認的持久化方式無法滿足要求時,才應該考慮UserType。總之記住一個原則,不到山窮水盡,不要輕易使用UserType。還有一個要慎重考慮使用UserType的原因是:一旦采用了UserType,你的項目就脫離了JPA,而直接和Hibernate耦合在一起了。

          擴展UserType主要分為兩種:
          1. immutable
          2. mutable
          今天我先舉個immutable的例子。

          Java 5提出了一個新的enum類,JPA提供的標準方法是保存enum的name或者是ordinal。這種默認方式能夠滿足新開發的項目,但是對于一些老項目翻新并不一定適用。下面我們來看一個例子:

          public class Status {

              
          public static final int ACTIVATED = 5;
              
          public static final int DEACTIVATED = 6;
          }

          這個是在java5之前常用的常量定義方法,老項目數據庫里面已經保存了很多的5啊6的。現在要把Status改寫成enum,而且不希望修改數據庫中已有的數據,怎么做?第一反應,status enum可以這么寫:

          public enum Status {
                  ACTIVATED,
                  DEACTIVATED;
          }

          持久化enum的name屬性是肯定不用考慮了,ordinal屬性呢?這里要保存的可是5和6,而Status enum只有兩個實體,他們的ordinal只是0和1。而且項目中還會有其他很多類似的常量類需要改寫成enum,JPA的默認方式無法完成任務,這時候可以開始考慮使用UserType了。

          先定義一個接口,這樣可以使用一個UserType支持所有類似的enum:

          public interface DescriptionID {

              String getDescription();

              
          int getId();
          }

          然后改寫Status enum:

          public enum Status implements DescriptionID {

              ACTIVATED(
          5"This object is activated"),  
              DEACTIVATED(
          9"This object is deactivated");

              
          private Integer id;
              
          private String description;
              
          private static List<Status> list;

              
          static {
                  list 
          = new ArrayList<Status>(2);
                  list.add(ACTIVATED);
                  list.add(DEACTIVATED);
              }

              
          private Status(int statusNr, String description) {
                  
          this.id = statusNr;
                  
          this.description = description;
              }

              
          public String getDescription() {

                  
          return this.description;
              }

              
          public Integer getId() {
                  
          return id;
              }

              
          public static List<Status> getAll() {
                  
          return list;
              }

              
          public static Status findById(Integer id) {
                  
          for (Status status : getAll()) {
                      
          if (id == status.getId()) {
                          
          return status;
                      }
                  }
                  
          return null;
              }

          }

          注意這里每個enum都必須有兩個static方法,這些方法名必須在所有的enum中保持一致。List()方法是為了方便獲取所有的Status常量,例如在用戶界面通過ComboBox展示,findById()方法是為了通過給定Id獲得對應的Enum實例。其中findById()方法參數一定要是Integer,原因后面會講到。

          下面編寫DescriptionIDUserType:



          public class DescriptionIDUserType implements UserType, ParameterizedType {

              
          private Class enumClass;

              
          public void setParameterValues(Properties parameters) {
                  
          try {
                      enumClass 
          = ReflectHelper.classForName(parameters.getProperty("class"));
                  } 
          catch (ClassNotFoundException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
                  
              }
              
              
          public Object assemble(Serializable cached, Object arg1)
                      
          throws HibernateException {

                  
          return cached;
              }
              
              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
               
          */
              
          public Object deepCopy(Object value) throws HibernateException {
                  
          // TODO Auto-generated method stub
                  return value;
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
               
          */
              
          public Serializable disassemble(Object value) throws HibernateException {
                  
          // TODO Auto-generated method stub
                  return (Serializable) value;
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#equals(java.lang.Object,
               *      java.lang.Object)
               
          */
              
          public boolean equals(Object id1, Object id2) throws HibernateException {
                  
          if (id1 == id2) {
                      
          return true;
                  }
                  
          if (id1 == null || id2 == null) {
                      
          return false;
                  }

                  
          final DescriptionID did1 = (DescriptionID) id1;
                  
          final DescriptionID did2 = (DescriptionID) id2;

                  
          return did1.getId() == did2.getId()
                          
          && StringUtils.equals(did1.getDescription(), did2
                                  .getDescription());
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
               
          */
              
          public int hashCode(Object value) throws HibernateException {
                  
          // TODO Auto-generated method stub
                  return value.hashCode();
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#isMutable()
               
          */
              
          public boolean isMutable() {
                  
          // TODO Auto-generated method stub
                  return false;
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet,
               *      java.lang.String[], java.lang.Object)
               
          */
              
          public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
                      
          throws HibernateException, SQLException {
                  
          try {
                      
          int id = resultSet.getInt(names[0]);
                      
          if (resultSet.wasNull()) {
                          
          return null;
                      }
                      
          return enumClass.getMethod("findById"new Class[] { Integer.class })
                              .invoke(
          null, id);
                  } 
          catch (IllegalArgumentException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  } 
          catch (SecurityException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  } 
          catch (IllegalAccessException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  } 
          catch (InvocationTargetException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  } 
          catch (NoSuchMethodException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
                  
          return null;
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement,
               *      java.lang.Object, int)
               
          */
              
          public void nullSafeSet(PreparedStatement statement, Object value, int index)
                      
          throws HibernateException, SQLException {
                  
          if (value == null) {
                      statement.setNull(index, Hibernate.INTEGER.sqlType());
                  } 
          else {
                      DescriptionID dID 
          = (DescriptionID) value;
                      statement.setInt(index, dID.getId());
                  }
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#replace(java.lang.Object,
               *      java.lang.Object, java.lang.Object)
               
          */
              
          public Object replace(Object original, Object arg1, Object arg2)
                      
          throws HibernateException {
                  
          // TODO Auto-generated method stub
                  return original;
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#returnedClass()
               
          */
              
          public Class returnedClass() {
                  
          return DescriptionID.class;
              }

              
          /*
               * (non-Javadoc)
               * 
               * @see org.hibernate.usertype.UserType#sqlTypes()
               
          */
              
          public int[] sqlTypes() {
                  
          return new int[]{Hibernate.INTEGER.sqlType()};
              }


          }

          我們的這個UserType是要支持實現DescriptionID的各種不同的enum,而enum是沒法繼承的。所以我們需要用戶給出具體的參數,以進一步確定到底是哪個enum類。這也就導致了,我們的這個類需要實現ParameterizedType接口。

          由于enum類本身是immutable的,所以這個UserType的實現類相對比較簡單,主要的兩個方法是
          nullSafeGet和nullSafeSet。在nullSaftGet中我們使用Java Reflection并借助用戶給出的enum類參數直接調用該enum類的findById()方法,這樣我們就可以使用數據庫中的integer找到對應的enum實例。注意,由于使用了Java Reflection,所以findById()方法參數必須是Integer而非int。 在nullSafeSet中,我們則通過DescriptionID接口直接獲取enum實例的id屬性,并且將它保存到數據庫中去。

          最后看看怎么使用這個UserType:

          @TypeDefs({@TypeDef(name = "status", typeClass = DescriptionIDUserType.class
                              parameters 
          = {@Parameter(name = "class", value = "com.yourpackage.Status")})})
          @Entity
          public class SomeObject {

              
          private Integer objectId;
              
          private Status status;

              @Id
             
          @GeneratedValue(strategy=GenerationType.AUTO)   
             
          public Integer getObjectId() {
                  
          return objectId;
              }

              
          public void setObjectId(Integer objectId) {
                  
          this.objectId = objectId;
              }

              @Type(type 
          = "status")
              
          public Status getStatus() {
                  
          return status;
              }

              
          public void setStatus(Status status) {
                  
          this.status = status;
              }
          }

          其中值得講講的就是定義Type時使用的parameter,"class"參數是我們自己定義的,該參數為DescriptionIDUserType提供了具體的enum類。前面已經講過了,DescriptionIDUserType就是在運行時態利用這個參數自定義enum與數據庫之間的持久化邏輯。

          使用這個UserType之后,我們就可以在確保數據庫數據不變的情況下,成功地將類型不保險的常量類改寫成enum,而且這個UserType支持所有實現了
          DescriptionID接口的enum類。

          類似的情況朋友們可以自由發揮了。

          關于Annotation和Usertype的相關知識請參考我寫的《Hibernate 3和Java Persistence API 程序開發從入門到精通》

          posted @ 2008-03-21 20:14 polygoncell 閱讀(2959) | 評論 (4)編輯 收藏

          僅列出標題  
          主站蜘蛛池模板: 东阿县| 东安县| 新和县| 彰化市| 秭归县| 嘉鱼县| 建德市| 桦南县| 民权县| 中阳县| 西青区| 苍溪县| 新化县| 滦平县| 天水市| 沙洋县| 新巴尔虎左旗| 社旗县| 岢岚县| 黄石市| 离岛区| 嘉定区| 祁连县| 晋江市| 花莲市| 抚松县| 六枝特区| 宁武县| 舟山市| 宜都市| 廉江市| 钟山县| 宣威市| 沙洋县| 商河县| 慈利县| 杭州市| 厦门市| 赤城县| 格尔木市| 宣城市|