各类网站建设,做视频的模板下载网站,温州哪里有做网站的,百度快照关键词推广前言
在开发Web应用时#xff0c;处理用户输入是不可避免的一环。然而#xff0c;用户输入往往充满不确定性#xff0c;可能是格式不正确、类型不匹配#xff0c;甚至包含恶意内容。为了确保应用的稳定性和安全性#xff0c;对输入参数进行有效校验显得尤为重要。Spring …前言
在开发Web应用时处理用户输入是不可避免的一环。然而用户输入往往充满不确定性可能是格式不正确、类型不匹配甚至包含恶意内容。为了确保应用的稳定性和安全性对输入参数进行有效校验显得尤为重要。Spring Boot作为当前最流行的Java开发框架之一通过其丰富的特性和集成的库为我们提供了一套高效、灵活的参数校验机制。本文将深入探讨Spring Boot中的参数校验技术包括基于JSR-303/JSR-349Bean Validation的注解校验、自定义校验器以及如何在不同场景下应用这些校验技术从而帮助你构建更加健壮、易于维护的Spring Boot应用。 一、为什么需要参数校验 在Web应用中用户输入是数据流动的起点。然而用户输入的数据往往难以预测和控制可能包含各种不符合预期的情况。如果不对这些输入进行校验就可能导致应用出现各种异常如类型转换错误、数据格式错误、业务逻辑错误等。这些错误不仅会影响用户体验还可能对应用的安全性和稳定性构成威胁。因此在数据进入应用的核心处理流程之前进行严格的参数校验是非常必要的。 二、常用的校验注解 Spring Boot支持的校验注解非常丰富包括但不限于
NotNull确保字段或参数的值不为null。NotEmpty确保字符串、集合或数组不为null且不为空对于字符串而言长度大于0对于集合或数组而言元素个数大于0。NotBlank仅适用于字符串确保字段或参数的值不为null且去除首尾空白字符后的长度大于0。Size(minvalue, maxvalue)限制字符串、集合或数组的长度或元素个数。Email确保字段或参数的值是一个有效的电子邮件地址。Pattern(regexvalue)使用正则表达式校验字段或参数的值。
接下来我们将详细讲解如何在Spring Boot项目中应用这些校验注解以及如何处理校验失败的情况。同时我们还将探讨如何自定义校验注解和校验器以满足更加复杂的校验需求。
实体类参数校验 SpringBoot 使用校验注解不需要新引入任何依赖是默认支持的。 首先看在项目中是如何使用校验注解的。先来定义一个用户实体类
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.*;
import java.util.List;Data
Builder
public class UserEntity {NotBlank(message 用户名不能为空)private String username;NotBlank(message 密码不能为空)Size(min 6, max 20, message 密码长度必须在6到20个字符之间)private String password;NotBlank(message 邮箱不能为空)Pattern(regexp ^\\w([-.]\\w)*\\w([-.]\\w)*(\\.\\w)$, message 邮箱格式不正确)private String email;NotEmpty(message 至少包含一位好友)private ListUserEntity friends;}
编写一个注册接口用来测试
import com.boot3.demo.commons.ResponseApi;
import com.boot3.demo.entity.UserEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;/*** 用户信息处理器* ResponseApi 是我自定义的响应对象为了方便测试也可以直接返回 UserEntity 对象* CreateTime: 2024-07-01 14:50*/
RestController
RequestMapping(users)
public class UserController {PostMapping(register)public ResponseApiUserEntity register(Valid RequestBody UserEntity user) {return ResponseApi.success(user);}
}
准备就绪以后就可以请求 /users/register 接口验证一下对于 UserEntity 使用的注解是否生效。
{username: ,password: 12312321,email: 123qq.com,friends: [{username: ,password: 1234567,email: 123qq.com,friends: [{}]}]
}测试1 测试2 测试3 测试4
这里你应该注意到了接口的响应数据对于用户而言是非常的友好的意思清晰明了这个是因为我在项目中定义了全局异常处理器统一处理由参数校验所抛出的异常并进一步对响应结果进行封装的结果。
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.boot3.demo.commons.ResponseApi;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
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.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ValidationException;
import java.util.Optional;/*** 全局异常处理** CreateTime: 2024-06-20 13:40*/
RestControllerAdvice
public class GlobalExceptionHandler {public final Logger logger LoggerFactory.getLogger(this.getClass());/*** 拦截实体类参数验证异常*/SneakyThrowsExceptionHandler(value {MethodArgumentNotValidException.class})public ResponseApi validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {BindingResult bindingResult ex.getBindingResult();FieldError firstFieldError CollectionUtil.getFirst(bindingResult.getFieldErrors());String exceptionStr Optional.ofNullable(firstFieldError).map(FieldError::getDefaultMessage).orElse(StrUtil.EMPTY);logger.error([{}] {} [ex] {}, request.getMethod(), getUrl(request), exceptionStr);return ResponseApi.error(exceptionStr);}/*** 拦截未捕获异常*/ExceptionHandler(Throwable.class)public ResponseApi defaultErrorHandler(HttpServletRequest request, Throwable throwable) {logger.error([{}] {} , request.getMethod(), getUrl(request), throwable);return ResponseApi.error();}private String getUrl(HttpServletRequest request) {if (StringUtils.isEmpty(request.getQueryString())) {return request.getRequestURL().toString();}return request.getRequestURL().toString() ? request.getQueryString();}
}
至此对于实体类的参数校验已经完成接下来再讲一讲如果不是实体类传参而是单个或多个String、Integer 等类型的传参应该怎么处理。
普通参数校验
假如我现在再定义一个根据用户名称获取用户信息的接口如下
/*** 用户信息处理器* CreateTime: 2024-07-01 14:50*/
RestController
RequestMapping(users)
Validated // 普通参数校验需要和这个注解一起使用不然不会生效。
public class UserController {PostMapping(register)public ResponseApiUserEntity register(Valid RequestBody UserEntity user) {return ResponseApi.success(user);}GetMapping(getUserByName)public ResponseApiUserEntity getUserByName(RequestParam(username) NotBlank String username) {return ResponseApi.success(test(username));}/*** 为了测试这里就随便写一个模拟查询数据库操作* CreateTime: 2024-07-01 14:50*/public UserEntity test(String username) {return UserEntity.builder().username(李白).password(admin).email(adminqq.com).build();}}
同样的准备就绪以后就可以请求 /users/getUserByName 接口验证一下对于 username 使用的注解是否生效。
测试1 这一次你可能会发现接口的响应结果和上面不一样并不是很友好打开控制台一看嗯抛了一个叫 ConstraintViolationException 的异常是正常拦截成功了但是好像并没有被全局异常控制器中的 validExceptionHandler() 方法处理这是因为validExceptionHandler() 方法指定拦截 项目中抛出的 MethodArgumentNotValidException 异常所以它不在拦截范围内。 测试2 既然知道它抛出的是什么异常那好办在异常处理器中再添加一个方法拦截这个异常就好了如下
/*** 拦截普通参数验证异常*/
ExceptionHandler(value {ConstraintViolationException.class})
public ResponseApi validExceptionHandler(HttpServletRequest request, ConstraintViolationException ex) {logger.error([{}] {} [ex] {}, request.getMethod(), getUrl(request), ex.getMessage());return ResponseApi.error(ex.getMessage());
}
再次请求一下 因为没有对 NotBlank 注解指定话术所以展示的为默认话术可以通过 NotBlank(message 用户名不能为空) 自定义拦截话术。 至此普通参数的校验也完成了。
自定义校验规则
书接上回假如现在有个需求要求用户的名称是唯一的不能重复。如果按照以前的习惯写这个需求可能是这样的
PostMapping(register)
public ResponseApiUserEntity register(Valid RequestBody UserEntity user) {// 前面校验都通过了获取出用户的名称String username user.getUsername();// 根据用户名查询数据库UserEntity userInfo getUserByName(username);// 判断如果 userInfo 不为空就代表用户名重复了if (userInfo ! null) {return ResponseApi.error(用户名已存在);}return ResponseApi.success(user);
}至此这个需求就完成了可是我不想这样写我就想通过加一个注解就能解决用户名重复的问题那怎么办呢 好办按照下面步骤来先定一个注解 想要了解以下元注解的含义请移步附录1 /*** 自定义唯一值参数校验注解*/
Documented
Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
Retention(RUNTIME)
Constraint(validatedBy UniqueValidator.class) // 指定实现校验逻辑的类
public interface UniqueValue {String message() default 用户名不能重复;Class?[] groups() default { };Class? extends Payload[] payload() default { };
}
创建一个校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;/*** 自定义唯一参数校验器*/
public class UniqueValidator implements ConstraintValidatorUniqueValue, String {Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {// value 就是入参比如在用户名上面加了注解那这个值就是用户名这里根据用户名查询一下数据库return getUserByName(value);}/*** 模拟查询数据库如果是张三就代表重复了*/private boolean getUserByName(String userName) {if (张三.equals(userName)) {return false;}return true;}
}
接下来在 UserEntity 对象的用户名上加上 UniqueValidator 注解
Data
Builder
public class UserEntity {NotBlank(message 用户名不能为空)UniqueValue(message 用户名已存在)private String username;NotBlank(message 密码不能为空)Size(min 6, max 20, message 密码长度必须在6到20个字符之间)private String password;NotBlank(message 邮箱不能为空)Pattern(regexp ^\\w([-.]\\w)*\\w([-.]\\w)*(\\.\\w)$, message 邮箱格式不正确)private String email;NotEmpty(message 至少包含一位好友)private ListUserEntity friends;}
然后请求再次请求 /users/register 接口 至此自定义校验规则也完成了整个流程都非常简单赶快打开电脑练习一下吧
附录1
在这个自定义注解UniqueValue的定义中使用了几个Java注解也称为元注解来定义其特性和行为。这些元注解分别是Documented、Target、Retention和Constraint。下面我将依次详细解释这些元注解的含义
Documented: Documented注解表明该自定义注解UniqueValue在通过javadoc等工具生成文档时应该被包含进去。这意味着当你查看使用UniqueValue注解的类的文档时你可以看到这个注解的信息。默认情况下自定义注解不会在javadoc中显示。 Target: Target注解用于指定被注解的注解UniqueValue可以应用的Java元素类型。在这个例子中Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })表明UniqueValue可以应用于方法、字段、注解类型、构造器以及方法参数上。这为UniqueValue的使用提供了灵活性可以根据需要将其应用于不同类型的元素上。 Retention: Retention注解指定了被注解的注解UniqueValue的保留策略。保留策略决定了注解在什么级别上可用源代码SOURCE、类文件CLASS或运行时RUNTIME。在这个例子中Retention(RUNTIME)表明UniqueValue注解在运行时是可以通过反射被读取的。这对于在运行时进行注解处理如参数校验是必需的。 Constraint(validatedBy UniqueValidator.class): Constraint是Bean Validation APIJSR 303/349中定义的用于标识一个自定义的校验注解。它不是Java标准库中的一部分而是Bean Validation规范的一部分。validatedBy UniqueValidator.class指定了实现该注解校验逻辑的类。在这个例子中UniqueValidator类将包含校验UniqueValue注解所标记的元素是否满足“唯一性”逻辑的代码。这意味着当Spring Boot或任何支持Bean Validation的框架遇到UniqueValue注解时它会调用UniqueValidator类来执行实际的校验逻辑。
总结来说这些元注解定义了UniqueValue注解的基本属性和行为包括它是否应该被文档化、它可以应用于哪些Java元素、它在何处被保留以及它的校验逻辑由哪个类实现。这些定义对于创建功能丰富且易于使用的自定义校验注解至关重要。