“参数校验”属于比较无聊但是又非常硬性的需求。。。
最原始的方式就是在方法头手动逐个校验,但是这样写不太好看,而且容易造成大量重复代码,扩展起来也不是很方便。
我简单看了一下已有的Spring Validation,粗看下去不太合胃口。想想写一个似乎也不难,于是尝试自定义了一个简单的玩具版本。
MyTest.java:
public class MyTest { /** * 创建一次,应用N次。 */ private static final EasyValidator EASY_VALIDATOR = new EasyValidator(); public static void main(String[] args) { // 允许自定义各种各样的校验器,只需实现Validator接口即可 EASY_VALIDATOR.addValidator(new RegexpValidator()); EASY_VALIDATOR.addValidator(new TypeValidator()); // 示例↓ int type = 3; String username = " "; String password = "32"; // 通过抛异常的方式传递具体错误信息 EASY_VALIDATOR.check(type, "type"); EASY_VALIDATOR.check(username, "username.regexp"); EASY_VALIDATOR.check(password, "password.regexp"); } } /* ouput example - 1: Exception in thread "main" org.sample.exception.ValidateException: type越界了! ouput example - 2: Exception in thread "main" org.sample.exception.ValidateException: 用户名不能为空 */
通过addValidator方法让校验器生效。
仅需实现Validator接口就可以随心所欲地定义各式各样的参数校验器。
Validator接口仅包含三个非常简单的方法:
public interface Validator { /** * 用于标识和查找校验器 */ String getName(); void validate(Object o); /** * 便于多级查找校验方法,如果不支持该功能, * 直接内部调void validate(Object o);方法即可 */ void validate(Object o, String validatorName); }
validate方法说明:校验不通过抛异常就ok了,在异常里传递具体错误信息。
定义不抛异常的校验器。。是没意义的。在“传Result”和“传异常”间我也权衡了一下,网上说“传异常”没“传Result”性能好,但就写代码的角度来看,还是传异常干净得多,并且更简单,性能方面的差距估计也是微乎其微,so。。。。
public class TypeValidator implements Validator { private static final String NAME = "type"; @Override public String getName() { return NAME; } @Override public void validate(Object o) { int i = Integer.parseInt(String.valueOf(o)); // 通过抛异常的方式传递具体错误信息 if (i < 0 || i > 10) throw new ValidateException("type越界了!"); // 没抛出异常代表没问题! } @Override public void validate(Object o, String validatorName) { validate(o); } }
同样可以定义一个复杂些的校验器↓
原始版本来自Spring笔记#02#利用切面和注解校验方法参数
这是一个专门用来校验字符串的校验器,思路就是用.properties文件存键值对(“名”-正则)然后读到HashMap构造Pattern。根据“名”查找对应的Pattern进行正则校验。顺便提一下自定义的两种异常:ValidatorException和ValidateException,两个异常都继承自运行时异常,这样不至于把代码搞乱,前者属于程序员的错误,后者则用于抛出校验失败的具体信息。
public class RegexpValidator implements Validator { private static final String NAME = "regexp"; private static final String FILE_NAME = File.separator + "easy-validator.properties"; private static final Map<String, Pattern> MAP = new HashMap<>(); static { // 把.properties文件里的键值对读到内存里 Properties prop = new Properties(); try (InputStream input = ServiceMethodAspect.class.getClassLoader().getResourceAsStream(FILE_NAME)){ if (input == null) { throw new RuntimeException("Sorry, unable to find " + FILE_NAME); } prop.load(input); Enumeration<?> e = prop.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); String value = prop.getProperty(key); MAP.put(key, Pattern.compile(value)); } } catch (IOException e) { throw new RuntimeException("An exception occurred while reading " + FILE_NAME, e); } } @Override public String getName() { return NAME; } @Override public void validate(Object o) { throw new ValidatorException("validate(Object o)方法未定义"); } @Override public void validate(Object o, String validatorName) { String str = String.valueOf(o); if (StringUtils.isBlank(str)) { throw new ValidateException(PresentationUtils.english2chinese(validatorName) + "不能为空"); } else { Pattern pattern = MAP.get(validatorName); // 程序员要确保格式已经定义 if (pattern == null) throw new ValidatorException(validatorName + "校验器未定义"); // 格式检查 if (!pattern.matcher(str).matches()) { throw new ValidateException(PresentationUtils.english2chinese(validatorName) + "格式不正确"); } } } }
在相应路径下的easy-validator.properties中定义正则表达式:
username=^[a-zA-Z0-9_]{4,16}$ # 用户名:4到16位大小写字母、数字、下划线 password=^[a-zA-Z0-9_]{6,16}$ # 密码:6到16位大小写字母、数字、下划线
EasyValidator的实现像它的名字一样简单。每个校验器由getName()的返回值标识(确保唯一),允许自定义多级校验器,命名规则为“xx.次级校验器名.顶级校验器名”,详细匹配过程参考代码实现:
/** * 自定义的一个简易参数校验器 */ public class EasyValidator { /** * 初始化后不再更新,否则在多线程环境下有风险 */ private static final Map<String, Validator> MAP = new HashMap<>(); public void addValidator(Validator validator) { MAP.put(validator.getName(), validator); } public void check(Object object, String validatorName) { Validator validator = MAP.get(getLast(validatorName)); if (validator != null) { validator.validate(object, removeLast(validatorName)); } else { throw new ValidatorException("validator not found, validatorName=" + validatorName); } } /** * 获取顶级校验器名称,如果只有一级就原样返回 */ private static String getLast(String validatorName) { int index = StringUtils.lastIndexOf(validatorName, ‘.‘); if (index == -1) return validatorName; return StringUtils.substring(validatorName, index + 1); } /** * 去掉顶级校验器名称,以便向下传递,如果只有一级就原样返回 */ private static String removeLast(String validatorName) { int index = StringUtils.lastIndexOf(validatorName, ‘.‘); if (index == -1) return validatorName; return StringUtils.substring(validatorName, 0, index); } }
Example代码(因为用到了Spring的黑科技,需要添加相应依赖):
@Aspect @Configuration public class ServiceMethodAspect { private static final Logger LOGGER = LogManager.getLogger(); private static final ParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); private static final EasyValidator EASY_VALIDATOR = new EasyValidator(); static { EASY_VALIDATOR.addValidator(new RegexpValidator()); } @Pointcut("execution(* org.sample.shop.common.service.impl.*.*(..))") public void serviceMethod() {} @Around("serviceMethod()") public ServiceResult work(ProceedingJoinPoint jp) { // 参数校验 Method method = ((MethodSignature) jp.getSignature()).getMethod(); Parameter[] parameter = method.getParameters(); // 便于获取注解对象 String[] parameterName = DISCOVERER.getParameterNames(method); Object[] parameterValue = jp.getArgs(); String currentValidatorName; try { // 逐个参数进行校验,先尝试获取Validator注解值,注解不存在用参数名作为校验器名 for (int i = 0; i != parameter.length; ++i) { if (parameter[i].getAnnotation(Validator.class) != null) { currentValidatorName = parameter[i].getAnnotation(Validator.class).value(); } else { currentValidatorName = parameterName[i]; } LOGGER.info("check: object={}, validatorName={}", parameterValue[i], currentValidatorName); // 偶尔出现校验异常有日志可查 TODO 用户信息 EASY_VALIDATOR.check(parameterValue[i], currentValidatorName); } return (ServiceResult) jp.proceed(); } catch (ValidateException e) { return ServiceResult.fail(e.getMessage()); } catch (Throwable throwable) { // 错误日志 LOGGER.error(new DetailedInfo(jp.getArgs(), throwable.getMessage()), throwable); return ServiceResult.error(); } } private class DetailedInfo { private Object[] args; private String message; DetailedInfo(Object[] args, String message) { this.args = args; this.message = message; } @Override public String toString() { return "DetailedInfo{" + "args=" + Arrays.toString(args) + ", message=‘" + message + ‘\‘‘ + ‘}‘; } } }
Example代码之应用注解:
@Override public ServiceResult<User> getUser(@Validator("username.regexp") String username, @Validator("password.regexp") String password) { return ServiceUtils.daoOperation(() -> { User user = userDAO.getUser(username, password); return user != null ? ServiceResult.ok(user) : new ServiceResult<>(LOGIN_FAIL); }, Connection.TRANSACTION_READ_COMMITTED); }
注解的实现依然和Spring笔记#02#利用切面和注解校验方法参数里的差不多,只不过换了个名字。。。
Java笔记 #06# 自定义简易参数校验框架——EasyValidator
原文:https://www.cnblogs.com/xkxf/p/10350596.html