一、JSR303介绍
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
二、JSR303使用
- 使用场景:当前端进行表单验证后,还是可以使用postman去恶意传参,所以JSR303一般用于后台的表单验证,即验证java bean
- 使用依赖:
1.基本的import javax.validation.constraints.*
2.hibernate也封装扩展了一些import org.hibernate.validator.constraints.*
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
3.当查询某个字段不为空的时候,返回字段名import com.fasterxml.jackson.annotation.JsonInclude;
比如查询sort的数据库结果为空,返回给前端的时候就不包含此字段
/**
* 排序
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer sort;
具体使用类型参考其他博客:JsonInclude注解
4.如果自己也需要扩展校验规则,可使用@Pattern(regexp="")
传一个正则表达式去验证
- 使用步骤:
1.直接在javabean上添加校验注解,如邮箱验证:
import javax.validation.constraints.*;
//规定长度
@Length(min = 6, max = 19, message = "用户名长度是6-18位")
@ApiModelProperty(value = "用户名", required = true)
private String username;
@ApiModelProperty(value = "密码", required = true)
private String password;
//不能是空的
@NotEmpty
@ApiModelProperty(value = "用户头像")
private String icon;
@Email(message = "邮箱格式错误")
@ApiModelProperty(value = "邮箱")
private String email;
@NotNull
@ApiModelProperty(value = "用户昵称")
private Stringname;
@ApiModelProperty(value = "备注")
private String note;
@URL(message = "Invalid URL", protocols = {"http", "https"})
private String websiteUrl;
2.在controller上添加@Valid
这时用postman将name传空字符串,返回400错误
3.统一处理校验失败结果
(1)获取所有校验结果
在controller请求方法的参数列表新加一个参数BingingResult result
(2)根据result.hasErrors()判断是否校验成功
public save(@Valid @RequestBody UmsAdminParam user,BindingResult result) {
//得到所有错误信息计数
int errorCount = result.getErrorCount();
//错误数大于0
if (errorCount>0){
//得到所有错误
List<FieldError> fieldErrors = result.getFieldErrors();
//迭代错误
fieldErrors.forEach((fieldError)->{
//错误信息
String field = fieldError.getField();
log.debug("属性:{},传来的值是:{},出错的提示消息:{}",
field,fieldError.getRejectedValue(),fieldError.getDefaultMessage());
});
return fieldError.getRejectedValue()+"出错:"+fieldError.getDefaultMessage();
}else{
return "成功";
}
}
再次传非法参数测试结果:
三、统一处理异常
- 将之前的验证结果参数注释掉,让验证失败后先抛出异常,后面统一处理
- 使用springmvc提供的@ControllerAdvice来统一拦截异常
新建包exception,新建类MyExcControllerAdvice,统一拦截校验失败后抛出的MethodArgumentNotValidException
,代码如下
package com.furenqiang.gulimall.product.exception;
import com.furenqiang.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
/**
* 集中处理所有异常
* */
@Slf4j
@RestControllerAdvice(basePackages = "com.furenqiang.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
private R handleVaildExcepction(MethodArgumentNotValidException e){
log.error("校验出现异常{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
HashMap<String,String> errMap=new HashMap<String,String>();
if (bindingResult.hasErrors()){
bindingResult.getFieldErrors().forEach((item)->{
errMap.put(item.getField(),item.getDefaultMessage());
});
}
return R.error(400,"参数校验不合法").put("data",errMap);
}
@ExceptionHandler(value = Throwable.class)
private R handleVaildExcepction(Throwable throwable){
log.error("出现异常{},异常类型{}",throwable.getMessage(),throwable.getClass());
return R.error();
}
}
注意:这里的MethodArgumentNotValidException
异常是spring自带的org.springframework.web.bind.MethodArgumentNotValidException
,如果要捕捉自定义异常,可按照下面的写法:
抛出自定义的异常
四、JSR303分组校验
- 新建valid包,创建新增分组接口AddGroup、UpdateGroup
- 在javabean上指定所属分组,只有匹配上分组后才会使用相应的校验规则
- 在controller上将@Valid改成spring提供的@Validated({AddGroup.class}),可指定分组
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
五、更灵活的自定义校验
第一种方式是使用@Pattern(regexp="")
传一个正则表达式去验证,需要在controller类上加上@Validated
注解,否则正则验证不生效
第二种方式:完全自定义
- 编写自定义校验注解
package com.furenqiang.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(
validatedBy = {ListValueConstraintValidator.class}//自定义的校验器
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
String message() default "{com.furenqiang.common.value.ListValid.message}";//对应ValidationMessages.properties的配置
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}
- 编写自定义校验器
package com.furenqiang.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* 自定义校验器,要实现 implements ConstraintValidator<ListValue,Integer>
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<>();
@Override
public void initialize(ListValue constraintAnnotation) {
//获取注解上的指定值,遍历放入set
for (int val : constraintAnnotation.vals()) {
set.add(val);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
//判断set中的指定值是否包含前端传来的参数value
return set.contains(value);
}
}
在资源包resources下新建ValidationMessages.properties写入如下配置
com.furenqiang.common.value.ListValue.message=必须提交指定的值
- 关联自定义注解和自定义校验器
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals = {0,1},groups = {AddGroup.class})
private Integer showStatus;
- 测试
六、补充JavaBean实体类包名划分规范
PO:完整对应数据库字段
DO :
TO:不同服务之间通过feign远程调用时传输的对象
DTO:前端调用接口时候传入的对象
VO:前端传给服务端的参数对象或者服务端返回给前端的封装对象
当VO包含Entity实体类字段名时,可使用
import org.springframework.beans.BeanUtils; BeanUtils.copyProperties(VO,Entity);
将相同名称的字段赋值,此方法无返回值,调用后即完成转换,如果要忽略某字段的拷贝,比如name,可以使用BeanUtils.copyProperties(sensorGroupPageListDto, sensorInfoGroup, new String[]{"name"});
即可忽略掉name字段
BO、POJO、DAO: