文章目录
在Javaweb的开发中,为了防止懂技术的人对数据库的恶意攻击,我们通常使用参数校验对无效数据进行筛选,Java生态下的@valid注解配
置SpringBoot的使用可以方便快速的完成对数据校验的各种场景。同时数据校验分为前端校验和后端校验。
可为何前端做完校验之后,还要在后端进行校验?
如果有人拿到了url地址,使用第三方测试工具比如postman就可以跳过前端页面的参数检验,所以为了数据库数据正确性,我们十分有必要对传来的数据在后端进行第二次校验
通过源码可以看出:
@Valid注解可以作用于:方法、属性(包括枚举中的常量)、构造函数、方法的形参上。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}
关于注解源码解析,可以参考如下链接:
import javax.validation.Valid;
| 注解 | 应用 |
|---|---|
| @Null | 用于基本类型上,限制只能为null |
| @NotNull | 用在基本类型上;不能为null,但可以为empty,没有Size的约束 |
| @NotEmpty | 用在集合类上面;不能为null,而且长度必须大于0 |
| @NotBlank | 只能作用在String上,不能为null,而且调用trim()后,长度必须大于0 |
插播一条小内容!!!null和empty有何区别?
String a = new String
String b = ""
String c = null
| 注解 | 应用 |
|---|---|
| @AssertFalse | 限制必须为false |
| @AssertTrue | 限制必须为true |
| 注解 | 应用 |
|---|---|
| @Size(max,min) | 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
| @Length(min=, max=) | 验证字符串长度是否在给定的范围之内 |
| 注解 | 应用 |
|---|---|
| @Past | 限制必须是一个过去的日期,并且类型为java.util.Date |
| @Future | 限制必须是一个将来的日期,并且类型为java.util.Date |
| @Pattern(value) | 限制必须符合指定的正则表达式 |
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null。
| 注解 | 应用 |
|---|---|
| @Min(value) | 验证 Number 和 String 对象必须为一个不小于指定值的数字 |
| @Max(value) | 验证 Number 和 String 对象必须为一个不大于指定值的数字 |
| @DecimalMax(value) | 限制必须为一个不大于指定值的数字,小数存在精度 |
| @DecimalMin(value) | 限制必须为一个不小于指定值的数字,小数存在精度 |
| @Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
| @Digits | 验证 Number 和 String 的构成是否合法 |
| @Range(max =3 , min =1 , message = " ") | Checks whether the annotated value lies between (inclusive) the specified minimum and maximum |
Max和Min是对你填的“数字”是否大于或小于指定值,这个“数字”可以是number或者string类型。长度限制用length。
| 注解 | 应用 |
|---|---|
| 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email |
整个过程如下图所示,用户访问接口,然后进行参数效验,因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验,而 POST 则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。

