PC的blog

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

BlogJava 首页 新随笔 联系 聚合 管理
  9 Posts :: 0 Stories :: 54 Comments :: 0 Trackbacks

2008年3月21日 #

本文紧接使用重构移除丑陋的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 阅读(4598) | 评论 (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 阅读(1917) | 评论 (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 阅读(2227) | 评论 (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 阅读(2069) | 评论 (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 阅读(2670) | 评论 (3)编辑 收藏

陆陆续续又看了一些技术书,加上最近工作任务繁重,实在没时间写技术类的博文,计划断断续续写一些书的阅读评论。今天先写一本,

最近阅读的书并不都是最新的,因为些书的确是需要花时间反复研读,仔细思考的,例如关于设计模式的书。

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 阅读(1382) | 评论 (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 阅读(2326) | 评论 (5)编辑 收藏

在上一篇博客里,我编写了一个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 阅读(1136) | 评论 (0)编辑 收藏

前段时间写了本书《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 阅读(2912) | 评论 (4)编辑 收藏