注解+反射 参数校验更加简洁

Posted 光光-Leo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了注解+反射 参数校验更加简洁相关的知识,希望对你有一定的参考价值。

背景

做RPC接口的时候 我们需要对一些字段做非空校验 在字段很多的情况下 如果一个一个的用if判断 代码会很恶心 所以我们需要有一种便捷的方式去实现这个功能 比如使用注解+反射的方式

怎么做?

首先定义注解
非空注解:

package com.api.annotation;

import java.lang.annotation.*;

/**
 * 非空校验注解
 */

@Target(ElementType.METHOD, ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull 
    String message() default "";

数值注解:

package com.api.annotation;

import java.lang.annotation.*;

/**
 * 数字类型校验注解
 */

@Target(ElementType.METHOD, ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Number 
    String message() default "";

日期注解:

package com.api.annotation;

import java.lang.annotation.*;

/**
 * 日期格式校验注解
 */

@Target(ElementType.METHOD, ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Date 
    String message() default "";
    String format() default "yyyy-MM-dd HH:mm:ss";

接下来定义一下校验的返回值

public class BaseResultInfo 
    /**
     * 响应码
     */
    protected int code = 200;

    /**
     * 响应消息
     */
    protected String msg = "OK";
    public int getCode() 
        return code;
    
    public void setCode(int code) 
        this.code = code;
    
    public String getMsg() 
        return msg;
    
    public void setMsg(String msg) 
        this.msg = msg;
    

再定义一个返回码的枚举:

public enum ResultCodeEnum 

    SUCCESS(200,"成功"),
    PARAM_ERROR(1,"参数异常!!"),
    SYSTEM_ERROR(2,"系统异常!");
    ResultCodeEnum(int code, String msg) 
        this.code = code;
        this.msg = msg;
    

    /**
     * 返回code
     */
    private int code;

    /**
     * 错误消息
     */
    private String msg;

    public int getCode() 
        return code;
    

    public void setCode(int code) 
        this.code = code;
    

    public String getMsg() 
        return msg;
    

    public void setMsg(String msg) 
        this.msg = msg;
    

接下来定义一个基类 包含一个校验方法 通过反射来对字段进行校验

@Service
public class BaseService 

    /**
     * 验证某个bean的参数
     *
     * @param object 被校验的参数
     */
    /**
     * 验证某个bean的参数
     * @param object        校验bean对象
     * @param resultInfo    校验结果
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> BaseResultInfo validate(T object, BaseResultInfo resultInfo) throws Exception 

        if(object == null)
            resultInfo.setCode(ResultCodeEnum.PARAM_ERROR.getCode());
            resultInfo.setMsg("入参对象不能为null");
            return resultInfo;
        

        //获取object的类型
        Class<? extends Object> clazz = object.getClass();
        //获取该类型声明的成员
        Field[] fields = clazz.getDeclaredFields();
        //遍历属性
        for (Field field : fields) 
            //对于private私有化的成员变量,通过setAccessible来修改器访问权限
            field.setAccessible(true);
            if(!validate(field, object, resultInfo))
                return resultInfo;
            ;
            //重新设置会私有权限
            field.setAccessible(false);
        

        return resultInfo;
    


    /**
     * 校验bean对象注解
     * @param field
     * @param object
     * @param resultInfo
     * @throws Exception
     */
    private <T> boolean validate(Field field, T object, BaseResultInfo resultInfo) throws Exception 

        if(field.isAnnotationPresent(NotNull.class))
            NotNull annotation = field.getAnnotation(NotNull.class);
            if(field.get(object) == null || StringUtils.isBlank(field.get(object).toString()))
                resultInfo.setCode(ResultCodeEnum.PARAM_ERROR.getCode());
                                resultInfo.setMsg(StringUtils.isEmpty(annotation.message()) ? field.getName()+"不可为空" : annotation.message() );

                return false;
            
        

        if(field.isAnnotationPresent(Date.class))
            Date annotation = field.getAnnotation(Date.class);
            SimpleDateFormat format = new SimpleDateFormat(annotation.format());
            try 
                if(field.get(object) != null)
                    format.parse(field.get(object).toString());
                
             catch (ParseException e) 
                resultInfo.setCode(ResultCodeEnum.PARAM_ERROR.getCode());
                resultInfo.setMsg(annotation.message());
                return false;
            

        

        if(field.isAnnotationPresent(Number.class))
            Number annotation = field.getAnnotation(Number.class);
            if(field.get(object) != null)
                try
                    new BigDecimal(field.get(object).toString());
                catch (Exception e)
                    resultInfo.setCode(ResultCodeEnum.PARAM_ERROR.getCode());
                                    resultInfo.setMsg(StringUtils.isEmpty(annotation.message()) ? field.getName()+"必须是数值" : annotation.message() );

                    return false;
                
            
        

        return true;

    



准备工作做好了 接下来看下怎么用
随便定义一个方法的入参,对于thrift等接口,数值类型如果使用double会丢失精度,而且也不支持date类型,所以我们一般都是使用String类型(这里仅仅是参考 没有加thrift的相关注解)

import com.api.annotation.Date;
import com.api.annotation.NotNull;
import com.api.annotation.Number;

public class ApplyRequest 
    @NotNull
    private String applyNum;
    @NotNull(message = "描述不可以为空")
    private String description;
    @Number
    private String amount;
    @Date(format = "yyyy-MM")
    private String periodDate;
    @Date
    private String creationDate;

    public String getApplyNum() 
        return applyNum;
    

    public void setApplyNum(String applyNum) 
        this.applyNum = applyNum;
    

    public String getAmount() 
        return amount;
    

    public void setAmount(String amount) 
        this.amount = amount;
    

    public String getPeriodDate() 
        return periodDate;
    

    public void setPeriodDate(String periodDate) 
        this.periodDate = periodDate;
    

    public String getCreationDate() 
        return creationDate;
    

    public void setCreationDate(String creationDate) 
        this.creationDate = creationDate;
    


然后我们只需要让接口的实现类继承BaseService类,然后在方法内部调用父类的validate方法即可进行参数的一些基础校验

以上是关于注解+反射 参数校验更加简洁的主要内容,如果未能解决你的问题,请参考以下文章

自定义注解结合SpringAop实现权限,参数校验,日志等等功能

更简洁的参数校验,使用 FluentValidation 对参数进行校验

瞧瞧人家用SpringBoot写的后端API接口,那叫一个优雅

Java自定义注解反射校验数据

Android注解方式实现表单校验

自定义validation注解:解决动态多字段联动校验问题