<!--第一种:valid依赖-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>版本号</version>
</dependency>
<!-- 第二种:集成于web依赖中(注意版本号)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<!-- 第三种:springboot的validation-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不为空")
private String name;
@Length(max = 3,message = "address最大长度是3")
private String address;
@Max(value = 5,message = "reqNo最大值是5")
private String reqNo;
}
@RestController
public class ValidTestController {
@RequestMapping("/valid/test")
public void test(@Valid @RequestBody TestRequest request){
System.out.println(request);
}

postman返回结果:
{
"timestamp": "2022-11-12T09:54:24.202+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.controller.ValidTestController.test(com.example.domain.TestRequest) with 3 errors: [Field error in object 'testRequest' on field 'name': rejected value []; codes [NotBlank.testRequest.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.name,name]; arguments []; default message [name]]; default message [name不为空]] [Field error in object 'testRequest' on field 'reqNo': rejected value [8]; codes [Max.testRequest.reqNo,Max.reqNo,Max.java.lang.String,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.reqNo,reqNo]; arguments []; default message [reqNo],5]; default message [reqNo最大值是5]] [Field error in object 'testRequest' on field 'address': rejected value [gtyjh]; codes [Length.testRequest.address,Length.address,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.address,address]; arguments []; default message [address],3,0]; default message [address最大长度是3]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:681)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
"message": "Validation failed for object='testRequest'. Error count: 3",
"errors": [
{
"codes": [
"NotBlank.testRequest.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"testRequest.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "name不为空",
"objectName": "testRequest",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
},
{
"codes": [
"Max.testRequest.reqNo",
"Max.reqNo",
"Max.java.lang.String",
"Max"
],
"arguments": [
{
"codes": [
"testRequest.reqNo",
"reqNo"
],
"arguments": null,
"defaultMessage": "reqNo",
"code": "reqNo"
},
5
],
"defaultMessage": "reqNo最大值是5",
"objectName": "testRequest",
"field": "reqNo",
"rejectedValue": "8",
"bindingFailure": false,
"code": "Max"
},
{
"codes": [
"Length.testRequest.address",
"Length.address",
"Length.java.lang.String",
"Length"
],
"arguments": [
{
"codes": [
"testRequest.address",
"address"
],
"arguments": null,
"defaultMessage": "address",
"code": "address"
},
3,
0
],
"defaultMessage": "address最大长度是3",
"objectName": "testRequest",
"field": "address",
"rejectedValue": "gtyjh",
"bindingFailure": false,
"code": "Length"
}
],
"path": "/valid/test"
}
从后端返回给postman的结果可以看出,三个字段的校验都已经实现。但是,特别情况,RequestBody可能是嵌套的实体,这个时候,对于嵌套的实体类来说,嵌套必须加 @Valid,如果只在字段上添加校验注解嵌套中的验证不生效。
如果只在嵌套类字段上加上校验注解,如下:
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不为空")
private String name;
@Length(max = 3, message = "address最大长度是3")
private String address;
@Max(value = 5, message = "reqNo最大值是5")
private String reqNo;
private TestRequestInner inner;
@Data
@NoArgsConstructor
public static class TestRequestInner {
@Length(max = 3, message = "最大长度是3")
private String sonName;
private Integer sonAge;
private String schoolNo;
}
}
postman测试:

控制台打印:

可以看出,嵌套类中sonName的长度校验并没有起到作用。
在嵌套类的外层加上@Valid注解,如下:
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不为空")
private String name;
@Length(max = 3, message = "address最大长度是3")
private String address;
@Max(value = 5, message = "reqNo最大值是5")
private String reqNo;
@Valid
private TestRequestInner inner;
// 即使放在list集合里面仍然是需要加上 @Valid 注解
// @Valid
// private List<TestRequestInner> inner;
@Data
@NoArgsConstructor
public static class TestRequestInner {
@Length(max = 3, message = "最大长度是3")
private String sonName;
private Integer sonAge;
private String schoolNo;
}
}
postman测试:
校验成功。

刚才的测试我们看到,校验注解全部生效,但是所有的异常全部抛出给postman,从控制台可以看出:

程序抛出了MethodArgumentNotValidException异常信息,在实际业务中,有时候需要处理这个异常,这个时候就需要一个全局异常处理类中处理 @Valid 抛出的异常。
抛出的异常结构:

代码如下:
实体类如上不变,controller接口方法改为返回string:
@RequestMapping("/valid/test")
public String test(@Valid @RequestBody TestRequest request){
System.out.println(request);
return "success";
}
异常处理类:
我们可以根据上图抛出的异常结构,去get我们想要获得的内容。
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 自定义验证异常
* MethodArgumentNotValidException 方法参数无效异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST) //设置状态码为 400
@ExceptionHandler({MethodArgumentNotValidException.class})
public String paramExceptionHandler(MethodArgumentNotValidException e) {
BindingResult exceptions = e.getBindingResult();
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (exceptions.hasErrors()) {
List errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
// 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
FieldError fieldError = (FieldError) errors.get(0);
return fieldError.getDefaultMessage();
}
}
return "请求参数错误";
}
}
postman测试:

