自定义注解实现数据验证和过滤等操作

背景

在java开发中spring的很多功能已经非常的完美了。但是再完美的工具也难以抵挡用户的要求。
在很多次的项目中,我都会设计到一些标准文档,里面有各种各样的参数,这些数据有各种各样的取值,有各种各样的要求。数据的传输采用采用json方式。收到数据之后需要对数据进行按照标准进行过滤。在博主所知的现有的工具中没有办法满足这些功能。
但是获取每一个对象来进行判断的工作量太大太耗时间。所以想到采用自定义注解的方式来实现一些需要的数据验证和过滤。

实现
1. 定义一个注解

我需要的功能有如下几点:

  1. 验证必填属性
  2. 数据可添加默认值
  3. 值域校验(正则表达式)
  4. 在对象中的某个属性不为空时,该值不可为空,实现值的关联性

所以设计了以下的自定义注解

/**
 * @author yixiangxuehai
 * 自定义注解实现
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.FIELD})
public @interface DefineAnnotation {
    //是否必填
    boolean isRequire() default false;

    //默认值实现
    String defaultValue() default "";

    //值域正则表达式
    String regular() default "";

    //在本类的其他其他对象存在时不能为空,多个以“,”隔开
    String notNullRegular() default "";

}

自定义注解必须要有默认值default
关于元注解的解释(meta-annotation)
  元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
    1.@Target,
    2.@Retention,
    3.@Documented,
    4.@Inherited
  这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Target:
   @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
  作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
  取值(ElementType)有:
    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
@Retention:
  @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
  作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
  取值(RetentionPoicy)有:
    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留)
  Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值
  Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理

2. 定义一个POJO对象

测试的javaBean设计如图:

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class AnnotionInfo {

    @JsonProperty("Name")
    @DefineAnnotation(isRequire = true)
    private String name;

    @JsonProperty("Age")
    @DefineAnnotation(notNullRegular = "birthDay")
    private Integer age;

    @JsonProperty("Birthday")
    @DefineAnnotation(regular = "^[0-9]{10}$", defaultValue = "1234567891")
    private String birthDay;

    @JsonProperty("Gender")
    @DefineAnnotation(regular = "0|1|2")
    private String gender;

    @JsonProperty("DefaultString")
    @DefineAnnotation(defaultValue = "ghdfagh")
    private String defaultString;
}

定义好实体类对象,并在需要的属性上写上自己的注解。

3. 实现注解验证过滤的公共方法
/**
     * 根据自定义注解检测数据
     * @param obj 需要检测的数据
     * @return 检测结果
     */
    private String inspectAnnotion(Object obj){
        StringBuilder stringBuilder = new StringBuilder();
        if(obj == null){
            return null;
        }
        Field[] fields = obj.getClass().getDeclaredFields();

       for(Field field : fields){  //获取每一个属性
           field.setAccessible(true);   //设置属性的访问权限
           DefineAnnotation defineAnnotation = field.getAnnotation(DefineAnnotation.class);
           if(defineAnnotation != null ){
           //包含自定义注解 DefineAnnotation 
               Type type = field.getType();
               try {
                    Object value = field.get(obj);      //获取对象属性值
                    if(value == null){
                        //属性没有值
                        if(defineAnnotation.isRequire()){
                            //必填
                            stringBuilder.append(field.getName() + "不能为空;");
                        }

                        //在某些属性不为空的情况下,属性值不为空
                        String notNullRegular = defineAnnotation.notNullRegular();
                        boolean fieldValue = getFieldValue(obj, notNullRegular);
                        if(!fieldValue){
                            stringBuilder.append(field.getName() + "在["+ notNullRegular + "]值不为空时,不能为空;");
                        }

                        //默认值设定
                        String defaultValue = defineAnnotation.defaultValue();
                        if(StringUtils.isNotEmpty(defaultValue)){
                            field.set(obj, changeValue(field, defaultValue));
                        }

                    } else {
                    	//属性有值
                        String regular =  defineAnnotation.regular();
                        //正则验证
                        if(StringUtils.isNotEmpty(regular)) {
                            Object fieldValue = field.get(obj);
                            Pattern pattern = Pattern.compile(regular);
                            Matcher matcher = pattern.matcher(getStringValue(field, fieldValue));
                            if (!matcher.matches()) {
                                stringBuilder.append(field.getName() + "不符合[" + regular + "]规则");
                            }
                        }
                    }
               } catch (Exception e){
                   System.out.println(field.getName() + "获取不到值");
                   e.printStackTrace();
               }
           }

       }

       return stringBuilder.toString();
    }
    
    /**
     * 根据属性名称获取属性值
     * @param obj
     * @param fieldName
     * @return
     */
    private boolean getFieldValue(Object obj, String fieldName){
        Field field  = null;
        if(StringUtils.isEmpty(fieldName)){
            return true;
        }
        for(String str : fieldName.split(",")) {
            try {
                field = obj.getClass().getDeclaredField(str);
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value == null) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

由于涉及到的数据有不同类型,但是注解中的正则表达式和默认值都是String,所以需要做对应数据的转换。
方法如下:

 /**
     * 基本数据类型转为String
     * @param field
     * @param fieldValue
     * @return
     */
    private String getStringValue(Field field, Object fieldValue){
        Type type = field.getType();
        String classNmae = ((Class) type).getName().substring(((Class) type).getName().indexOf('.') == -1 ? 0 : ((Class) type).getName().lastIndexOf('.') + 1);
        if("Integer".equals(classNmae) || "int".equals(classNmae)){
            return String.valueOf((Integer)fieldValue);
        }
        else if("Double".equals(classNmae) || "double".equals(classNmae)){
            return String.valueOf((Double) fieldValue);
        }
        else if("Short".equals(classNmae) || "short".equals(classNmae)){
            return String.valueOf((Short) fieldValue);
        }
        else if("Boolean".equals(classNmae) || "boolean".equals(classNmae)) {
            if ((boolean)fieldValue){
                return "1";
            }else {
                return "0";
            }
        }
        else if("Date".equals(classNmae)){
            return new SimpleDateFormat("yyyyMMddhhmmss").format((Date)fieldValue);
        }
        else if("String".equals(classNmae)){
            return (String) fieldValue;
        } else {
            try {
                throw new Exception("值域出错");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * String 值转为基本数据类型
     * @param field
     * @param fieldValue
     * @return
     */
    private Object changeValue(Field field, String fieldValue){
        Type type = field.getType();
        String classNmae = ((Class) type).getName().substring(((Class) type).getName().indexOf('.') == -1 ? 0 : ((Class) type).getName().lastIndexOf('.') + 1);
        if("Integer".equals(classNmae) || "int".equals(classNmae)){
            return Integer.valueOf(fieldValue);
        }
        else if("Double".equals(classNmae) || "double".equals(classNmae)){
            return Double.valueOf(fieldValue);
        }
        else if("Short".equals(classNmae) || "short".equals(classNmae)){
            return Short.valueOf(fieldValue);
        }
        else if("Boolean".equals(classNmae) || "boolean".equals(classNmae)) {
            if ("true".equals(fieldValue)){
                return true;
            }else {
                return false;
            }
        }
        else if("Date".equals(classNmae)){
            try {
                return new SimpleDateFormat("yyyyMMddhhmmss").parse(fieldValue);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        else if("String".equals(classNmae)){
            return (String) fieldValue;
        } else {
            try {
                throw new Exception("值域出错");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

这样就能实现一些简单的自定义数据过滤功能。
开始测试:
测试一:


    @Test
    public void  test(){

        AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
        /*annotionInfo.setDefaultString(null);
        annotionInfo.setAge(null);
        annotionInfo.setBirthDay(null);*/

        String result = inspectAnnotion(annotionInfo);
        System.out.println(annotionInfo.toString());
        System.out.println(result);
    }

得到结果为:

AnnotionInfo(name=12233, age=12, birthDay=1234567890, gender=5, defaultString=)
gender不符合[0|1|2]规则

测试二:


    @Test
    public void  test(){

        AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
        annotionInfo.setDefaultString(null);
        /*annotionInfo.setAge(null);
        annotionInfo.setBirthDay(null);*/

        String result = inspectAnnotion(annotionInfo);
        System.out.println(annotionInfo.toString());
        System.out.println(result);
    }

得到结果为:

AnnotionInfo(name=12233, age=12, birthDay=1234567890, gender=5, defaultString=ghdfagh)
gender不符合[0|1|2]规则

测试三:


    @Test
    public void  test(){

        AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
        annotionInfo.setDefaultString(null);
        annotionInfo.setAge(null);
        /*annotionInfo.setBirthDay(null);*/

        String result = inspectAnnotion(annotionInfo);
        System.out.println(annotionInfo.toString());
        System.out.println(result);
    }

得到结果为:

AnnotionInfo(name=12233, age=null, birthDay=1234567890, gender=5, defaultString=ghdfagh)
age在[birthDay]值不为空时,不能为空;gender不符合[0|1|2]规则

测试四:


    @Test
    public void  test(){

        AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
        annotionInfo.setDefaultString(null);
        annotionInfo.setAge(null);
        annotionInfo.setBirthDay(null);

        String result = inspectAnnotion(annotionInfo);
        System.out.println(annotionInfo.toString());
        System.out.println(result);
    }

得到结果为:

AnnotionInfo(name=12233, age=null, birthDay=1234567891, gender=5, defaultString=ghdfagh)
gender不符合[0|1|2]规则

至此结束,如有错误之处,请大家指正。如有更好的方法,请告知,谢谢。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值