在数据传递,尤其当你准备好发送一个请求到其他系统前,对于bean的取值做必要的校验是十分重要的。
对于一些简单的字段,JSR已经提供了一些annotation比如@NotNull , @NotEmpty来完成,我们这里主要来讨论比较复杂的校验:
需求:
假设我们在数据模型AppInfo中有一个字段叫Tenant,这个tenant是个字符串类型,并且它的取值只可能是{"US","Brazil","ASDA","Canada","Multi-tenant"} ,那么我们如何校验这个数据模型呢?
解决方法:
很简单,首先我们自定义一个Validator类来封装校验逻辑,它要求我们实现isValid()方法:
/** * Validator class definition to validate the tenant field of AppService * datamodel * * @author cwang58 * @created date: Jan 28, 2013 */ public class TenantValidator implements ConstraintValidator<Tenant, String> { private final String[] ALL_TENANT_TYPE = { "US", "Brazil", "ASDA", "Canada", "Multi-tenant" }; @Override public void initialize(Tenant tenant) { } /** * validation logic to identifiy whether the field which annotatied by @Tenant is valid or invalid */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (Arrays.asList(ALL_TENANT_TYPE).contains(value)) { return true; } return false; } }
其次,我们定义一个注解接口 @Tenant,并且让这个注解接口使用我们刚才定义的Validator类作为其校验类,我们这类是可以标注在字段或者方法上的。
/** * Annotation of Tenant to validate the "tenant" field of AppInfo *@author cwang58 *@created date: Jan 28, 2013 */ @Constraint(validatedBy = {TenantValidator.class}) @Documented @Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Tenant { String message() default " only support tenant type of ['US','Brazil','ASDA',Canada','Multi-tenant']"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
现在我们在目标数据模型中相应的字段上使用我们开发的标注就可以了,见27-28行:
/** * this is the data model for appinfo *@author cwang58 *@created date: Jan 24, 2013 */ public class AppInfo implements Serializable { /** * */ private static final long serialVersionUID = 3L; @NotNull private String groupId; @NotNull private String artifactId; @NotNull private String versionNo; @NotNull private boolean isPCIApplication; @Tenant private String tenant; @NotEmpty private String ri; ...
现在我们来编写校验方法,我们在业务方法createAppInfo()中加入以下代码,这样在创建数据模型时会完成我们的校验逻辑:
/** * build the AppInfo object based on the appInfoJsonString */ public AppInfo createAppInfo(String appInfoJSONString) { if ((appInfoJSONString == null) || ("".equals(appInfoJSONString))) return null; JSONObject appInfoJSON; try { appInfoJSON = new JSONObject(appInfoJSONString); String groupId = appInfoJSON.getString("groupId"); String artifactId = appInfoJSON.getString("artifactId"); String versionNo = appInfoJSON.getString("versionNo"); boolean isPCIApplication = Boolean.parseBoolean(appInfoJSON .getString("isPCIApplication")); String tenant = appInfoJSON.getString("tenant"); String ri = appInfoJSON.getString("ri"); String appRunningEnv = appInfoJSON.getString("appRunningEnv"); //Date goLiveDate = new Date(appInfoJSON.getLong("goliveDate")); String goLiveDate = appInfoJSON.getString("goliveDate"); // Parse docInfos JSONArray docInfos = appInfoJSON.getJSONArray("docInfos"); List<DocInfo> docInfoList = new ArrayList<DocInfo>(); for (int i = 0; i < docInfos.length(); i++) { JSONObject docInfoJSON = docInfos.getJSONObject(i); String bookTitle = docInfoJSON.getString("title"); String bookUrl = docInfoJSON.getString("url"); docInfoList.add(new DocInfo(bookTitle, bookUrl)); } AppInfo appInfo = new AppInfo(groupId, artifactId, versionNo, isPCIApplication, tenant, ri, appRunningEnv, goLiveDate, docInfoList); ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); //begin validate Set<ConstraintViolation<AppInfo>> violations = validator.validate(appInfo); if(violations.size() == 0) { return appInfo; } else { StringBuilder volidationFailureSB = new StringBuilder() ; for(ConstraintViolation<AppInfo> violation: violations) { volidationFailureSB.append("-" + violation.getPropertyPath().toString()); volidationFailureSB.append(violation.getMessage() +"\n"); } logger.error("validation failure as follows:"); logger.error(volidationFailureSB.toString()); return null; } ...
从38行开始就会让校验框架来进行校验,我们首先获得ValidatorFactory,然后获得Validator对象(这是jsr的validator),然后从第41行开始校验,并且把校验的违背结果放入violations集合对象中,如果集合对象size()为0 ,那么就说明数据模型没有违背的地方,所以正常创建数据模型,否则,从46行开始,我们把所有违背的地方全部罗列出来,并且写入日志。
实验:
如果用户在前端对于tenant字段传递的不是我们所要的合法tenant值,比如"tenant1":
最终,日志就可以看出,吧这行非法内容检查了出来:
[Server Log] validation failure as follows: -tenant only support tenant type of ['US','Brazil','ASDA',Canada','Multi-tenant']