上述的异常处理只是一个简单的string返回,但是在实际项目中,返回结构是固定的,下面对于固定的返回结构,做异常处理。
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不为空")
private String name;
@Length(max = 3, message = "address最大长度是3")
private String address;
@Max(value = 5, message = "reqNo最大值是5")
private String reqNo;
@Valid
private List<TestRequestInner> inner;
@Data
@NoArgsConstructor
public static class TestRequestInner {
@Length(max = 3, message = "最大长度是3")
private String sonName;
private Integer sonAge;
@NotBlank(message = "schoolNo不空")
private String schoolNo;
}
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class ResponseResult {
private List<ProvideInfo> provideInfos;
@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public static class ProvideInfo {
private String code;
private String detail;
}
}
import com.example.domain.ResponseResult;
import com.example.domain.TestRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class ValidTestController {
@RequestMapping("/valid/test")
public ResponseEntity<ResponseResult> test(@Valid @RequestBody TestRequest request) {
System.out.println(request);
return null;
}
}

再插播一个小插曲!!!!
刚开始写的结果返回实体类中的provideInfo是这样式儿滴:

然后运行项目就出了这个错:

英文版是这样式儿滴:

上网查了一下,问题出现在这:

原因:
本应当(只能)使用无参构造器,但编译器却发现代码中使用了有参(全参)构造器,这个全参构造器出现在Builder类的build()方法中,该方法试图调用一个全参构造器。这个报错信息表明,@NoArgsConstructor抑制了@Builder生成全参构造器,只生成了一个无参构造器,使用lombok插件delombok @Builder和@NoArgsConstructor两个注解,可以证实这一抑制现象。
解决方法:一种方法是同时使用@Builder、@NoArgsConstructor和@AllArgConstructor,还有一种方法是显式添加@Tolerate注解的无参构造器。
然后在provideInfo上添加了@AllArgConstructor注解(如下图),成功运行!

对于全局异常类的处理,涉及到拦截器相关内容,这里不做多说。全局异常类的处理方式有很多种:
// Enum枚举类
public enum CodeEnum {
// 根据自己的项目需求更改状态码,这里只是一个示范
UNKNOW_EXCEPTION(10000,"系统未知错误"),
VALID_EXCETIPON(10001,"参数格式校验错误");
private int code;
private String msg;
CodeEnum(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
//异常处理类
@Slf4j
@RestControllerAdvice("com.cbj.db_work.controller") //表明需要处理异常的范围
public class GlobalExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class) // 参数异常抛出的异常类型为MethodArgumentNotValidException,这里捕获这个异常
// R为统一返回的处理类
public R validExceptionHandler(MethodArgumentNotValidException e){
System.out.println("数据异常处理");
log.error("数据校验出现问题,异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
String message = item.getDefaultMessage();
// 获取错误的属性字段名
String field = item.getField();
map.put(field,message);
});
return R.error().code(CodeEnum.VALID_EXCETIPON.getCode()).message(CodeEnum.VALID_EXCETIPON.getMsg()).data("errorData",map);
}
}
@ControllerAdvice
@RestControllerAdvice
@Slf4j
public class ValidExceptionHandler extends GlobalExceptionHandler {
// GET请求参数异常处理
@ExceptionHandler(value = ConstraintViolationException.class)
public Result<Object> constraintViolationExceptionHandler(ConstraintViolationException e) {
StringBuilder msg = new StringBuilder();
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
String message = constraintViolation.getMessage();
msg.append(message).append(";");
}
return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg.toString());
}
@ExceptionHandler(ArithmeticException.class)
public Result<Object> arithmeticExceptionHandler(ArithmeticException e) {
e.printStackTrace();
return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算术异常!"+e.getMessage());
}
// POST请求参数异常处理
@ExceptionHandler(BindException.class)
public Result<Object> bindExceptionHandler(BindException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
String msg;
if (Objects.isNull(fieldError)) {
msg = "POST请求参数异常:" + JSON.toJSONString(e.getBindingResult());
log.info(msg);
} else {
msg = fieldError.getDefaultMessage();
}
return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg);
}
}
特别的,get请求校验:
@RestController
@RequestMapping(value = "/test")
@Slf4j
//@ApiIgnore
@Validated
public class TestController {
@GetMapping(value = "/test")
public Result<Object> test(@NotNull(message = "name必传")
@NotBlank(message = "name格式错误")String name) {
return ResultResponse.getSuccessResult("hello: " + name);
}
}
假设有这样一种场景:
我们使用同一个VO(Request)类来传递save和update方法的数据,但对于id来说,通常有框架帮我们生成id,我们不需要传id此时需要使用注解@Null表名id数据必须为空。但对于update方法,我们必须传id才能进行update操作,所以同一个字段面对不同的场景不同需求就可以使用分组校验。
这里并不需要实现编写什么代码,标明分类。
//分组接口 1
public interface InsertGroup {
}
//分组接口 2
public class UpdateGroup {
}
@Data
@NoArgsConstructor
public class TestRequest {
@Null(message = "无需传id",groups = InsertGroup.class)
@NotBlank(message = "必须传入id",groups = UpdateGroup.class)
private String id;
}
@RequestMapping("/valid/test")
public ResponseEntity<ResponseResult> test(@Validated({UpdateGroup.class})@RequestBody TestRequest request) {
System.out.println(request);
return null;
}

