資料校驗架構現狀
在我們的方法入口後面,難免會有如下樣子的代碼:
result.setSuccess(false);
if (StringUtils.isBlank(bizOrder.getThirdOrder())) {
result.setResultMessage("thirdOrder不能為空");
return result;
}
if(bizOrder.getThirdOrder().length() > 100){
result.setResultMessage("thirdOrder長多過長,必須在100以内");
return result;
}
if (StringUtils.isBlank(bizOrder.getSku())) {
result.setResultMessage("sku不能為空");
return result;
}
if (StringUtils.isBlank(bizOrder.getName())) {
result.setResultMessage("name不能為空");
return result;
}
if(bizOrder.getName().length() > 20){
result.setResultMessage("name字數過長");
return result;
}
if (bizOrder.getProvince() == 0 || bizOrder.getCity() == 0
|| bizOrder.getCounty() == 0) {
result.setResultMessage("位址資訊不正确");
return result;
}
if (StringUtils.isBlank(bizOrder.getAddress())) {
result.setResultMessage("address不能為空");
return result;
}
對于一名有潔癖的程式員,這顯然是不行的,我們要更加的優雅。
好吧,馬後炮了,其實早就有這樣的規範了:JSR 303 - Bean Validation
對于其實作,目前用的最廣泛的是:Hibernate Validator
Hiberante Validator, 小巧,規範,易擴充,易整合。
但是本文不是說它。。。
對于Web應用,可能更多的我們還是使用Spring MVC的校驗,叫做:spring mvc validator
一百度一大堆,可以跟頁面的error标簽很好的結合做頁面輸入的校驗。
但是本文也不是說它。。。
本文主要是說,來寫一個适合自己的校驗架構
資料架構設計目的
要簡單
隻是作為一個小的工具包,代碼最多幾K,無依賴也是必須的吧
要優雅
if.else的調用方式太難看了。看看如下的這種怎麼樣:
new Validator().notNull(name, "姓名").notNull(mail, "郵箱");1
要易用
注解是易用的一個好辦法,就像JSR303那樣
要可擴充
要友善用戶端程式友善的建立自定義校驗器
總體設計
首先得起個名字吧,叫MiniValidator
主要分了兩個部分:
1. 用來給對象進行注解的Annotation及其解析器和校驗器
Annotation ,一組注解
Parser, 注解解析器,主要處理注解的行為
AnnotationValidator 使用注解和解析器對傳入的對象的字段進行校驗
2. 可擴充的校驗器
AnnotationRule 注解校驗rule,作為内置的rule使用
Rule 用于擴充,可以自定義Rule
Validator 使用Rule對資料進行校驗,或者使用内置的校驗器
實作
注解校驗部分
首先寫一個注解, 例如不能為空白:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotBlank {
publicStringfieldName();
}
然後是對應該注解的解析器:
public classNotBlankParserimplementsIAnnotationParser{
@Override
publicValidateResultvalidate(Field f, Object value){
ValidateResult result = new ValidateResult();
if(f.isAnnotationPresent(NotBlank.class)){
NotBlank notBlank = f.getAnnotation(NotBlank.class);
if(value == null || value.toString().length() == 0){
result.setMessage(notBlank.fieldName() + "不能為空");
}
}
return result;
}
}
下面是使用上面内容的注解校驗器:
public classAnnotationValidator{
private static final Logger log = Logger.getLogger(AnnotationValidator.class.getName());
private final static List vList = new ArrayList();
static {
vList.add(new NotNullParser());
vList.add(new NotBlankParser());
}
public static ValidateResultvalidate(T t){
ValidateResult result = null;
for (Field f : t.getClass().getDeclaredFields()) {
f.setAccessible(true);
Object value = null;
try {
value = f.get(t);
} catch (IllegalArgumentException e) {
log.log(Level.SEVERE, "Exception", e);
} catch (IllegalAccessException e) {
log.log(Level.SEVERE, "Exception", e);
}
for (IAnnotationParser va : vList) {
result = va.validate(f, value);
if(!result.isValid()){
return result;
}
}
}
return result;
}
publicstaticvoidregister(IAnnotationParser parser){
vList.add(parser);
}
}
可以看到該校驗器已經注冊了多個解析器。然後對于傳入的對象,會對每一個字段的值進行所有解析器的校驗,得到校驗結果。
寫一個測試程式吧:
class User{
private Long id;
@NotBlank(fieldName="姓名")
private String name;
@Less(fieldName="年齡", value=100)
private int age;
private String phone;
private String birthday;
publicLonggetId(){
return id;
}
publicvoidsetId(Long id){
this.id = id;
}
publicStringgetName(){
return name;
}
publicvoidsetName(String name){
this.name = name;
}
publicintgetAge(){
return age;
}
publicvoidsetAge(intage){
this.age = age;
}
publicStringgetPhone(){
return phone;
}
publicvoidsetPhone(String phone){
this.phone = phone;
}
publicStringgetBirthday(){
return birthday;
}
publicvoidsetBirthday(String birthday){
this.birthday = birthday;
}
}
public class TestAnnotationValidator {
publicstaticvoidmain(String[] args){
User user = new User();
ValidateResult result = AnnotationValidator.validate(user);
if(result.isValid()){
System.out.println("校驗通過");
}else{
System.out.println(result.getMessage());
}
}
}
輸出的結果:
姓名不能為空1
擴充注解校驗器
基于這個架構,還是可以比較友善的進行擴充的。
要寫一個新的注解,新的解析器,然後注冊一下新的解析器就能給新的字段進行校驗了。如下:
新的注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DateFormat {
publicStringfieldName();
publicStringformat();
}1
2
3
4
5
6
新的解析器
public classDateFormatParserimplementsIAnnotationParser{
@Override
publicValidateResultvalidate(Field f, Object value){
ValidateResult result = new ValidateResult();
if(f.isAnnotationPresent(DateFormat.class)){
DateFormat dateFormat = f.getAnnotation(DateFormat.class);
try {
if(value != null){
SimpleDateFormat format = new SimpleDateFormat(dateFormat.format());
format.parse(value.toString());
}
} catch (ParseException e) {
result.setMessage(dateFormat.fieldName() + "不滿足格式:" + dateFormat.format());
}
}
return result;
}
}
使用擴充注解的測試程式:
public class TestAnnotationValidator {
publicstaticvoidmain(String[] args){
User user = new User();
user.setName("wzj");
user.setAge(21);
user.setBirthday("20150525");
AnnotationValidator.register(new DateFormatParser());
ValidateResult result = AnnotationValidator.validate(user);
if(result.isValid()){
System.out.println("校驗通過");
}else{
System.out.println(result.getMessage());
}
}
}
結果:
生日不滿足格式:yyyy-MM-dd
好了,注解的部分就這麼多了。
通用校驗部分
通用校驗部分首先是一個接口Rule, 供給校驗器調用:
public interfaceRule{
publicStringgetMessage();
publicbooleanisValid();
}
使用Rule的校驗器:
public classValidator{
publicValidatorvalidate(Rule rule){
if(this.isValid){
this.isValid = rule.isValid();
this.message = rule.getMessage();
}
return this;
}
publicValidatorvalidateAnnotation(Object o){
return validate(new AnnotationRule(o));
}
publicValidatornotNull(Object fieldValue, String fieldName){
if(this.isValid){
if(fieldValue == null){
this.isValid = false;
this.message = fieldName + "不能為空";
}
}
return this;
}
publicbooleanisValid(){
return isValid;
}
publicStringgetMessage(){
return message;
}
private boolean isValid = false; // 是否有效
private String message; // 錯誤資訊
}
該類除了使用Rule以外,還内置了一些notXX的方法,傳回this,這樣可以用.notXX().notXX().notXX()的結構來進行校驗。
來測試一下:
public class TestValidator {
publicstaticvoidmain(String[] args){
testMethod("name", null, null, null);
}
publicstaticvoidtestMethod(String name, String mail, String thirdOrderId, String address){
Validator v = new Validator().notNull(name, "姓名").notNull(mail, "郵箱").notNull(address, "位址");
if(v.isValid()){
System.out.println("校驗通過");
}else{
System.out.println(v.getMessage());
}
}
}
結果:
郵箱不能為空
擴充通用校驗器
擴充就需要實作Rule接口,如下我們實作一個基于AnnotationValidator的Rule:
public classAnnotationRuleimplementsRule{
private String message;
private Object o;
publicAnnotationRule(Object o){
this.o = o;
}
@Override
publicStringgetMessage(){
return message;
}
@Override
publicbooleanisValid(){
ValidateResult result = AnnotationValidator.validate(this.o);
this.message = result.getMessage();
return result.isValid();
}
}
然後在測試中使用中rule:
public class TestValidator {
publicstaticvoidmain(String[] args){
new Validator().validate(new AnnotationRule(new User()));
}
}1
2
3
4
5
總結
到此,這個簡單的校驗架構就完成了。
主要的技術上使用了注解,
然後通過反射再利用注解解析器來進行解析進行校驗
校驗器每個方法傳回this,可以使用更優雅的代碼來完成校驗
并且還可以比較友善的擴充。