引言
很多应用都需要对传入的数据进行验证,web应用对这种需要更为强烈。提高验证代码的内聚性和高可重用性是一个比较重要的话题。本文提出了一种利用Annotation机制对Java对象的属性进行合法性验证的新方法。
设计目标
如果有大量的属性需要对其合法性进行验证,不可能进行硬编码,同时,业务代码也不应该感知这种验证的存在。但是需要有一个接口供业务代码设置验证的逻辑。并且,某个验证逻辑可以重用到多处变量。还需要达到验证逻辑的集中,接口清晰,便于后续的维护和添加。
设计策略
利用Annotation,可以设置java类的某个实例变量多个验证逻辑,通用的验证框架会调用每一种验证逻辑对该属性进行验证。
主要接口
如何提供公共的框架来调用每一种验证逻辑对某个对象的实例变量进行验证?接口可以给我们提供这样的方便。一个很直观的接口是FieldChecker。
public interface FieldChecker {
/**
* 根据某个验证规则,验证某个实例变量的值得合法性。
* @param field 实例变量
* @param value 变量的值
* @param annotation 验证规则的annotation
* @return
*/
public CheckResult check(Field field,Object value,Annotation annotation);
}
不同的验证规则只需要实现这个接口即可。
CheckResult是验证的结果。
public class CheckResult {
/**
* 验证正确的结果,为减少实例的创建,这个为单例模式
*/
public static CheckResult SUCCESS = new CheckResult();
private boolean success = true;
private String errorMessage = null;
private CheckResult(){}
public CheckResult(String errorMessage){
this.success = false;
this.errorMessage = errorMessage;
}
public boolean isSuccess(){
return success;
}
public String getErrorMessage(){
return this.errorMessage;
}
}
我们用一个工厂接口来生成FieldChecker实例。
public interface FieldCheckerFactory {
/**
* 得到一个FieldChecker的实例
* @return
*/
public FieldChecker getChecker();
}
还需要一个管理工厂的集中类,根据某个Annotation,得到其对应的FieldCheckerFactory,因此,我们创建了FieldCheckerFactories。
public class FieldCheckerFactories {
private static Map<Class,FieldCheckerFactory> factories = new HashMap<Class,FieldCheckerFactory>();
/**
* 注册一个工厂到某个验证规则上
* @param clazz
* @param factory
*/
public static void registFactory(Class clazz,FieldCheckerFactory factory){
factories.put(clazz, factory);
}
/**
* 根据某个验证规则,得到其对应的工厂类实例
* @param clazz
* @return
*/
public static FieldCheckerFactory getFactory(Class clazz){
return factories.get(clazz);
}
}
下面展示一个Time验证的Annotation
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Time{
public static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
static FieldCheckerFactory factory = new TimeCheckerFactory();
static class TimeCheckerFactory implements FieldCheckerFactory{
public TimeCheckerFactory(){
FieldCheckerFactories.registFactory(Time.class, this);
}
/**
* 真正的属性检查规则实现
*/
private static FieldChecker checker = new FieldChecker() {
public CheckResult check(Field field, Object value, Annotation annotation) {
if(value == null) return CheckResult.SUCCESS;
Time time = (Time)annotation;
String timeValue = value.toString();
try{
new SimpleDateFormat(Time.FORMAT).parse(timeValue);
return CheckResult.SUCCESS;
}catch(Throwable e){
return new CheckResult("field:["+field.getName()+"] is not valid time string");
}
}
};
public FieldChecker getChecker(){
return checker;
}
}
}
真正的通用验证框架是ObjectChecker
public class ObjectChecker {
public static ObjectChecker checker = new ObjectChecker();
private ObjectChecker(){}
public static ObjectChecker instance(){
return checker;
}
/**
* 依次检查
* @param obj
* @return
*/
public CheckResult check(Object obj){
Field[] fields = obj.getClass().getDeclaredFields();
if(fields == null || fields.length == 0) return CheckResult.SUCCESS;
for(Field field:fields){
CheckResult result = checkField(obj,field);
if(!result.isSuccess()) return result;
}
return CheckResult.SUCCESS;
}
/**
* 检查某个属性。依次检查该属性的每一个检验规则,
* 如果遇到一个规则检查失败,则立即返回。也就是一种fast-fail的方式。
* 如果所有的规则都检查通过,则返回成功的CheckResult。
* @param obj
* @param field
* @return
*/
private CheckResult checkField(Object obj,Field field){
Annotation[] annotations = field.getAnnotations();
if(annotations == null || annotations.length == 0) return CheckResult.SUCCESS;
for(Annotation annotation:annotations){
CheckResult result = checkField(obj, field, annotation);
if(!result.isSuccess()) return result;
}
return CheckResult.SUCCESS;
}
/**
* 检查某个属性的某个规则
* @param obj
* @param field
* @param annotation
* @return
*/
private CheckResult checkField(Object obj,Field field,Annotation annotation){
FieldCheckerFactory factory = FieldCheckerFactories.getFactory(annotation.annotationType());
if(factory == null) return CheckResult.SUCCESS;
FieldChecker checker = factory.getChecker();
if(checker == null) return CheckResult.SUCCESS;
return checker.check(field, getFieldValue(obj, field), annotation);
}
private static Object getFieldValue(Object obj,Field field){
field.setAccessible(true);
try {
return field.get(obj);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
下面是一个简单的测试用例:
public class ObjectCheckerTest extends TestCase {
public void testChecker(){
Person person = new Person();
assertFalse((ObjectChecker.instance().check(person).isSuccess()));
person.birthday = new SimpleDateFormat(Time.FORMAT).format(new Date(System.currentTimeMillis()));
assertFalse(ObjectChecker.instance().check(person).isSuccess());
person.name = "bobo";
assertTrue(ObjectChecker.instance().check(person).isSuccess());
}
public static class Person{
@Time
private String birthday;
@NotNull
private String name;
public Person(){
this.birthday = (new SimpleDateFormat("yyyy-MM-dd")).format(new Date(System.currentTimeMillis()));
}
}
}