Skip to content

Latest commit

 

History

History
701 lines (587 loc) · 24.4 KB

2.参数校验-2-自定义注解.md

File metadata and controls

701 lines (587 loc) · 24.4 KB

参数校验-2-自定义注解


简介: Java 自身提供了参数校验接口,通过第三方实现类即可进行参数校验,但是由于接口提供的参数校验注解过少,并不能够满足实际工作需要,且可拓展性较差。因此可以通过编写自定义注解的方式来来显参数的校验。通过自定义注解可以根据实际需要实现更加灵活的参数校验,也无需添加第三方依赖,减少项目运行负担

1 自定义参数校验注解

com.ljq.demo.annotation.Validate

package com.ljq.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: 校验注解,添加该注解则表明该参数需要进行校验
 * @Author: junqiang.lu
 * @Date: 2018/12/16
 *
 * @Retention: 定义注解的保留策略
 *     @Retention(RetentionPolicy.SOURCE)  注解仅存在于源码中,在class字节码文件中不包含
 *     @Retention(RetentionPolicy.CLASS)   默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
 *     @Retention(RetentionPolicy.RUNTIME) 注解会在class字节码文件中存在,在运行时可以通过反射获取到
 *
 * @Target 注解类型声明
 *     CONSTRUCTOR 构造方法声明
 *     FIELD 字段声明(包括枚举常量)LOCAL_VARIABLE 局部变量声明
 *     METHOD 方法声明
 *     PACKAGE 包声明
 *     PARAMETER 参数声明
 *     TYPE 类、接口(包括注释类型)或枚举声明
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.PARAMETER})
public @interface Validate {


}

com.ljq.demo.annotation.ParamsCheck

package com.ljq.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: (自定义)参数校验注解, 添加该注解则表明该参数需要满足注解中对应的参数要求
 * @Author: junqiang.lu
 * @Date: 2018/12/16
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ParamsCheck {

    /**
     * 参数描述信息(建议每一个注解都包含该参数)
     *
     * @return
     */
    String desc() default "";

    /**
     * 非空
     * 对于 String 对象,值为 "" 也会返回非空信息
     *
     * @return
     */
    boolean notNull() default false;

    /**
     * 最大值(数字参数使用该注解,参数值不可大于该限定值)
     * 支持 int,Integer,float,Float,double,Double 类型字段
     *
     * @return
     */
    int max() default -1;

    /**
     * 最小值(与 max 对应,数字参数使用该注解,参数值不可小于该限定值)
     * 支持 int,Integer,float,Float,double,Double 类型字段
     *
     * @return
     */
    int min() default -1;

    /**
     * 字符串最大长度值(String 参数使用该注解,字符串长度不可大于该限定值)
     *
     * @return
     */
    int strMaxLength() default -1;

    /**
     * 字符串最小长度值(String 参数使用该注解,字符串长度不可小于该限定值)
     *
     * @return
     */
    int strMinLength() default -1;

    /**
     * 是否为(中国大陆)手机号
     * 不校验非空,参数为空会抛出空指针异常(NullPointException)
     *
     * @return
     */
    boolean isMobile() default false;

    /**
     * 是否为邮箱
     * 不校验非空,参数为空会抛出空指针异常(NullPointException)
     *
     * @return
     */
    boolean isEmail() default false;

    /**
     * 是否为字母数字
     * 包含 a-z,A-Z 以及 0-9,不包含符号
     * 不校验非空,参数为空会抛出空指针异常(NullPointException)
     *
     * @return
     */
    boolean isAlphanumericStr() default false;

    /**
     * 是否为纯数字组成的字符串
     * 只包含 0-9
     * 不校验非空,参数为空会抛出空指针异常(NullPointException)
     *
     * @return
     */
    boolean isNumericStr() default false;

}

说明: Validate 注解类是用于表明使用该注解的 对象/方法/参数 需要进行参数校验

ParamsCheck 注解类是用于自定义对参数字段的具体校验注解

2 参数校验工具类

在定义了注解之后,需要对每一个定义的注解提供对应校验方法

com.ljq.demo.util.ParamsValidator

package com.ljq.demo.util;