当我们在controller层指定分组后,属性上没有表名分组的校验还执行么?
下面的Request实体中,id进行了分组,address没有进行分组:
@Data
@NoArgsConstructor
public class TestRequest {
@Null(message = "无需传id",groups = InsertGroup.class)
@NotBlank(message = "必须传入id",groups = UpdateGroup.class)
private String id;
@Length(max = 3, message = "address最大长度是3")
private String address;
}
postman测试:
如下图,校验没有成功!!!!说明一旦开启了分组校验,就必须把所有的校验规则都指定组别,不然不生效。

假设我们有一个字段比如showStatus只能由0和1两个取值,我们可以使用正则,也可以自定义注解校验,这里我们展示如何使用自定义校验.
如下图所示:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
// Target表示注解使用的范围
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
// 获取注解的时间,固定值
@Retention(RetentionPolicy.RUNTIME)
// 匹配的校验器,我们稍后编写,关联注解和校验器
@Constraint(validatedBy = {StatusValueValidator.class})
@Documented
public @interface StatusValue {
// 错误信息去哪找,通常我们使用配置文件,稍后编写
String message() default "{com.cbj.db_work.valid.ListValue.message}";
// 支持分组校验
Class<?>[] groups() default {};
// 自定义负载信息
Class<? extends Payload>[] payload() default {};
// 指定参数,就是上图中指定的可取值的范围
int [] vals() default {};
}
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
// 实现ConstraintValidator接口,泛型值:<自定义注解类,被校验值的数据类型>
public class StatusValueValidator implements ConstraintValidator<StatusValue, Integer> {
// 整体思路,使用set在initialize获得参数信息,在isValid方法中校验,成功true,失败false
Set<Integer> set = new HashSet<>();
// 初始化方法,可以得到详细信息
@Override
public void initialize(StatusValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 校验是否匹配
* @param value 就是需要校验的值
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(integer);
}
}

com.cbj.db_work.valid.ListValue.message=必须提交指定的值
在配置文件中可以指定匹配错误时显示的信息,也可以message指定。


我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd
在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee
我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
我已经在mountainlion上成功安装了rbenv和rubybuild。运行rbenvinstall1.9.3-p392结束于:校验和不匹配:ruby-1.9.3-p392.tar.gz(文件已损坏)预期f689a7b61379f83cbbed3c7077d83859,得到1cfc2ff433dbe80f8ff1a9dba2fd5636它正在下载的文件看起来没问题,如果我使用curl手动下载文件,我会得到同样不正确的校验和。有没有人遇到过这个?他们是如何解决的? 最佳答案 tl:博士;使用浏览器从http://ftp.rub
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐
我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby-vips的github页面上的链接,我们将不胜感激!如果有ruby-