首页 > 编程语言 > 详细

Java笔记 #06# 自定义简易参数校验框架——EasyValidator

时间:2019-02-04 00:29:43      阅读:231      评论:0      收藏:0      [点我收藏+]

“参数校验”属于比较无聊但是又非常硬性的需求。。。

最原始的方式就是在方法头手动逐个校验,但是这样写不太好看,而且容易造成大量重复代码,扩展起来也不是很方便。

我简单看了一下已有的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);
}
Validator.java

定义一个最简单的校验器

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的实现

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

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!