网站建设竞标书,页面设计要会什么,怎么做简单网站首页,seo 优化案例作者简介#xff1a;大家好#xff0c;我是smart哥#xff0c;前中兴通讯、美团架构师#xff0c;现某互联网公司CTO 联系qq#xff1a;184480602#xff0c;加我进群#xff0c;大家一起学习#xff0c;一起进步#xff0c;一起对抗互联网寒冬 写一个接口#xff0c…作者简介大家好我是smart哥前中兴通讯、美团架构师现某互联网公司CTO 联系qq184480602加我进群大家一起学习一起进步一起对抗互联网寒冬 写一个接口大致就几个步骤
参数校验编写Service、DaoSQLResult封装返回值如果是分布式还可能涉及网关配置、服务引用等 业务代码总是变化的没太多可说的统一结果封装我们已经介绍过今天我们来聊聊参数校验的琐事。 老实说参数校验很烦不校验不行仔细校验吧代码又显得非常冗余很丑
PostMapping(insertUser)
public ResultBoolean insertUser(RequestBody User user) {if (user null) {return Result.error(ExceptionCodeEnum.EMPTY_PARAM);}if (user.getId() null || user.getId() 0) {return Result.error(id为空或小于0);}if (StringUtils.isEmpty(user.getName()) || user.getName().length() 4) {return Result.error(姓名不符合规范);}if (user.getAge() 18) {return Result.error(年龄不小于18);}if (StringUtils.isEmpty(user.getPhone()) || user.getPhone().length() ! 11) {return Result.error(手机号码不正确);}return Result.success(userService.save(user));
}
但无论以什么方式进行参数校验归根到底就是两种
手动校验自动校验 对应到实际编码的话推荐
封装ValidatorUtils使用Spring Validation 其实对于上面两种方式Spring都提供了解决方案。很多人只知道Spring Validation却不知道简单好用的Assert。
public class SpringAssertTest {/*** Spring提供的Assert工具类可以指定IllegalArgumentException的message** param args*/public static void main(String[] args) {String name ;
// Assert.hasText(name, 名字不能为空);Integer age null;
// Assert.notNull(age, 年龄不能为空);Integer height 180;Assert.isTrue(height 185, 身高不能低于185);}
} 只要在全局异常处理IllegalArgumentException即可。但个人觉得还是自己封装自由度高一些所以我们按照这个思路写一个ValidatorUtils。 封装ValidatorUtils
封装ValidatorUtils也有两种思路
校验并返回结果调用者自行处理校验失败直接抛异常 方式一校验并返回结果调用者自行处理
比如方法只返回true/false
public final class ValidatorUtils {private ValidatorUtils() {}/*** 校验id是否合法** param id*/public static boolean isNotId(Long id) {if (id null) {return true;}if (id 0) {return true;}return false;}
}
调用者根据返回值自行处理抛异常或者用Result封装
PostMapping(insertUser)
public ResultBoolean insertUser(RequestBody User user) {if (user null) {return Result.error(ExceptionCodeEnum.EMPTY_PARAM);}// 对校验结果进行判断并返回也可以抛异常让RestControllerAdvice兜底if (ValidatorUtils.isNotId(user.getId())) {return Result.error(id为空或小于0);}return Result.success(userService.save(user));
}
这种方式本质上和不封装差不多... 方式二校验失败直接抛异常
这种形式一般会结合RestControllerAdvice进行全局异常处理
public final class ValidatorUtils {private ValidatorUtils() {}// 错误信息模板private static final String IS_EMPTY %s不能为空;private static final String LESS_THAN_ZERO %s不能小于0;/*** 校验参数是否为null** param param* param fieldName*/public static void checkNull(Object param, String fieldName) {if (param null) {// ValidatorException是自定义异常throw new ValidatorException(String.format(IS_EMPTY, fieldName));}}/*** 校验id是否合法** param id* param fieldName*/public static void checkId(Long id, String fieldName) {if (id null) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (id 0) {throw new ValidatorException(String.format(LESS_THAN_ZERO, fieldName));}}
}
PostMapping(updateUser)
public ResultBoolean updateUser(RequestBody User user) {// 一连串的校验ValidatorUtils.checkNull(user, user);ValidatorUtils.checkId(user.getId(), 用户id);return Result.success(true);
}
Slf4j
RestControllerAdvice
public class GlobalExceptionHandler {/* 省略业务异常、运行时异常处理*//*** ValidatorUtils校验异常* see ValidatorUtils** param e* return*/ExceptionHandler(ValidatorException.class)public ResultExceptionCodeEnum handleValidatorException(ValidatorException e) {// 打印精确的参数错误日志方便后端排查log.warn(参数校验异常: {}, e.getMessage(), e);// 一般来说给客户端展示“参数错误”等泛化的错误信息即可联调时可以返回精确的信息e.getMessage()return Result.error(ExceptionCodeEnum.ERROR_PARAM);}
} 代码
具体选择哪种看个人喜好啦。这里给出第二种封装形式也可以改成第一种
public final class ValidatorUtils {private ValidatorUtils() {}private static final String IS_EMPTY %s不能为空;private static final String LESS_THAN_ZERO %s不能小于0;private static final String LENGTH_OUT_OF_RANGE %s长度要在%d~%d之间;private static final String LENGTH_LESS_THAN %s长度不能小于%d;private static final String LENGTH_GREATER_THAN %s长度不能大于%d;private static final String ILLEGAL_PARAM %s不符合规则;// 手机号码正则可以根据需要自行调整public static final String MOBILE 1\\d{10};/*** 是否为true** param expression* param message*/public static void isTrue(boolean expression, String message) {if (!expression) {throw new ValidatorException(message);}}/*** 校验参数是否为null** param param* param fieldName*/public static void checkNull(Object param, String fieldName) {if (param null) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}}/*** 校验参数是否为null或empty** param param* param fieldName*/public static void checkNullOrEmpty(Object param, String fieldName) {if (param null) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (param instanceof CharSequence) {if (param instanceof String null.equals(((String) param).toLowerCase())) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (isBlank((CharSequence) param)) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}}if (isCollectionsSupportType(param) sizeIsEmpty(param)) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}}/*** 校验id是否合法** param id* param fieldName*/public static void checkId(Long id, String fieldName) {if (id null) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (id 0) {throw new ValidatorException(String.format(LESS_THAN_ZERO, fieldName));}}/*** 校验id是否合法** param id* param fieldName*/public static void checkId(Integer id, String fieldName) {if (id null) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (id 0) {throw new ValidatorException(String.format(LESS_THAN_ZERO, fieldName));}}/*** 校验参数字符串** param param 字符串参数* param min 最小长度* param max 最大长度*/public static void checkLength(String param, int min, int max, String fieldName) {if (param null || .equals(param)) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}int length param.length();if (length min || length max) {throw new ValidatorException(String.format(LENGTH_OUT_OF_RANGE, fieldName, min, max));}}/*** 校验参数字符串** param param 字符串参数* param min 最小长度*/public static void checkMinLength(String param, int min, String fieldName) {if (param null || .equals(param)) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (param.length() min) {throw new ValidatorException(String.format(LENGTH_LESS_THAN, fieldName, min));}}/*** 校验参数字符串** param param 字符串参数* param max 最大长度*/public static void checkMaxLength(String param, int max, String fieldName) {if (param null || .equals(param)) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}if (param.length() max) {throw new ValidatorException(String.format(LENGTH_GREATER_THAN, fieldName, max));}}/*** 校验手机号是否合法** param phone 手机号*/public static void checkPhone(String phone, String fieldName) {if (phone null || .equals(phone)) {throw new ValidatorException(String.format(IS_EMPTY, fieldName));}boolean matches Pattern.matches(MOBILE, phone);if (!matches) {throw new ValidatorException(String.format(ILLEGAL_PARAM, fieldName));}}// --------- private method ----------private static boolean isBlank(CharSequence cs) {int strLen;if (cs ! null (strLen cs.length()) ! 0) {for (int i 0; i strLen; i) {if (!Character.isWhitespace(cs.charAt(i))) {return false;}}}return true;}private static boolean sizeIsEmpty(final Object object) {if (object null) {return true;} else if (object instanceof Collection?) {return ((Collection?) object).isEmpty();} else if (object instanceof Map?, ?) {return ((Map?, ?) object).isEmpty();} else if (object instanceof Object[]) {return ((Object[]) object).length 0;} else {try {return Array.getLength(object) 0;} catch (final IllegalArgumentException ex) {throw new IllegalArgumentException(Unsupported object type: object.getClass().getName());}}}private static boolean isCollectionsSupportType(Object value) {boolean isCollectionOrMap value instanceof Collection || value instanceof Map;return isCollectionOrMap || value.getClass().isArray();}
}
Getter
NoArgsConstructor
public class ValidatorException extends RuntimeException {/*** 自定义业务错误码*/private Integer code;/*** 系统源异常*/private Exception originException;/*** 完整的构造函数参数错误码参数错误信息源异常信息** param code 参数错误码* param message 参数错误信息* param originException 系统源异常*/public ValidatorException(Integer code, String message, Exception originException) {super(message);this.code code;this.originException originException;}/*** 构造函数错误枚举源异常信息** param codeEnum*/public ValidatorException(ExceptionCodeEnum codeEnum, Exception originException) {this(codeEnum.getCode(), codeEnum.getDesc(), originException);}/*** 构造函数参数错误信息源异常信息** param message 参数错误信息* param originException 系统源错误*/public ValidatorException(String message, Exception originException) {this(ExceptionCodeEnum.ERROR_PARAM.getCode(), message, originException);}/*** 构造函数错误枚举** param codeEnum 错误枚举*/public ValidatorException(ExceptionCodeEnum codeEnum) {this(codeEnum.getCode(), codeEnum.getDesc(), null);}/*** 构造函数参数错误信息** param message 参数错误信息*/public ValidatorException(String message) {this(ExceptionCodeEnum.ERROR_PARAM.getCode(), message, null);}
} Spring Validation
Spring也封装了一套基于注解的参数校验逻辑常用的有
ValidatedNotNullNotBlankNotEmptyPositiveLengthMaxMin 大家可能之前听说过Valid它和Validated有什么关系呢Valid是JSR303规定的Validated是Spring扩展的Validated相对来说功能更加强大推荐优先使用Validated。 SpringBoot2.3.x之前可以直接使用Validated及ValidSpringBoot2.3.x以后需要额外引入依赖
dependencygroupIdorg.hibernate/groupIdartifactIdhibernate-validator/artifactIdversion6.0.1.Final/version
/dependency GET散装参数校验ConstraintViolationException
实际开发中如果某个GET接口只有一两个参数可以使用“散装”的参数列表注意类上加Validated
Slf4j
Validated
RestController
public class UserController {GetMapping(getUser)public ResultUser getUser(NotNull(message 部门id不能为空) Long departmentId,NotNull(message 年龄不能为空)Max(value 35, message 年龄不超过35)Min(value 18, message 年龄不小于18) Integer age) {return Result.success(null);}
}
如果RestControllerAdvice没有捕获对应的异常会返回SpringBoot默认的异常JSON 服务端则抛出ConstraintViolationException 这样的提示不够友好我们可以按之前的思路为ConstraintViolationException进行全局异常处理
Slf4j
RestControllerAdvice
public class GlobalExceptionHandler {/* 省略业务异常、运行时异常等其他异常处理*//*** ConstraintViolationException异常** param e* return* see ValidatorUtils*/ExceptionHandler(ConstraintViolationException.class)public ResultExceptionCodeEnum handleConstraintViolationException(ConstraintViolationException e) {log.warn(参数错误: {}, e.getMessage(), e);// 一般只需返回泛化的错误信息比如“参数错误”return Result.error(ExceptionCodeEnum.ERROR_PARAM, e.getMessage());}
} 格式觉得丑的话可以自己调整。 GET DTO参数校验BindException
Data
public class User {NotNull(message id不能为空)private Long id;NotNull(message 年龄不能为空)Max(value 35, message 年龄不超过35)Min(value 18, message 年龄不小于18)private Integer age;
}
Slf4j
RestController
public class UserController {/*** 如果都是用DTO包装参数那么Controller可以不加Validated但建议还是都加上吧* 参数列表里用Validated或Valid都可以** param user* return*/GetMapping(getUser)public ResultUser getUser(Validated User user) {System.out.println(进来了);return Result.success(null);}
}
你会发现虽然参数校验确实生效了 但是全局异常似乎没有捕获到这个异常最终又交给了SpringBoot处理
{timestamp: 2021-02-08T02:57:27.02500:00,status: 400,error: Bad Request,message: ,path: /getUser
}
这是怎么回事呢 实际上从GET“散装参数”变成“DTO参数”后校验异常从ConstraintViolationException变成了BindException见上面的截图所以需要另外定义
Slf4j
RestControllerAdvice
public class GlobalExceptionHandler {/* 省略业务异常、运行时异常等其他异常处理*//*** BindException异常** param e* return*/ExceptionHandler(BindException.class)public ResultMapString, String validationBindException(BindException e) {ListFieldError fieldErrors e.getBindingResult().getFieldErrors();String message fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.joining( ));log.error(参数错误: {}, message, e);return Result.error(ExceptionCodeEnum.ERROR_PARAM, message);}
}
重新请求
{code: 10000,message: id不能为空 年龄不小于18,data: null
} POST参数校验MethodArgumentNotValidException
PostMapping(updateUser)
public ResultBoolean updateUser(Validated RequestBody User user) {System.out.println(进来了);return Result.success(null);
}
和GET DTO参数校验形式上一样但POST校验的异常又是另一种所以全局异常处理又要加一种
Slf4j
RestControllerAdvice
public class GlobalExceptionHandler {/* 省略业务异常、运行时异常等其他异常处理*//*** MethodArgumentNotValidException异常** param e* return*/ExceptionHandler(MethodArgumentNotValidException.class)public ResultMapString, String validationMethodArgumentNotValidException(MethodArgumentNotValidException e) {ListFieldError fieldErrors e.getBindingResult().getFieldErrors();String message fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.joining( ));log.error(参数错误: {}, message, e);return Result.error(ExceptionCodeEnum.ERROR_PARAM, message);}
}
代码
Slf4j
RestControllerAdvice
public class GlobalExceptionHandler {/*** 业务异常** param* return*/ExceptionHandler(BizException.class)public ResultExceptionCodeEnum handleBizException(BizException bizException) {log.warn(业务异常:{}, bizException.getMessage(), bizException);return Result.error(bizException.getError());}/*** 运行时异常** param e* return*/ExceptionHandler(RuntimeException.class)public ResultExceptionCodeEnum handleRunTimeException(RuntimeException e) {log.warn(运行时异常: {}, e.getMessage(), e);return Result.error(ExceptionCodeEnum.ERROR);}/*** ValidatorUtils校验异常** param e* return* see ValidatorUtils*/ExceptionHandler(ValidatorException.class)public ResultExceptionCodeEnum handleValidatorException(ValidatorException e) {// 打印精确的参数错误日志方便后端排查log.warn(参数校验异常: {}, e.getMessage(), e);// 一般来说给客户端展示泛化的错误信息即可联调时可以返回精确的信息return Result.error(e.getMessage());}/*** ConstraintViolationException异常散装GET参数校验** param e* return* see ValidatorUtils*/ExceptionHandler(ConstraintViolationException.class)public ResultExceptionCodeEnum handleConstraintViolationException(ConstraintViolationException e) {log.warn(参数错误: {}, e.getMessage(), e);return Result.error(ExceptionCodeEnum.ERROR_PARAM, e.getMessage());}/*** BindException异常GET DTO校验** param e* return*/ExceptionHandler(BindException.class)public ResultMapString, String validationBindException(BindException e) {ListFieldError fieldErrors e.getBindingResult().getFieldErrors();String message fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.joining( ));log.error(参数错误: {}, message, e);return Result.error(ExceptionCodeEnum.ERROR_PARAM, message);}/*** MethodArgumentNotValidException异常POST DTO校验** param e* return*/ExceptionHandler(MethodArgumentNotValidException.class)public ResultMapString, String validationMethodArgumentNotValidException(MethodArgumentNotValidException e) {ListFieldError fieldErrors e.getBindingResult().getFieldErrors();String message fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.joining( ));log.error(参数错误: {}, message, e);return Result.error(ExceptionCodeEnum.ERROR_PARAM, message);}} 其他校验场景
Spring Validation还有一些校验场景这里补充一下
嵌套校验分组校验List校验 嵌套校验
Validated不支持嵌套校验只能用Valid
Data
public class User {NotNull(message id不能为空)private Long id;NotNull(message 年龄不能为空)Max(value 35, message 年龄不超过35)Min(value 18, message 年龄不小于18)private Integer age;NotNull(message 所属部门不能为空)Validprivate Department department;Datastatic class Department {NotNull(message 部门编码不能为空)private Integer sn;NotBlank(message 部门名称不能为空)private String name;}
} 分组校验
Data
public class User {NotNull(message id不能为空, groups {Update.class})private Long id;NotNull(message 年龄不能为空, groups {Add.class, Update.class})Max(value 35, message 年龄不超过35, groups {Add.class, Update.class})Min(value 18, message 年龄不小于18, groups {Add.class, Update.class})private Integer age;public interface Add {}public interface Update {}
}
Slf4j
RestController
public class UserController {PostMapping(insertUser)public ResultBoolean insertUser(Validated(User.Add.class) RequestBody User user) {System.out.println(进来了);return Result.success(null);}PostMapping(updateUser)public ResultBoolean updateUser(Validated(User.Update.class) RequestBody User user) {System.out.println(进来了);return Result.success(null);}
} 有两点需要注意
interface Add这些接口只是做个标记本身没有任何实际意义可以抽取出来作为单独的接口复用interface Add还可以继承Default接口
Data
public class User {// 只在Update分组下生效NotNull(message id不能为空, groups {Update.class})private Long id;// 此时如果没执行Group那么无论什么分组都会校验NotNull(message 年龄不能为空)Max(value 35, message 年龄不超过35)Min(value 18, message 年龄不小于18)private Integer age;public interface Add extends Default {}public interface Update extends Default {}
} 继承Default后除非显示指定否则只要加了NotNull等注解就会起效。但显示指定Group后就按指定的分组进行校验。比如上面的id只会在update时校验生效。 个人不建议继承Default一方面是理解起来比较乱另一方是加了Default后就无法进行部分字段更新了。比如
PostMapping(updateUser)
public ResultBoolean updateUser(Validated(User.Update.class) RequestBody User user) {System.out.println(进来了);return Result.success(null);
}
Data
public class User {NotNull(message id不能为空, groups {Update.class})private Long id;NotNull(message 年龄不能为空)private Integer age;NotBlank(message 住址不能为空)private String address;public interface Add extends Default {}public interface Update extends Default {}
}
此时如果想更新name就不能只传id和name了address也要传默认也会校验。当然你也可以认为一般情况下update前都会有getById()所以更新时数据也是全量的。 List校验
Spring Validation不支持以下方式校验
Data
public class User {NotNull(message id不能为空)private Long id;NotNull(message 年龄不能为空)private Integer age;
}
PostMapping(updateBatchUser)
public ResultBoolean updateBatchUser(Validated RequestBody ListUser list) {System.out.println(list);return Result.success(null);
}
即使age不填还是进来了说明对于List而言Validated根本没作用 解决办法是借鉴嵌套校验的模式在List外面再包一层
PostMapping(updateBatchUser)
public ResultBoolean updateBatchUser(Validated RequestBody ValidationListUser userList) {System.out.println(userList);return Result.success(null);
}
public class ValidationListE implements ListE {NotEmpty(message 参数不能为空)Validprivate ListE list new LinkedList();Overridepublic int size() {return list.size();}Overridepublic boolean isEmpty() {return list.isEmpty();}Overridepublic boolean contains(Object o) {return list.contains(o);}Overridepublic IteratorE iterator() {return list.iterator();}Overridepublic Object[] toArray() {return list.toArray();}Overridepublic T T[] toArray(T[] a) {return list.toArray(a);}Overridepublic boolean add(E e) {return list.add(e);}Overridepublic boolean remove(Object o) {return list.remove(o);}Overridepublic boolean containsAll(Collection? c) {return list.containsAll(c);}Overridepublic boolean addAll(Collection? extends E c) {return list.addAll(c);}Overridepublic boolean addAll(int index, Collection? extends E c) {return list.addAll(index, c);}Overridepublic boolean removeAll(Collection? c) {return list.removeAll(c);}Overridepublic boolean retainAll(Collection? c) {return list.retainAll(c);}Overridepublic void clear() {list.clear();}Overridepublic E get(int index) {return list.get(index);}Overridepublic E set(int index, E element) {return list.set(index, element);}Overridepublic void add(int index, E element) {list.add(index, element);}Overridepublic E remove(int index) {return list.remove(index);}Overridepublic int indexOf(Object o) {return list.indexOf(o);}Overridepublic int lastIndexOf(Object o) {return list.lastIndexOf(o);}Overridepublic ListIteratorE listIterator() {return list.listIterator();}Overridepublic ListIteratorE listIterator(int index) {return list.listIterator(index);}Overridepublic ListE subList(int fromIndex, int toIndex) {return list.subList(fromIndex, toIndex);}public ListE getList() {return list;}public void setList(ListE list) {this.list list;}} 实际开发时建议专门建一个package存放Spring Validation相关的接口和类 SpringValidatorUtils封装
一起来封装一个SpringValidatorUtils
public final class SpringValidatorUtils {private SpringValidatorUtils() {}/*** 校验器*/private static final Validator validator Validation.buildDefaultValidatorFactory().getValidator();/*** 校验参数** param param 待校验的参数* param groups 分组校验比如Update.class可以不传* param T*/public static T void validate(T param, Class?... groups) {SetConstraintViolationT validateResult validator.validate(param, groups);if (!CollectionUtils.isEmpty(validateResult)) {StringBuilder validateMessage new StringBuilder();for (ConstraintViolationT constraintViolation : validateResult) {validateMessage.append(constraintViolation.getMessage()).append( );}// 去除末尾的 validateMessage.delete(validateMessage.length() - 4, validateMessage.length());// 抛给全局异常处理throw new ValidatorException(validateMessage.toString());}}
}
代码很简单做的事情本质是和Validated是一模一样的。Validated通过注解方式让Spring使用Validator帮我们校验而SpringValidatorUtils则是我们从Spring那借来Validator自己校验
PostMapping(insertUser)
public ResultBoolean insertUser(RequestBody User user) {SpringValidatorUtils.validate(user);System.out.println(进来了);return Result.success(null);
}
此时不需要加Validated。 买一送一看看我之前一个同事封装的工具类更加自由调用者决定抛异常还是返回错误信息
public final class ValidationUtils {private static final Validator DEFAULT_VALIDATOR Validation.buildDefaultValidatorFactory().getValidator();private ValidationUtils() {}/*** 验证基于注解的对象** param target*/public static T String validateReq(T target, boolean throwException) {if (null target) {return errorProcess(校验对象不能为空, throwException);} else {SetConstraintViolationT constraintViolations DEFAULT_VALIDATOR.validate(target);ConstraintViolationT constraintViolation Iterables.getFirst(constraintViolations, null);if (constraintViolation ! null) {// 用户可以指定抛异常还是返回错误信息return errorProcess(constraintViolation.getPropertyPath() : constraintViolation.getMessage(),throwException);}}return ;}private static String errorProcess(String errorMsg, boolean throwException) {if (throwException) {throw new InvalidParameterException(errorMsg);}return errorMsg;}
}
OK至此对Spring Validation的介绍结束。 为什么Validated这么方便还要封装这个工具类呢首先很多人搞不清楚Validated的使用或者觉得注解很碍眼不喜欢。其次也是最重要的如果你想在Service层做校验使用SpringValidatorUtils会方便些Service有接口和实现类麻烦些。当然Service也能用注解方式校验。 参数校验就介绍到这有更好的方式欢迎大家评论交流。我个人曾经特别喜欢Spring Validation后来觉得其实使用工具类也蛮好想校验啥就写啥很细腻不用考虑乱七八糟的分组而Spring Validation有时需要花费很多心思在分组上就有点本末倒置了。 最后抛出两个问题
写完才发现ValidatorUtils竟然用了static final抽取错误信息模板然后利用String.format()拼接。会出现线程安全问题吗你知道如何设计山寨版的Spring Validation吗只需要实现NotNull ValidatorUtils参考答案见评论区
作者简介大家好我是smart哥前中兴通讯、美团架构师现某互联网公司CTO 进群大家一起学习一起进步一起对抗互联网寒冬