import com.ljq.demo.annotation.ParamsCheck;

import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description: 参数校验类
 * @Author: junqiang.lu
 * @Date: 2018/12/16
 */
public class ParamsValidator {

    private ParamsValidator(){}


    /**
     * 参数校验方法
     * 当被校验对象的参数符合要求时,返回 null
     * 当被校验对象不符合某一项校验要求时,返回对应的错误信息
     *
     * @param arg 参数对象
     * @return
     * @throws Exception
     */
    public static String validateParams(Object arg) throws Exception {
        String result = null;
        // 获取参数的所有成员变量
        Field[] field = arg.getClass().getDeclaredFields();
        for (int j = 0; j < field.length; j++) {
            // 获取方法参数对象 field 上的 ParamsCheck 注解
            ParamsCheck check = field[j].getAnnotation(ParamsCheck.class);
            if (check != null) {
                result = validateField(check, field[j], arg);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;

    }

    /**
     * 参数字段校验
     *
     * @param paramsCheck 参数校验注解
     * @param field 字段
     * @param arg 参数对象
     * @return
     * @throws Exception
     */
    private static String validateField(ParamsCheck paramsCheck, Field field, Object arg) throws Exception{
        field.setAccessible(true);

        /**
         * 非空校验
         */
        if (paramsCheck.notNull()) {
            if (field.get(arg) == null || "".equals(String.valueOf(field.get(arg)))) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数不能为 null";
            }
        }

        /**
         * 最大值校验
         */
        if (paramsCheck.max() != -1) {
            int param = Double.valueOf(String.valueOf(field.get(arg))).intValue();
            if (param > paramsCheck.max()) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数最大值不可超过" + paramsCheck.max();
            }
        }

        /**
         * 最小值校验
         */
        if (paramsCheck.min() != -1) {
            int param = Double.valueOf(String.valueOf(field.get(arg))).intValue();
            if (param < paramsCheck.min()) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数最小值不可小于" + paramsCheck.min();
            }
        }

        /**
         * 字符串最大长度校验
         */
        if (paramsCheck.strMaxLength() != -1) {
            String param = String.valueOf(field.get(arg));
            int paramLength = param.length();
            if (paramLength > paramsCheck.strMaxLength()) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数最大长度不可超过" + paramsCheck.strMaxLength();
            }
        }

        /**
         * 字符串最小长度校验
         */
        if (paramsCheck.strMinLength() != -1) {
            String param = String.valueOf(field.get(arg));
            int paramLength = param.length();
            if (paramLength < paramsCheck.strMinLength()) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数最小长度不可小于" + paramsCheck.strMinLength();
            }
        }

        /**
         * 手机号检验
         */
        if (paramsCheck.isMobile()) {
            String param = String.valueOf(field.get(arg));
            String regExp = "^((13[0-9])|145|147|(15[^4])|166|(17[^2])|(17[^4])|(17[^9])|(18[0-9])|(19[8-9]))\\d{8}$";
            Pattern p = Pattern.compile(regExp);
            Matcher m = p.matcher(param);
            if (!m.matches()) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数不符合手机号格式要求";
            }
        }

        /**
         * 邮箱校验
         */
        if (paramsCheck.isEmail()) {
            String param = String.valueOf(field.get(arg));
            String regExp = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])" +
                    "+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])" +
                    "+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]" +
                    "|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|" +
                    "(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*" +
                    "(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|" +
                    "[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])" +
                    "([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|" +
                    "[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])" +
                    "|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|" +
                    "[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$";
            Pattern p = Pattern.compile(regExp);
            Matcher m = p.matcher(param.toLowerCase());
            if (!m.matches()) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数不符合邮箱格式要求";
            }
        }

        /**
         * 数字字母校验
         */
        if (paramsCheck.isAlphanumericStr()) {
            String param = String.valueOf(field.get(arg));
            String regex = "[a-z]*[A-Z]*\\d*";
            if(param.replaceAll(regex,"").length() > 0) {
                return "[" + field.getName() + "]" + paramsCheck.desc() + "参数不符合数字字母要求,参数值包含特殊符号";
            }
        }

        /**
         * 纯数字字符串校验
         */
        if (paramsCheck.isNumericStr()) {
            String param = String.valueOf(field.get(arg));
            for (int i = 0; i < param.length(); i++) {
                if (!Character.isDigit(param.charAt(i))) {
                    return "[" + field.getName() + "]" + paramsCheck.desc() + "参数不符合纯数字要求,参数值包含非数字字符";
                }
            }
        }


        return null;
    }


}

