Spring Validation框架+AOP实现Controller控制层入参校验

项目开发过程中,通常都涉及到表单提交时候前台传递的表单数据的数据合法性校验,这里说的合法性指的是数据合法性,不涉及业务逻辑上的合法性。为了避免在每个方法中都去写重复的校验逻辑,基于这个目的,使用了spring自带的validation校验框架+aop实现了一个通用的的入参校验处理方法。
这里实现基于springboot1.5.18.RELEASE,java 1.8,项目结构如下:

pom文件引入的依赖:

创建实体类:


import javax.validation.constraints.Max;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;

public class User {
    
    private String id;
    
    //校验字符串是否在指定的范围内
    @Length(min=0, max=20,message = "名字最大长度20")
    private String name;
    
    @Length(min=0, max=100,message = "地址最大长度100")
    private String address;
    
    //email校验,不符合格式则直接返回,为空不校验
    @Email(message = "邮箱格式不正确")
    private String email;
    
    //年龄最大值
    @Max(value = 150,message = "年龄超过最大值150")
    private Integer age;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", address=" + address + ", email=" + email + ", age=" + age + "]";
    }
}

自定义注解,用来创建切面时使用:


import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME) 
@Inherited
@Target(ElementType.METHOD)
public @interface ParamValidate {

}

创建切面类:


import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import cst.constant.ResponseConstant;


@Aspect
@Component
public class ParamValidateAspect {
    
    //使用java简单日志门面slf4j,springboot默认使用logback
    private static final Logger log = LoggerFactory.getLogger(ParamValidateAspect.class);
    
    //定义切面要切的方法为所有的带这个注解的方法
    @Pointcut("@annotation(cst.annotation.ParamValidate)")
    public void paramValidate() {}
    
    //切面逻辑
    @Before("paramValidate()")
    public void paramValidateBefore(JoinPoint point) {
        //获取方法入参的数据
        Object[] paramObjs = point.getArgs();
        StringBuffer buffer = new StringBuffer();
        //如果入参个数不为0
        if (paramObjs.length > 0) {
            for (Object object : paramObjs) {
                //如果是BindingResult类型的参数
                if (object instanceof BindingResult) {
                    BindingResult result = (BindingResult)object;
                    //如果有校验失败的信息
                    if (result.hasErrors()) {
                        //循环拼接所有的错误信息
                        List allErrors = result.getAllErrors();
                        for (ObjectError error : allErrors) {
                            buffer.append(error.getDefaultMessage()+";");
                        }
                    }
                    
                }
            }
        }
        //如果校验信息不为空,则直接返回请求
        String checkResult = buffer.toString();
        if (!StringUtils.isEmpty(checkResult)) {
            //获取到HttpServletResponse对象
            ServletRequestAttributes res = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = res.getResponse();
            //设置编码格式
            response.setCharacterEncoding("UTF-8");
            //设置应答头类型
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            //根据实际情况拼接要返回的json字符串,这里因为返回使用了自定义的ReturnData实体,所以拼接成这种实体的json格式
            String returnData = "{" + 
                                    "\"code\":" + "\""+ ResponseConstant.FAILURE_CODE+ "\"" + "," +
                                    "\"msg\":" + "\""+ checkResult + "\"" + "," +
                                    "\"data\":" + "\""+ checkResult + "\""+"," +
                                    "\"pages\": null" + 
                                 "}";
            //将请求返回
            OutputStream output = null;
            try {
                output = response.getOutputStream();
                output.write(returnData.getBytes("UTF-8"));
            } catch (Exception e) {
                log.error(e.getMessage());
            } finally {
                try {
                    if (output != null) {
                        output.close();
                    }
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
    }
}

要使用的返回值的统一实体和返回值常量类:

/**
 * 返回到前台的信息实体
 * @author Administrator
 *
 * @param 
 */
public class ReturnData {
    //状态码,0为请求成功,其他均失败
    private Integer code;
    //返回的附加信息
    private String msg;
    //要返回的数据
    private T data;
    
    private Integer pages;
    //无参构造
    public ReturnData() {}
    
    //有参构造
    public ReturnData(int code,String msg,T t){
        this.code=code;
        this.msg=msg;
        this.data=t;
    }
    public ReturnData(int code,String msg,T t,int pages){
        this.code=code;
        this.msg=msg;
        this.data=t;
        this.pages=pages;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

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

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Integer getPages() {
        return pages;
    }

    public void setPages(Integer pages) {
        this.pages = pages;
    }

    @Override
    public String toString() {
        return "ReturnData [code=" + code + ", msg=" + msg + ", data=" + data + ", pages=" + pages + "]";
    }
}
/**
 * 返回到前台信息的实体常量类
 */
public class ResponseConstant {
    //请求成功的code
    public static final Integer SUCCESS_CODE=0;
    //请求失败的code
    public static final Integer FAILURE_CODE=1;
    //请求成功的msg
    public static final String SUCCESS_MESSAGE="操作成功!";
    //请求失败的msg
    public static final String FAILURE_MESSAGE="操作失败!";
}

创建的测试controller:



import javax.validation.Valid;

import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cst.annotation.ParamValidate;
import cst.constant.ResponseConstant;
import cst.entity.ReturnData;
import cst.entity.User;


@RestController
@RequestMapping(value = "/user")
public class UserController {
    
    @ParamValidate//这个方法上定义这个注解,切面注解
    @RequestMapping(value = "/saveUser")
    //这里注意使用@Valid 和 BindingResult,这有对应关系
    public ReturnData saveUser(@Valid @RequestBody User user,BindingResult result) {
        return new ReturnData(ResponseConstant.SUCCESS_CODE,ResponseConstant.SUCCESS_MESSAGE,ResponseConstant.SUCCESS_MESSAGE);
    }
}

使用postman模拟表单提交进行测试,请求头设置:

请求返回结果:

常见的注解如下,基本满足需求:
JSR提供的校验注解:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

以上是一种最简单的实现思路,具体可以根据自己需求进一步优化。

你可能感兴趣的:(Spring Validation框架+AOP实现Controller控制层入参校验)