当前位置: 首页 > wzjs >正文

不用源码做网站注册公司流程和费用时间

不用源码做网站,注册公司流程和费用时间,邯山企业做网站推广,建网站书籍1.概述 书接上回,我们总结了后端接口参数校验的重要性,详解讲述了Spring Boot项目中如何整合Spring-Validator组件进行参数校验,实战教程:后端接口没做参数检验导致服务雪崩,被批评代码健壮性太差… 因为参数校验是W…

1.概述

书接上回,我们总结了后端接口参数校验的重要性,详解讲述了Spring Boot项目中如何整合Spring-Validator组件进行参数校验,实战教程:后端接口没做参数检验导致服务雪崩,被批评代码健壮性太差…

因为参数校验是Web开发中保证数据完整性和安全性的重要环节,所以Spring Boot基于**JSR-380(Bean Validation 2.0)**规范,提供了强大的参数校验机制,支持:

声明式校验(通过注解)

嵌套校验(参数对象多级)

分组校验(不同场景不同规则)

自定义校验逻辑(扩展ConstraintValidator

国际化错误消息(支持多语言)

✔的已经在入门实战教程中总结过了,不清楚的自行跳转查看,今天我们结合实际项目开发,谈谈自定义注解校验特定场景规则和国际化多语言错误消息处理。在此之前,我们先来看看Spring Boot参数校验原理

Spring Boot参数校验原理

接口参数校验属于web应用的范畴,所以对于最常用的@RequestBody参数对象校验肯定是在Spring MVC组件中实现的,RequestResponseBodyMethodProcessor是用来解析参数@RequestBody和处理响应@ResponseBody的核心所在,所以参数校验的逻辑也一定在这里解析参数的方法里:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {......@Overridepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional();// 入参转换成对象Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());String name = Conventions.getVariableNameForParameter(parameter);if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);if (arg != null) {// 参数检验validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}if (mavContainer != null) {mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());}}return adaptArgumentIfNecessary(arg, parameter);}......
}

参数检验的方法:validateIfApplicable(binder, parameter):

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {// 获取参数注解Annotation[] annotations = parameter.getParameterAnnotations();for (Annotation ann : annotations) {// 获取@Validated注解Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);// 有@Validated直接开启校验。// 没有再判断参数前是否有Valid起头的注解。if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});// 执行校验binder.validate(validationHints);break;}}
}

这里也是@Validated@Valid都能开启参数检验的逻辑所在。

跟着执行校验代码binder.validate(validationHints), 最终来到了LocalValidatorFactoryBean的验证方法:

@Override
public void validate(Object target, Errors errors, Object... validationHints) {if (this.targetValidator != null) {processConstraintViolations(// 进入Hibernate Validator执行真正的校验this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);}
}

来到MetaConstraintdoValidateConstraint()方法:

  private boolean doValidateConstraint(ValidationContext<?> executionContext, ValueContext<?, ?> valueContext) {valueContext.setConstraintLocationKind(this.getConstraintLocationKind());boolean validationResult = this.constraintTree.validateConstraints(executionContext, valueContext);return validationResult;}

最后执行ConstraintTreevalidateSingleConstraint():

  protected final <V> Optional<ConstraintValidatorContextImpl> validateSingleConstraint(ValueContext<?, ?> valueContext, ConstraintValidatorContextImpl constraintValidatorContext, ConstraintValidator<A, V> validator) {boolean isValid;try {// 获取参数值V validatedValue = (V)valueContext.getCurrentValidatedValue();// 具体约束逻辑实现isValid = validator.isValid(validatedValue, constraintValidatorContext);} catch (RuntimeException e) {if (e instanceof ConstraintDeclarationException) {throw e;}throw LOG.getExceptionDuringIsValidCallException(e);}return !isValid ? Optional.of(constraintValidatorContext) : Optional.empty();}

可以看到真正验证参数是否合法逻辑在ConstraintValidatorisValid(),这也是我们自定义注解验证特定场景需要实现的接口逻辑所在哦。

关于方法级别requestParam/PathVariable参数校验大家自行了解,入口在MethodValidationPostProcessor这个切面

3.自定义注解验证进阶实践

在真实的项目开发中,业务场景需求是多种多样的,校验框架提供的原生注解不一定能满足复杂场景的校验,这时候我们只能自定义一个注解来实现该场景的参数校验,这里我列举两个实际开发中经常用到的。

3.1 枚举值合法性校验

在接口入参中,枚举字段很常见,比如入门教程里面的userParam的性别字段:

/** 性别  0:男生  1:女生 */
@NotNull(message = "性别不能为空")
private Integer gender;

使用了@NotNull校验了参数不能为空,但是并没有对性别的枚举值进行校验,要是传一个2上来没校验直接落库,就会产生了非法数据”不男不女“了,后患无穷~~~所以我们需要对参数值进行校验,必须武装严谨到牙齿,哈哈。

基于上面的实现原理和原生注解的实现套路,我们首先先定一个注解标记验证字段:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {String message() default "enum value is not valid";/** 关联的枚举类  CheckEnumValue是我们定义的公共枚举接口,所以枚举类都要实现提供返回枚举值的方法   */Class<? extends CheckEnumValue> linkEnum() default CheckEnumValue.class;Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };
}

该注解和框架原生提供的注解套路几乎一致,这里只是多了一个验证字段关联的枚举类的属性linkEnum

接下来定义具体约束校验逻辑:

public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {private Class<? extends CheckEnumValue> clz;@Overridepublic void initialize(EnumValue constraintAnnotation) {clz = constraintAnnotation.linkEnum();}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {// 参数值为空不校验if (Objects.isNull(value)) {return true;}// 关联的不是枚举类不校验if (!clz.isEnum()) {return true;}CheckEnumValue[] enumConstants = clz.getEnumConstants();if (enumConstants == null || enumConstants.length == 0) {return true;}CheckEnumValue enumConstant = enumConstants[0];List enumValue = enumConstant.getEnumValue();// 判断参数值是否在枚举值中if (CollectionUtils.isEmpty(enumValue)) {return true;}if (enumValue.contains(value)) {return true;}return false;}
}

枚举校验的公共接口,枚举类都要实现该接口,该接口返回所有枚举值

public interface CheckEnumValue<T> {List<T> getEnumValue();}

性别的枚举类如下:

public enum GenderEnum implements CheckEnumValue<Integer> {MAN(0, "男生"),WOMAN(1, "女生");private Integer code;private String name;GenderEnum(Integer code, String name) {this.code = code;this.name = name;}@Overridepublic List<Integer> getEnumValue() {return Stream.of(GenderEnum.values()).map(genderEnum -> genderEnum.code).collect(Collectors.toList());}
}

接下来我们就可以在接口参数对象使用自定义注解进行枚举字段校验了

/** 性别  0:男生  1:女生 */
@EnumValue(message = "性别枚举值不对", linkEnum = GenderEnum.class)
@NotNull(message = "性别不能为空")
private Integer gender;

调接口输入参数:

{"gender":2,
}

接口放回结果如下:

{"code": 400,"msg": "Bad Request","data": {"gender": "性别枚举值不对"}
}

可以看出,我们自定义注解正常运作了。

3.2 字段联合校验

上篇文章中我们提到了因为后端没有参数校验导致服务崩溃不可用,其实接口是使用了validator进行参数校验的,但有一种特殊情况,当其中一个字段的入参等于某个值的时候,另一个字段不能为空,这种情况框架提供的基本注解解决不了,只能在接口代码写代码判断,还是userParam为例,要求输入性别为女生gender=1时,出生日期birthday必传:

if (Objects.equals(userParam.getGender(), 1) && Objects.isNull(userParam.getBirthday())) {throw new BizException("出生日期不能为空");}

可惜用了框架校验,再在接口里面写参数校验代码就显得繁杂,不够优雅,所以就没写最后就出问题了~~~

要想解决这个问题,我们只能自定义注解来实现这个复杂场景校验,同时又要不失优雅。

首先定义一个多字段组合验证注解

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CombineNotNullValidator.class})
public @interface CombineNotNull {String message() default "enum value is not valid";/** Spring SpEL表达式   */@Language("SpEL")String condition() default "";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };
}

实现约束校验逻辑:

public class CombineNotNullValidator implements ConstraintValidator<CombineNotNull, Object> {// 解析SpEL有一定开销, 缓存表达式private static final ConcurrentHashMap<String, Expression> EXPRESSION_CACHE = new ConcurrentHashMap<>();// SpelExpressionParser是线程安全的private static final ExpressionParser parser = new SpelExpressionParser();// 条件表达式字符串private String condition;@Overridepublic void initialize(CombineNotNull constraintAnnotation) {condition = constraintAnnotation.condition();}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {// 表达式为空if (StringUtils.isBlank(condition)) {return true;}// 获取入参对象(被校验的对象)RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();Object requestBody = requestAttributes.getAttribute("request_body", RequestAttributes.SCOPE_REQUEST);if (requestBody == null) {return true;}Expression expression = EXPRESSION_CACHE.computeIfAbsent(condition,k -> parser.parseExpression(k));Boolean result = expression.getValue(requestBody, Boolean.class);if (Boolean.FALSE.equals(result)) {return true;}return value != null;}
}

框架提供的ConstraintValidatorContext context上下文并没有提供入参对象,直接获取不了,所以只能我们自己实现传递入参对象上下文,实现传递方式很多,大家可以自行实现,我这里使用RequestBodyAdvice实现:

@RestControllerAdvice
public class RequestBodyHandlerAdvice implements RequestBodyAdvice {@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {return inputMessage;}@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {// 判断接口有没有开启接口参数校验Valid valid = parameter.getParameterAnnotation(Valid.class);Validated validated = parameter.getParameterAnnotation(Validated.class);if (valid != null || validated != null) {// 传递入参对象RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();requestAttributes.setAttribute("request_body", body, RequestAttributes.SCOPE_REQUEST);}return body;}@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return null;}
}

RequestBodyAdvice不太了解的,可以查看我们之前的总结:一文带你掌握SpringMVC扩展点RequestBodyAdvice和ResponseBodyAdvice如何使用及实现原理

接下来就可以接口参数类上直接使用:

@CombineNotNull(message = "出生日期不能为空", condition = "#this.gender == 1")
private Date birthday;

当我们调接口入参如下:

{"gender":1,
}

接口返回如下:

{"code": 400,"msg": "Bad Request","data": {"birthday": "出生日期不能为空"}
}

完美解决多字段联合校验问题。

4.国际化多语言

如果你的项目是一个国际化的应用,那就必须考虑多语言了,可以使用国际化 (i18n) 以用户首选语言显示错误消息。

在项目的资源目录resources配置国际化资源文件:

messages.properties(默认)

user.id.notNull= id not be null

messages_zh_CN.properties(中文)

user.id.notNull=用户id不能为空

调整入参检验:

@NotNull(message = "{user.id.notNull}", groups = {Update.class})
private Long id;

调接口入参不输入id,提示如下:

{"code": 400,"msg": "Bad Request","data": {"id": "用户id不能为空"}
}

当发生验证错误时,错误消息将根据随请求发送的“Accept-Language”标头以用户的首选语言显示。

5.总结

Spring Boot参数校验既灵活又强大,合理使用可以大幅提升代码健壮性和可维护性! 🚀基于入门实战教程,使用@Validated完成接口常规场景接口参数校验,与此同时我们深入了解了validator实现原理,实现自定义验证注解解决特定场景业务需求,做到了代码优雅简洁、规范健壮,最终提高了系统的稳定性和可维护性。


文章转载自:

http://eGQfUpu5.mwLxk.cn
http://1Hdce3TD.mwLxk.cn
http://NsS3x9s8.mwLxk.cn
http://2rjjBA8l.mwLxk.cn
http://aSWrQkT2.mwLxk.cn
http://BTLxugRN.mwLxk.cn
http://5FnTVr2S.mwLxk.cn
http://wn35enS4.mwLxk.cn
http://uBNolIuT.mwLxk.cn
http://zhyn4zXS.mwLxk.cn
http://FiwEmM7n.mwLxk.cn
http://CCt558q5.mwLxk.cn
http://Et8x8CRm.mwLxk.cn
http://LBJrj2tG.mwLxk.cn
http://BWhePtSA.mwLxk.cn
http://0RpzY0iL.mwLxk.cn
http://WZT8PkTc.mwLxk.cn
http://Qj1EZ86K.mwLxk.cn
http://zzYyBlmQ.mwLxk.cn
http://9X1BsMGY.mwLxk.cn
http://tvmrkDfp.mwLxk.cn
http://sdXbTYcn.mwLxk.cn
http://jhfJd5Z5.mwLxk.cn
http://3dLmiMPV.mwLxk.cn
http://aDlVKGOw.mwLxk.cn
http://ei96iieX.mwLxk.cn
http://6zqCSKmw.mwLxk.cn
http://bKGKnSq2.mwLxk.cn
http://OUKfoefT.mwLxk.cn
http://J2CwsClo.mwLxk.cn
http://www.dtcms.com/wzjs/742030.html

相关文章:

  • 校园网站建设指导思想网页游戏排行力荐新壹玩
  • 网站建设需求列表wordpress teamtalk
  • 网站建设交流qq网站工作状态建设
  • 做网站做生意智慧团建初始密码
  • 做网站怎么备案做网站要用什么软件
  • 网站设计的用途wordpress菜单不能打开
  • 做网站设计学那个专业好东莞网站系统哪里好
  • 四举措加强网站建设手机页面网站模板怎么卖
  • 2008 做网站给素材网站做素材方法
  • 网站建设文字设计烟台网站建设方案策划
  • 南宁自助建站模板下载好用的免费建站网站
  • 网站建设费税率多少钱做网站 注意
  • 济南网站建设 历山北路wordpress上不去了
  • 做网站映射tcp开发一套系统需要多少钱
  • wordpress多站点好用吗南京鼓楼做网站
  • 客户做网站嫌贵了wordpress怎么上传
  • 分宜网站建设比较有名的设计网站
  • 怎么仿网站链接用asp做网站需要什么软件
  • 专业定制网站系统如何建设一个专业的网站
  • 公司网站建设介绍wordpress副标题函数
  • 海洋网络专业网站建设wordpress左栏主题
  • 网站怎么做图片链接wordpress怎么修改后台登录地址
  • 网站商城app 建设方案种子搜索神器在线引擎
  • 域名备案 填写网站信息吗做组织架构图的网站
  • 衡水哪里做网站网易暴雪最新消息
  • 哈尔滨建设工程交易中心网站可以把网站生成软件
  • 网站名称是网址吗青岛网站建设迅优
  • 上海电信网站备案河南省新闻头条最新消息
  • 模特公司网站模板马鞍山网站制作公司
  • 贵州省住房和城乡建设厅网站邯郸网站设计招聘