3 使用自定义参数校验注解的实体类

com.ljq.demo.bean.ParamsCheckBean

package com.ljq.demo.bean;

import com.ljq.demo.annotation.ParamsCheck;
import lombok.Data;

/**
 * @Description: 参数校验实体类
 * @Author: junqiang.lu
 * @Date: 2018/12/16
 */
@Data
public class ParamsCheckBean {

    @ParamsCheck(notNull = true, desc = "账号")
    private String account;

    @ParamsCheck(max = 150, desc = "年龄")
    private int age;

    @ParamsCheck(min = 20, desc = "身高")
    private int height;

    @ParamsCheck(strMinLength = 2, desc = "昵称")
    private String nickName;

    @ParamsCheck(strMaxLength = 50, desc = "个性签名")
    private String description;

    @ParamsCheck(notNull = true, isMobile = true, desc = "手机号")
    private String mobile;

    @ParamsCheck(notNull = true, isEmail = true, desc = "邮箱")
    private String email;

    @ParamsCheck(notNull = true, isAlphanumericStr = true, desc = "邀请码")
    private String inviteCode;

    @ParamsCheck(notNull = true, isNumericStr = true, desc = "学号")
    private String studentId;

}

4 参数校验测试类

com.ljq.demo.util.ParamsCheckerTest

package com.ljq.demo.util;

import com.ljq.demo.bean.ParamsCheckBean;
import org.junit.Test;

public class ParamsCheckerTest {


