springboot统一表单数据校验
统一表单数据校验
在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦:
- 验证代码繁琐,重复劳动
- 方法内代码显得冗长
- 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码
hibernate validator(官方文档)提供了一套比较完善、便捷的验证实现方式。
spring-boot-starter-web
包里面有hibernate-validator
包,不需要引用hibernate validator依赖。
于是编写了一套校验工具类,并配置了切面做统一的校验,无需在每次手动调用校验方法。
校验工具类 ValidatorUtil
import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.groups.Default; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * @author lism * @date 2018年8月29日10:25:34 * bean 校验工具类 */ public class ValidatorUtil { private static Validator validator = Validation.buildDefaultValidatorFactory() .getValidator(); /** * 校验bean * @param bean * @param <T> * @return */ public static <T> List<ValidateBean> validate(T bean) { Set<ConstraintViolation<T>> constraintViolations = validator.validate(bean, Default.class); return errors2ValidateBeanList(constraintViolations); } /** * 校验属性 * @param bean * @param property * @param <T> * @return */ public static <T> List<ValidateBean> validateProperty(T bean, String property) { Set<ConstraintViolation<T>> constraintViolations = validator.validateProperty(bean, property, Default.class); return errors2ValidateBeanList(constraintViolations); } /** * 校验属性值 * @param bean * @param property * @param propertyValue * @param <T> * @return */ public static <T> List<ValidateBean> validateValue(T bean, String property, Object propertyValue) { Set<? extends ConstraintViolation<?>> constraintViolations = validator.validateValue(bean.getClass(), property, propertyValue, Default.class); return errors2ValidateBeanList(constraintViolations); } private static <T> List<ValidateBean> errors2ValidateBeanList(Set<? extends ConstraintViolation<?>> errors) { List<ValidateBean> validateBeans = new ArrayList<>(); if (errors != null && errors.size() > 0) { for (ConstraintViolation<?> cv : errors) { //这里循环获取错误信息,可以自定义格式 String property = cv.getPropertyPath().toString(); String message = cv.getMessage(); validateBeans.add(new ValidateBean(property, message)); } } return validateBeans; } }
校验结果辅助类 ValidateBean
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ValidateBean { private String property; private String message; @Override public String toString() { final StringBuilder sb = new StringBuilder("{"); sb.append("property='").append(property).append('\''); sb.append(", message='").append(message).append('\''); sb.append('}'); return sb.toString(); } }
统一数据校验和异常处理切面
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.List; import java.util.Optional; /** * 统一数据校验和异常处理 * 控制层无需再进行数据校验和异常捕获 * @author lism */ @Aspect @Component @Slf4j @Order(1) public class ValidateBeanAspect { /** * 定义一个切入点 */ @Pointcut("execution(* org.lism..controller..*.*(..))") private void anyMethod() { } @Around("anyMethod()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); if (args.length > 0) { Optional<List<ValidateBean>> optional = Arrays.stream(args).filter(arg -> { return !(arg == null ||arg instanceof HttpServletRequest || arg instanceof HttpServletResponse); }).map(arg -> { return ValidatorUtil.validate(arg); }).filter(validateBeans -> { return validateBeans.size() > 0; }).findFirst(); if (optional.isPresent()) { return new ResponseBean(RTCodeEnum.CODE_FAIL.getCode(), optional.get().toString()); } } try { Object proceed = pjp.proceed(args); if (proceed instanceof ResponseBean) { return proceed; } else { return new ResponseBean(proceed, RTCodeEnum.CODE_200); } } catch (BaseException e) { RequestContextUtil.writeToResponse(new ResponseBean<>(e.getCode(), e.getMessage()).toString()); log.error(e.getMessage()); // return new ResponseBean<>(e.getCode(), e.getMessage()); return null; } catch (Exception e) { log.error(e.getMessage()); RequestContextUtil.writeToResponse(new ResponseBean<>(RTCodeEnum.CODE_FAIL.getCode(), e.getMessage())); // return new ResponseBean<>(RTCodeEnum.CODE_FAIL.getCode(), e.getMessage()); return null; } } @Before("anyMethod()") public void doBefore(JoinPoint pjp) throws Throwable { } @AfterReturning("anyMethod()") public void doAfterReturning(JoinPoint pjp) throws Throwable { } }
RequestContextUtil工具类
import com.alibaba.fastjson.JSON; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * RequestContext工具类 * @Author lism * @Date 2018/8/29 17:04 */ public class RequestContextUtil { public static ServletRequestAttributes getRequestAttributes() { return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); } /** * 获取Request * @return */ public static HttpServletRequest getRequest() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getRequest(); } else { return null; } } /** * 获取Response * @return */ public static HttpServletResponse getResponse() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getResponse(); } else { return null; } } /** * 获取SessionId * @return */ public static String getSessionId() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getSessionId(); } else { return null; } } /** * 往前端写数据 * @param object */ public static void writeToResponse(Object object) { PrintWriter writer = null; try { HttpServletResponse response = RequestContextUtil.getResponse(); response.setCharacterEncoding("utf-8"); response.setHeader("Content-type", "text/html;charset=utf-8"); writer = response.getWriter(); writer.write(JSON.toJSONString(object)); writer.flush(); } catch (IOException e) { e.printStackTrace(); }finally { writer.close(); } } }
返回code 枚举类
import com.alibaba.fastjson.JSONObject; /** */ public enum RTCodeEnum { CODE_OK(0, "OK"), // CODE_DONE(1, "Done"), // CODE_FAIL(-1, "Failed"), CODE_PARAM_ERROR(300, "Input Param Error"), CODE_TOKEN_ERROR(301, "Token Validation Error"), CODE_CAPTCHA_ERROR(302, "验证码错误,请重试"), CODE_DATA_VALIDATE_FAILED(303, "数据校验未通过"), CODE_STATE_EXIST(305, "请勿重复请求"), // Data Issue: 4** CODE_400(400, "服务404"), CODE_DATA_ERROR_PAGETIME_EXPIRE(401, "页面超时不可用,请刷新重试"), // System Service Issue: 5** CODE_SERVICE_NOT_AVAILABLE(500, "系统服务不可用,请联系管理员"), CODE_200(200,"成功"), CODE_401(401,"未登录,需要登录"), CODE_405(405, "权限不足"), CODE_406(406,"客户端请求接口参数不正确或缺少参数"), CODE_501(501,"服务器接口错误"), CODE_999(999,"保留码"); private int code; private String desc; RTCodeEnum(int code, String desc) { this.code = code; this.desc = desc; } public JSONObject toJSON() { JSONObject jsonObject = new JSONObject(); jsonObject.put("code", code); jsonObject.put("desc", desc); return jsonObject; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } }
ResponseBean工具类
import com.alibaba.fastjson.annotation.JSONField; import com.google.common.base.MoreObjects; import java.util.Date; /** * Created by lism 2018/8/23. */ public class ResponseBean<T> { private int code = 200; private String msg; private T data; @JSONField(format="yyyy-MM-dd HH:mm:ss") private Date date = new Date(); public static ResponseBean me(Object data){ return new ResponseBean(data); } public ResponseBean(T data) { this.data = data; } public ResponseBean(T data, RTCodeEnum rtCodeEnum) { this.data = data; this.msg = rtCodeEnum.getDesc(); this.code = rtCodeEnum.getCode(); } public ResponseBean(RTCodeEnum rtCodeEnum) { this.msg = rtCodeEnum.getDesc(); this.code = rtCodeEnum.getCode(); } public ResponseBean(T data, int code, String msg) { this.data = data; this.code = code; this.msg = msg; } public ResponseBean(int code, String msg) { this.code = code; this.msg = msg; } public ResponseBean() { } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Date getDate() { return date; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("code", code) .add("msg", msg) .add("data", data) .toString(); } }
1.校验实体类型参数
编写测试实体
import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.persistence.Entity; import javax.persistence.Table; import javax.validation.constraints.NotEmpty; @Entity @Table(name = "t_subject") @Data public class Subject extends BaseEntityModel { @NotEmpty(message = "专题名不能为空") @Length(min = 1, max = 20, message = "专题名1-20个字符之间") private String name; private String keyword; private String[] docType; private String startTime; private String endTime; }
测试控制器
@RestController @RequestMapping(value = "/subject") public class SubjectController { @RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json", consumes = "application/json") @ResponseBody @Override public ResponseBean create(@RequestBody Subject model) { return super.create(model); }
输入一个空的名子进行测试,可以看到返回结果
2.校验RequestParam 类型请求参数
ValidatorUtil
方式无法校验RequestParam 请求方法,在BaseController 加上@Validated注解
@RestController @Validated public class BaseController { }
在子控制器写测试方法
@RestController @RequestMapping(value = "/subject") public class SubjectController extends BaseController { /** * test4 * @return */ @RequestMapping(value = "/test3/", method = RequestMethod.GET, produces = "application/json") @ResponseBody public void test3(HttpServletRequest request, HttpServletResponse response, @Length(min = 1, max = 20, message = "专题名1-20个字符之间") @RequestParam String name) { log.info("test4"); }
通过测试看到:验证不通过时,抛出了ConstraintViolationException异常,所以我们在ValidateBeanAspect
中使用统一的捕获异常是可以捕获到异常,并调用RequestContextUtil.writeToResponse
方法将异常写到前台。
测试结果:
参考 https://www.cnblogs.com/mr-yang-localhost/p/7812038.html
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java性能优化之编程技巧总结
1、慎用异常 在Java软件开发中,经常使用 try-catch 进行错误捕获,但是,try-catch 语句对系统性能而言是非常糟糕的。虽然在一次 try-catch中,无法察觉到它对性能带来的损失,但是,一旦try-catch被应用于循环之中,就会给系统性能带来极大的伤害。 以下是一段将try-catch应用于for循环内的示例 public void test() { int a = 0; try { for (int i = 0; i < 1000000; i++) { a = a + 1; System.out.println(i); } } catch (Exception e) { e.printStackTrace(); } } 这段代码我运行时间是 27211 ms。如果将try-catch移到循环体外,那么就能提升系统性能,如下代码 public void test() { int a = 0; for (int i = 0; i < 1000000; i++) { try { a = a + 1; System.out.println(i); } ca...
- 下一篇
Java源码阅读之HashMap - JDK1.8
阅读优秀的源码是提升编程技巧的重要手段之一。 如有不对的地方,欢迎指正~ 转载请注明出处https://blog.lzoro.com。 前言 基于JDK1.8。 基本说明 常量 以下常量皆为HashMap类中定义 常量 默认值 说明 DEFAULT_INITIAL_CAPACITY 1<<4=(16) 默认初始容量 MAXIMUM_CAPACITY 1 << 30 最大容量 DEFAULT_LOAD_FACTOR 0.75 默认负载因子(当存储比例超过该参数时会触发hashmap扩容) TREEIFY_THRESHOLD 8 链表 -> 树化阈值 UNTREEIFY_THRESHOLD 6 树 -> 链表化阈值 MIN_TREEIFY_CAPACITY 64 树化后表格最小容量(至少4倍于TREEIFY_THRESHOLD) 节点(静态内部类) HashMap的实际负责K,V存储的是transient Node<K,V>[] table,而这里的Node<K,V>则是HashMap的一个静态内部类,如下 /** * 基本Has...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker快速安装Oracle11G,搭建oracle11g学习环境