- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章SpringBoot 如何自定义请求参数校验由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
最近在工作中遇到写一些API,这些API的请求参数非常多,嵌套也非常复杂,如果参数的校验代码全部都手动去实现,写起来真的非常痛苦.
正好Spring轮子里面有一个Validation,这里记录一下怎么使用,以及怎么自定义它的返回结果.
。
Bean Validation是Java中的一项标准,它通过一些注解表达了对实体的限制规则。通过提出了一些API和扩展性的规范,这个规范是没有提供具体实现的,希望能够Constrain once, validate everywhere。现在它已经发展到了2.0,兼容Java8.
hibernate validation实现了Bean Validation标准,里面还增加了一些注解,在程序中引入它我们就可以直接使用.
Spring MVC也支持Bean Validation,它对hibernate validation进行了二次封装,添加了自动校验,并将校验信息封装进了特定的BindingResult类中,在SpringBoot中我们可以添加implementation(‘org.springframework.boot:spring-boot-starter-validation')引入这个库,实现对bean的校验功能.
。
gradle dependencies如下:
dependencies { implementation('org.springframework.boot:spring-boot-starter-validation') implementation('org.springframework.boot:spring-boot-starter-web')}
定义一个示例的Bean,例如下面的User.java.
public class User { @NotBlank @Size(max=10) private String name; private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }}
在name属性上,添加@NotBlank和@Size(max=10)的注解,表示User对象的name属性不能为字符串且长度不能超过10个字符.
然后我们暂时不添加任何多余的代码,直接写一个UserController对外提供一个RESTful的GET接口,注意接口的参数用到了@Validated注解.
// UserController.java,省略其他代码@RestControllerpublic class UserController { @RequestMapping(value = "/validation/get", method = RequestMethod.GET) public ServiceResponse validateGet(@Validated User user) { ServiceResponse serviceResponse = new ServiceResponse(); serviceResponse.setCode(0); serviceResponse.setMessage("test"); return serviceResponse; }}// ServiceResponse.java,简单包含了code、message字段返回结果。public class ServiceResponse { private int code; private String message; ... 省略getter、setter ...}
启动SpringBoot程序,发一个测试请求看一下:
http://127.0.0.1:8080/validation/get?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&password=1 。
此时已经可以实现参数的校验了,但是返回的结果不太友好,下面看一下怎么定制返回的消息。在定制返回结果前,先看下一下内置的校验注解有哪些,在这里我不一个个去贴了,写代码的时候根据需要进入到源码里面去看即可.
早期Spring版本中,都是在Controller的方法中添加Errors/BindingResult参数,由Spring注入Errors/BindingResult对象,再在Controller中手写校验逻辑实现校验.
新版本提供注解的方式(Controller上面bean加一个@Validated注解),将校验逻辑和Controller分离.
。
显然除了自带的NotNull、NotBlank、Size等注解,实际业务上还会需要特定的校验规则.
假设我们有一个参数address,必须以Beijing开头,那我们可以定义一个注解和一个自定义的Validator.
// StartWithValidator.javapublic class StartWithValidator implements ConstraintValidator<StartWithValidation, String> { private String start; @Override public void initialize(StartWithValidation constraintAnnotation) { start = constraintAnnotation.start(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (!StringUtils.isEmpty(value)) { return value.startsWith(start); } return true; }}// StartWithValidation.java@Documented@Constraint(validatedBy = StartWithValidator.class)@Target({METHOD, FIELD})@Retention(RUNTIME)public @interface StartWithValidation { String message() default "不是正确的性别取值范围"; String start() default "_"; Class[] groups() default {}; Class[] payload() default {}; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List { GenderValidation[] value(); }}
然后在User.java中增加一个address属性,并给它加上上面这个自定义的注解,这里我们定义了一个可以传入start参数的注解,表示应该以什么开头.
@StartWithValidation(message = "Param 'address' must be start with 'Beijing'.", start = "Beijing")private String address;
除了定义可以作用于属性的注解外,其实还可以定义作用于class的注解(@Target({TYPE})),用于校验class的实例.
第一步,实现一个Validator。(这种方法不需要我们的bean里面有任何注解之类的东西) 。
package com.example.validation.demo;import org.springframework.stereotype.Component;import org.springframework.validation.Errors;import org.springframework.validation.ValidationUtils;import org.springframework.validation.Validator;@Componentpublic class UserValidator implements Validator { @Override public boolean supports(Class clazz) { return User2.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "name", "name.empty"); User2 p = (User2) target; if (p.getId() == 0) { errors.rejectValue("id", "can not be zero"); } }}
第二步,修改Controller代码,注入上面的UserValidator实例,并给Controller的方法参数加上@Validated注解,即可完成和前面自定义注解一样的校验功能.
@RestControllerpublic class UserController { @Autowired UserValidator validator; @InitBinder public void initBinder(WebDataBinder binder) { binder.setValidator(validator); } @RequestMapping(value = "/user/post", method = RequestMethod.POST) public ServiceResponse handValidatePost(@Validated @RequestBody User user) { ServiceResponse serviceResponse = new ServiceResponse(); serviceResponse.setCode(0); serviceResponse.setMessage("test"); return serviceResponse; }}
这个方法和自定义注解的区别在于不需要在Bean里面添加注解,并且可以更加灵活的把一个Bean里面所有的Field的校验代码都搬到一起,而不是每一个属性都去加注解,如果校验的属性非常多,且默认注解的能力又不够的话,这种方式也是不错的,可以避免大量的自定义注解.
这种方式可以算是原始的Hibernate-Validation的方式。直接看代码,这里有一个比较不同的是,可以使用Hibernate-Validation的Fail fast mode。因为前面的方式,都将所有的参数都验证完了,再把错误返回。有时我们希望遇到一个参数错误,就立即返回.
设置fast-fail为true可以达到这个目的。不过貌似不能再用@Validated注解方法参数了,而是要用ValidatorFactory创建Validator.
在实际开发中,不必每次都编写代码创建Validator,可以采用@Configuration的方式创建,然后再@Autowired注入到每个需要使用Validator的Controller当中.
@RestControllerpublic class UserController { ... @RequestMapping(value = "/validation/postStudent", method = RequestMethod.POST) public ServiceResponse validatePostStudent(@RequestBody User user) { // User参数前面没有@Validated注解了,User类里面那些注解还是保留着即可。 HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure(); ValidatorFactory factory = configuration.failFast(true).buildValidatorFactory(); // fastFail Validator validator = factory.getValidator(); Set<constraintviolation> set = validator.validate(user); // 根据set的size,大于0时,抛异常。由于设置了failFast,这里set最多就一个元素 ServiceResponse serviceResponse = new ServiceResponse(); serviceResponse.setCode(0); serviceResponse.setMessage("test"); return serviceResponse; }}
有的时候,我们会有两个不同的接口,但是会使用到同一个Bean来作为VO(意思是两个接口的URI不同,但参数中都用到了同一个Bean).
而在不同的接口上,对Bean的校验需求可能不一样,比如接口2需要校验studentId,而接口1不需要。那么此时就可以用到校验注解的分组groups.
// User.javapublic class User { ... 省略其他属性 // 指明在groups={Student.class}时才需要校验studentId @NotNull(groups = {Student.class}, message = "Param 'studentId' must not be null.") private Long studentId; // 增加Student interface public interface Student { }}// UserController.java,增加了一个/getStudent接口@RestControllerpublic class UserController { @RequestMapping(value = "/validation/get", method = RequestMethod.GET) public ServiceResponse validateGet(@Validated User user) { ServiceResponse serviceResponse = new ServiceResponse(); serviceResponse.setCode(200); serviceResponse.setMessage("test"); return serviceResponse; } @RequestMapping(value = "/validation/getStudent", method = RequestMethod.GET) public ServiceResponse validateGetStudent(@Validated({User.Student.class}) User user) { ServiceResponse serviceResponse = new ServiceResponse(); serviceResponse.setCode(0); serviceResponse.setMessage("test"); return serviceResponse; }}
到这里,也可以带一嘴Valid和Validated注解的区别,其代码注释写着后者是对前者的一个扩展,支持了group分组的功能.
第二节中定义了一个ServiceResponse,其实作为一个开放的API,不论用户传入任何参数,返回的结果都应该是预先定义好的格式,并且可以写明在接口文档中,即使发生了校验失败,应该返回一个包含错误码code(发生错误时一般大于0)和message字段.
{ "code": 51000, "message": "Param 'name' must be less than 10 characters."}
的结果,而HTTP STATUS CODE一直都是200.
为了实现这个目的,我们加一个全局异常处理方法.
// ServiceExceptionHandler.javapackage com.example.validation.demo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.util.CollectionUtils;import org.springframework.validation.BindException;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.List;@RestControllerAdvicepublic class ServiceExceptionHandler { static final Logger LOG = LoggerFactory.getLogger(ServiceExceptionHandler.class); @ExceptionHandler(value = {Exception.class}) public ServiceResponse handleBindException(Exception ex) { LOG.error("{}", ex); StringBuilder message = new StringBuilder(); if (ex instanceof BindException) { List fieldErrorList = ((BindException) ex).getFieldErrors(); if (!CollectionUtils.isEmpty(fieldErrorList)) { for (FieldError fieldError : fieldErrorList) { if (fieldError != null && fieldError.getDefaultMessage() != null) { message.append(fieldError.getDefaultMessage()).append(" "); } } } } else if (ex instanceof MethodArgumentNotValidException) { List fieldErrorList = ((MethodArgumentNotValidException) ex).getBindingResult().getFieldErrors(); if (!CollectionUtils.isEmpty(fieldErrorList)) { for (FieldError fieldError : fieldErrorList) { if (fieldError != null && fieldError.getDefaultMessage() != null) { message.append(fieldError.getDefaultMessage()).append(" "); } } } } // 生成返回结果 ServiceResponse errorResult = new ServiceResponse(); errorResult.setCode(51000); // ErrorCode.PARAM_ERROR = 51000 errorResult.setMessage(message.toString()); return errorResult; }}// User.java,注解传入指定Messagepublic class User { @NotBlank(message = "Param 'name' can't be blank.") @Size(max=10, message = "Param 'name' must be less than 10 characters.") private String name; ...}
在上面的方法中,我们处理了BindException(非请求body参数,例如@RequestParam接收的)和MethodArgumentNotValidException(请求body里面的参数,例如@RequestBody接收的),这两类Exception里面都有一个BindingResult对象,它里面有一个包装成FieldError的List,保存着Bean对象出现错误的Field等信息.
取出它里面defaultMessage,放到统一的ServiceResponse返回即可实现返回码和消息的定制。由于消息内容是有注解默认的DefaultMessage决定的,为了按照自定义的描述返回,在Bean对象的注解上需要手动赋值为希望返回的消息内容.
@NotBlank(message = "Param 'name' can't be blank.")@Size(max=10,message = "Param 'name' must be less than 10 characters.")private String name;
这样当name参数长度超过10时,就会返回 。
{ "code": 51000, "message": "Param 'name' must be less than 10 characters."}
这里的FieldError fieldError = ex.getFieldError();只会随机返回一个出错的属性,如果Bean对象的多个属性都出错了,可以调用ex.getFieldErrors()来获得,这里也可以看到Spring Validation在参数校验时不会在第一次碰到参数错误时就返回,而是会校验完成所有的参数.
如果不想手动编程去校验,那么这里可以只读取一个随机的FieldError,返回它的错误消息即可.
其实还有一种比较典型的自定义返回,就是错误码(code)和消息(message)是一一对应的,比如:
这种情况比较特殊,一般当参数错误的时候,会返回一个整体的参数错误的错误码,然后携带参数的错误信息。但有时,业务 。
上就要不同的参数错误,既要错误码不同,错误信息也要不同。我想了下,有两种思路.
。
其实在实际的工作中,肯定还有更复杂的校验逻辑,但是不一定非要都用框架去实现,框架里面的实现(比如注解)应该是一个比较简单通用的校验,能够达到复用,减少重复的劳动.
而更加复杂的逻辑校验,一定是存在具体业务当中的,最好是在业务代码里面实现.
还有一点需要注意,Spring Validation的isValid方法,如果返回false,那么Controller不再会被调用,而是直接返回。如果你在Controller上面加了AOP进行接口调用统计的话,可能会漏掉.
这个时候,我们不应该让Controller不调用,建议这种情况在AOP里面对Controller的参数切面进行校验后,抛出统一的业务异常.
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我.
原文链接:https://blog.csdn.net/qq_22343483/article/details/103316856 。
最后此篇关于SpringBoot 如何自定义请求参数校验的文章就讲到这里了,如果你想了解更多关于SpringBoot 如何自定义请求参数校验的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试从该网站抓取历史天气数据: http://www.hko.gov.hk/cis/dailyExtract_uc.htm?y=2016&m=1 在阅读了 AJAX 调用后,我发现请求数据的正确
我有两个 postman 请求 x,y,它们命中了两个不同的休息 api X,Y 中的端点。 x 会给我一个身份验证 token ,这是发出 y 请求所必需的。如何在请求 y 中发出请求 x ?也就是
我使用请求库通过 API 与其他服务器进行通信。但现在我需要同时发送多个(10 个或更多)POST 请求,并且只有在所有响应都正确的情况下才能进一步前进。通常语法看起来有点像这样: var optio
背景:当用户单击按钮时,其类会在class1和class2之间切换,并且此数据是通过 AJAX 提交。为了确认此数据已保存,服务器使用 js 进行响应(更新按钮 HTML)。 问题:如果用户点击按钮的
我正在将 Node.js 中的请求库用于 Google 的文本转语音 API。我想打印出正在发送的请求,如 python example . 这是我的代码: const request = requi
我经常使用requests。最近我发现还有一个 requests2 和即将到来的 requests3 虽然有一个 page其中简要提到了 requests3 中的内容,我一直无法确定 requests
我正在尝试将图像发送到我的 API,然后从中获取结果。例如,我使用发送一个 bmp 图像文件 file = {"img": open("img.bmp)} r = requests.post(url,
我发现 Google Cloud 确保移出其物理环境的任何请求都经过强制加密,请参阅(虚拟机到虚拟机标题下的第 6 页)this link Azure(和 AWS)是否遵循类似的程序?如果有人能给我指
我有一个 ASP.NET MVC 应用程序,我正在尝试在 javascript 函数中使用 jQuery 来创建一系列操作。该函数由三部分组成。 我想做的是:如果满足某些条件,那么我想执行同步 jQu
我找不到如何执行 get http 请求,所以我希望你们能帮助我。 这个想法是从外部url(例如 https://api.twitter.com/1.1/search/tweets.json?q=tw
我的应用只需要使用“READ_SMS”权限。我的问题是,在 Android 6.0 上,当我需要使用新的权限系统时,它会要求用户“发送和查看短信”。 这是我的代码: ActivityCompat.re
我的前端代码: { this.searchInput = input; }}/> 搜索 // search method: const baseUrl = 'http://localho
我有一个由 AJAX 和 C# 应用程序使用的 WCF 服务, 我需要通过 HTTP 请求 header 发送一个参数。 在我的 AJAX 上,我添加了以下内容并且它有效: $.ajax({
我正在尝试了解如何使用 promises 编写代码。请检查我的代码。这样对吗? Node.js + 请求: request(url, function (error, response, body)
如果失败(除 HTTP 200 之外的任何响应代码),我需要重试发送 GWT RPC 请求。原因很复杂,所以我不会详细说明。到目前为止,我在同一个地方处理所有请求响应,如下所示: // We
当用户单击提交按钮时,我希望提交表单。然而,就在这种情况发生之前,我希望弹出一个窗口并让他们填写一些数据。一旦他们执行此操作并关闭该子窗口,我希望发出 POST 请求。 这可能吗?如果可能的话如何?我
像 Facebook 这样的网站使用“延迟”加载 js。当你必须考虑到我有一台服务器,流量很大时。 我很感兴趣 - 哪一个更好? 当我一次执行更多 HTTP 请求时 - 页面加载速度较慢(由于限制(一
Servlet 容器是否创建 ServletRequest 和 Response 对象或 Http 对象?如果是ServletRequest,谁在调用服务方法之前将其转换为HttpServletReq
这是维基百科文章的摘录: In contrast to the GET request method where only a URL and headers are sent to the serv
我有一个循环,每次循环时都会发出 HTTP post 请求。 for(let i = 1; i console.log("succes at " + i), error => con
我是一名优秀的程序员,十分优秀!