    /**
     * 参数非空测试
     *
     * @throws Exception
     */
    @Test
    public void checkParamNotNull() throws Exception {

        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamNotNull");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 限定最大值测试
     *
     * @throws Exception
     */
    @Test
    public void checkParamMax() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(156);
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamMax");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 限定最小值测试
     *
     * @throws Exception
     */
    @Test
    public void checkParamMin() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(10);
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamMin");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 字符串长度最小值测试
     *
     * @throws Exception
     */
    @Test
    public void checkParamStrMinLength() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("明");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamStrMinLength");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 字符串长度最大值测试
     *
     * @throws Exception
     */
    @Test
    public void checkParamStrMaxLength() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("hello小明");
        StringBuilder description = new StringBuilder();
        for (int i = 0; i < 60; i++) {
            description.append("a");
        }
        paramsCheckBean.setDescription(description.toString());
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamStrMaxLength");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 正则表达式-手机号校验
     *
     * @throws Exception
     */
    @Test
    public void checkParamMobile() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("hello小明");
        StringBuilder description = new StringBuilder();
        for (int i = 0; i < 40; i++) {
            description.append("a");
        }
        paramsCheckBean.setDescription(description.toString());
        paramsCheckBean.setMobile("1234567");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamMobile");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 正则表达式-邮箱校验
     *
     * @throws Exception
     */
    @Test
    public void checkParamEmail() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("hello小明");
        StringBuilder description = new StringBuilder();
        for (int i = 0; i < 40; i++) {
            description.append("a");
        }
        paramsCheckBean.setDescription(description.toString());
        paramsCheckBean.setMobile("13122223333");
        paramsCheckBean.setEmail("demo@");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamEmail");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 正则表达式-字母数字校验
     *
     * @throws Exception
     */
    @Test
    public void checkParamAlphanumericStr() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("hello小明");
        StringBuilder description = new StringBuilder();
        for (int i = 0; i < 40; i++) {
            description.append("a");
        }
        paramsCheckBean.setDescription(description.toString());
        paramsCheckBean.setMobile("13122223333");
        paramsCheckBean.setEmail("demo@gmail.com");
        paramsCheckBean.setInviteCode("123aD*");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamAlphanumericStr");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }

    /**
     * 正则表达式-纯数字字符串校验
     *
     * @throws Exception
     */
    @Test
    public void checkParamNumericStr() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("hello小明");
        StringBuilder description = new StringBuilder();
        for (int i = 0; i < 40; i++) {
            description.append("a");
        }
        paramsCheckBean.setDescription(description.toString());
        paramsCheckBean.setMobile("13122223333");
        paramsCheckBean.setEmail("demo@gmail.com");
        paramsCheckBean.setInviteCode("123QWEa");
        paramsCheckBean.setStudentId("12345Q");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamNumericStr");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }



    /**
     * 参数符合要求
     *
     * @throws Exception
     */
    @Test
    public void checkParamValid() throws Exception{
        ParamsCheckBean paramsCheckBean = new ParamsCheckBean();
        paramsCheckBean.setAccount("helloWorld");
        paramsCheckBean.setAge(20);
        paramsCheckBean.setHeight(100);
        paramsCheckBean.setNickName("hello小明");
        StringBuilder description = new StringBuilder();
        for (int i = 0; i < 40; i++) {
            description.append("a");
        }
        paramsCheckBean.setDescription(description.toString());
        paramsCheckBean.setMobile("13122223333");
        paramsCheckBean.setEmail("demo@gmail.com");
        paramsCheckBean.setInviteCode("123QWEa");
        paramsCheckBean.setStudentId("1234567890");
        String result = ParamsValidator.validateParams(paramsCheckBean);

        System.out.println("测试方法: checkParamValid");
        System.out.println(paramsCheckBean);
        System.out.println("result: " + result);
        System.out.println("------------------------------");
    }



}

5 测试结果

测试方法: checkParamMax
ParamsCheckBean(account=helloWorld, age=156, height=0, nickName=null, description=null, mobile=null, email=null, inviteCode=null, studentId=null)
result: [age]年龄参数最大值不可超过150
------------------------------
测试方法: checkParamMin
ParamsCheckBean(account=helloWorld, age=20, height=10, nickName=null, description=null, mobile=null, email=null, inviteCode=null, studentId=null)
result: [height]身高参数最小值不可小于20
------------------------------
测试方法: checkParamMobile
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=hello小明, description=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, mobile=1234567, email=null, inviteCode=null, studentId=null)
result: [mobile]手机号参数不符合手机号格式要求
------------------------------
测试方法: checkParamStrMaxLength
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=hello小明, description=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, mobile=null, email=null, inviteCode=null, studentId=null)
result: [description]个性签名参数最大长度不可超过50
------------------------------
测试方法: checkParamAlphanumericStr
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=hello小明, description=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, mobile=13122223333, email=demo@gmail.com, inviteCode=123aD*, studentId=null)
result: [inviteCode]邀请码参数不符合数字字母要求,参数值包含特殊符号
------------------------------
测试方法: checkParamNotNull
ParamsCheckBean(account=, age=0, height=0, nickName=null, description=null, mobile=null, email=null, inviteCode=null, studentId=null)
result: [account]账号参数不能为 null
------------------------------
测试方法: checkParamStrMinLength
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=明, description=null, mobile=null, email=null, inviteCode=null, studentId=null)
result: [nickName]昵称参数最小长度不可小于2
------------------------------
测试方法: checkParamEmail
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=hello小明, description=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, mobile=13122223333, email=demo@, inviteCode=null, studentId=null)
result: [email]邮箱参数不符合邮箱格式要求
------------------------------
测试方法: checkParamValid
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=hello小明, description=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, mobile=13122223333, email=demo@gmail.com, inviteCode=123QWEa, studentId=1234567890)
result: null
------------------------------
测试方法: checkParamNumericStr
ParamsCheckBean(account=helloWorld, age=20, height=100, nickName=hello小明, description=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, mobile=13122223333, email=demo@gmail.com, inviteCode=123QWEa, studentId=12345Q)
result: [studentId]学号参数不符合纯数字要求,参数值包含非数字字符
------------------------------

说明: 测试结果并没有按照测试方法的顺序执行,根据测试方法以及测试结果进行对比即可

—— update 2018-12-16