阿里妹导读
一、前言
public class TestController {
private static final Pattern ID_CARD_PATTERN = Pattern.compile("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)");
private static final Pattern MOBILE_PHONE_PATTERN = Pattern.compile("^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$");
"/api/saveUser", method = RequestMethod.POST) (value =
public Result<Boolean> saveUser(UserRequest user) {
if (StringUtils.isBlank(user.getUserName())) {
throw new IllegalArgumentException("用户姓名不能为空");
}
if (Objects.isNull(user.getGender())) {
throw new IllegalArgumentException("性别不能为空");
}
if (Objects.isNull(GenderType.getGenderType(user.getGender()))) {
throw new IllegalArgumentException("性别错误");
}
if (Objects.isNull(user.getAge())) {
throw new IllegalArgumentException("年龄不能为空");
}
if (user.getAge() < 0 || user.getAge() > 150) {
throw new IllegalArgumentException("年龄必须在0-150之间");
}
if (StringUtils.isBlank(user.getIdCard())) {
throw new IllegalArgumentException("身份证号不能为空");
}
if (!ID_CARD_PATTERN.matcher(user.getIdCard()).find()) {
throw new IllegalArgumentException("身份证号格式错误");
}
if (StringUtils.isBlank(user.getMobilePhone())) {
throw new IllegalArgumentException("手机号不能为空");
}
if (!MOBILE_PHONE_PATTERN.matcher(user.getIdCard()).find()) {
throw new IllegalArgumentException("手机号格式错误");
}
// 省略其他业务代码
return new ResponseEntity<>(HttpStatus.OK);
}
}
二、实践
2.1、Controller方法参数校验
public class UserRequest {
private String userId;
private String mobilePhone;
private Integer age;
private UserDetail userDetail;
//省略其他参数
}
public class TestController {
public ResponseEntity<BaseResult> saveUser( UserRequest user) {
// 省略其他业务代码
return new ResponseEntity<>(HttpStatus.OK);
}
}
{
"timestamp": 1666777674977,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"NotBlank.UserRequest.mobilePhone",
"NotBlank.mobilePhone",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"UserRequest.mobilePhone",
"mobilePhone"
],
"arguments": null,
"defaultMessage": "mobilePhone",
"code": "mobilePhone"
}
],
"defaultMessage": "电话号码不能为空",
"objectName": "UserRequest",
"field": "mobilePhone",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='UserRequest'. Error count: 1",
"path": "/api/saveUser"
}
public class GlobalExceptionHandler {
public BaseResult handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldErrors().get(0);
return new BaseResult(CommonResultCode.ILLEGAL_PARAMETERS.getErrorCode(),
"入参中的" + fieldError.getField() + fieldError.getDefaultMessage(), EagleEye.getTraceId());
}
}
{
"success": false,
"errorCode": "ILLEGAL_PARAMETERS",
"errorMessage": "入参中的mobilePhone电话号码不能为空",
"traceId": "1ef9749316674663696111017d73c9",
"extInfo": {}
}
@Valid [3]注解,是 Bean Validation 所定义,可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。
@Validated [4]注解,是 Spring Validation 所定义,可以添加在类、方法参数、普通方法上,表示它们需要进行约束校验。
public class UserRequest {
private String userId;
private String mobilePhone;
private Integer age;
private UserDetail userDetail;
//省略其他参数
}
public class TestController {
public ResponseEntity<BaseResult> saveUser( UserRequest user) {
// 省略其他业务代码
return new ResponseEntity<>(HttpStatus.OK);
}
}
由 @Constraint 注解的注解。
实现了 javax.validation.ConstraintValidator 的 validator。
public enum GenderEnum implements BasicEnum {
male("male", "男"),
female("female", "女");
private String code;
private String desc;
// 省略其他
}
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = InEnumValidator.class)
public @interface InEnum {
/**
* 枚举类型
*
* @return
*/
Class<? extends BasicEnum> enumType();
String message() default "枚举类型不匹配";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class InEnumValidator implements ConstraintValidator<InEnum, Object> {
private Class<? extends BasicEnum> enumType;
public void initialize(InEnum inEnum) {
enumType = inEnum.enumType();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
if (object == null) {
return true;
}
if (enumType == null || !enumType.isEnum()) {
return false;
}
for (BasicEnum basicEnum : enumType.getEnumConstants()) {
if (basicEnum.getCode().equals(object)) {
return true;
}
}
return false;
}
}
public class UserRequest {
private String gender;
//省略其他参数
}
{
"success": false,
"errorCode": "ILLEGAL_PARAMETERS",
"errorMessage": "入参中的gender用户性别不在枚举范围内",
"traceId": "1ef9749316674663696111017d73c9",
"extInfo": {}
}
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
// 这个是javax.validation.Validator
private Validator validator;
// 可以传入自定义注解
public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
this.validatedAnnotationType = validatedAnnotationType;
}
// 默认情况下,使用LocalValidatorFactoryBean进行校验,也可以传入自定义的Validator
public void setValidator(Validator validator) {
// Unwrap to the native Validator with forExecutables support
if (validator instanceof LocalValidatorFactoryBean) {
this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
}
else if (validator instanceof SpringValidatorAdapter) {
this.validator = validator.unwrap(Validator.class);
}
else {
this.validator = validator;
}
}
// 可以传入自定义ValidatorFactory
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validator = validatorFactory.getValidator();
}
public void afterPropertiesSet() {
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
// 使用MethodValidationInterceptor切面
protected Advice createMethodValidationAdvice(Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
}
public class MethodValidationInterceptor implements MethodInterceptor {
//xxx
public Object invoke(MethodInvocation invocation) throws Throwable {
// 如果是FactoryBean.getObject() 方法,就不要校验了
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
// 获取方法上@Validated注解里的分组信息
Class<?>[] groups = determineValidationGroups(invocation);
if (forExecutablesMethod != null) {
// Standard Bean Validation 1.1 API
Object execVal;
try {
execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator);
}
catch (AbstractMethodError err) {
Validator nativeValidator = this.validator.unwrap(Validator.class);
execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, nativeValidator);
this.validator = nativeValidator;
}
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<?>> result;
// 对方法的参数进行验证,验证结果保存在 result 中,如果验证失败,result 不为空,
// 此时会抛出异常 ConstraintViolationException
try {
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
// 调用目标方法
Object returnValue = invocation.proceed();
// 对方法执行的返回值进行验证 ,验证结果保存在 result 中,如果验证失败,result 不为空,
// 此时会抛出异常 ConstraintViolationException
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateReturnValueMethod,
execVal, invocation.getThis(), methodToValidate, returnValue, groups);
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
else {
// Hibernate Validator 4.3's native API
return HibernateValidatorDelegate.invokeWithinValidation(invocation, this.validator, groups);
}
}
//xxx
}
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {
// 约束注解必要的三个成员属性
private static final Set<String> internalAnnotationAttributes = new HashSet<String>(3);
static {
// 命中约束的错误提示信息
internalAnnotationAttributes.add("message");
// 分组校验使用
internalAnnotationAttributes.add("groups");
// 负载
internalAnnotationAttributes.add("payload");
}
private javax.validation.Validator targetValidator;
// 创建一个适配器
public SpringValidatorAdapter(javax.validation.Validator targetValidator) {
Assert.notNull(targetValidator, "Target Validator must not be null");
this.targetValidator = targetValidator;
}
SpringValidatorAdapter() {
}
void setTargetValidator(javax.validation.Validator targetValidator) {
this.targetValidator = targetValidator;
}
public boolean supports(Class<?> clazz) {
return (this.targetValidator != null);
}
// 调用校验器校验目标对象,把Validator校验的结果-ConstraintViolations错误信息,都放在Errors的BindingResult里
// 总之,就是把失败信息对象转换成spring内部的验证失败信息对象
public void validate(Object target, Errors errors) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
}
// 省略其他
}
2.2、Service方法参数校验
public interface SchedulerServiceClient {
/**
* 获取应用不同环境的所有定时任务
* @param appName 应用名称
* @param env 环境
* @param status 任务状态
* @param userId 用户工号
* @return
*/
List<JobConfigInfo> queryJobList( String appName,
String env,
Integer status,
String userId);
}
"BIZ-SERVICE") (topic =
3000) (serviceInterface = SchedulerServiceClient.class, clientTimeout =
public class SchedulerServiceClientImpl implements SchedulerServiceClient {
type = LogSourceEnum.SERVICE) (
public List<JobConfigInfo> queryJobList(String appName, String env, Integer status, String userId) {
// 省略业务代码
}
public class CreateDingNotificationRequest extends ToString {
/**
* 通知类型
*/
"通知类型不能为空") (message =
"通知类型不在枚举值范围内") (enumType = ProcessControlDingTypeEnum.class, message =
private String dingType;
// 省略其他
}
public interface ProcessControlDingService {
/**
* 发送钉钉通知
* @param request
* @return
**/
void createDingNotification(@Valid CreateDingNotificationRequest request);
}
5000) (serviceInterface = ProcessControlDingService.class, clientTimeout =
public class ProcessControlDingServiceImpl implements ProcessControlDingService {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerNames.BIZ_SERVICE);
private ProcessControlTaskService processControlTaskService;
(type = LogSourceEnum.SERVICE)
public void createDingNotification(CreateDingNotificationRequest request) {
// 省略业务代码
}
}
// 返回 bean 中所有约束违反约束校验结果
Set<ConstraintViolation<T>> violationSet = FastValidatorUtils.validate(bean);
"BIZ-SERVICE") (topic =
public class RequestValidAspect {
"@annotation(requestValid)") (
public Object around(ProceedingJoinPoint joinPoint, RequestValid requestValid) throws Throwable {
// 获取方法入参、入参类型、出参类型
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class<?>[] parameterTypes = signature.getParameterTypes();
Class<?> returnType = signature.getMethod().getReturnType();
// 调用前校验每个入参
for (Object arg : args) {
if (arg == null) {
continue;
}
try {
if (arg instanceof List && ((List<?>) arg).size() > 0) {
for (int j = 0; j < ((List<?>) arg).size(); j++) {
validate(((List<?>) arg).get(j));
}
} else {
validate(arg);
}
} catch (AlscBoltBizValidateException e) {
// 将异常处理为需要的格式返回
}
}
// 方法运行后校验是否有入参约束
Object result;
try {
result = joinPoint.proceed();
} catch (ConstraintViolationException e) {
// 将异常处理为需要的格式返回
}
return result;
}
public static <T> void validate(T t) {
try {
Set<ConstraintViolation<T>> res = FastValidatorUtils.validate(t);
if (!res.isEmpty()) {
ConstraintViolation<T> constraintViolation = res.iterator().next();
FastValidatorHelper.throwFastValidateException(constraintViolation);
}
LoggerUtil.info(log, "validator,校验成功");
} catch (FastValidatorException e) {
LoggerUtil.error(log, "validator,校验报错,request=[{}],result=[{}]", JSON.toJSONString(t),
e.getMessage());
throw new AlscBoltBizValidateException(CommonResultCode.ILLEGAL_PARAMETERS, e.getMessage());
}
}
}
public boolean saveCheckResult(List<CheckResultInfoModel> models) {
//xxxx
}
三、最后
参考链接:
[1]https://beanvalidation.org/specification/
[2]https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation-beanvalidation
[3]https://docs.oracle.com/javaee/7/api/javax/validation/Valid.html
[4]https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/validation/annotation/Validated.java