栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化

导包和配置

导入 JSR 303 的包、hibernate valid 的包


  org.hibernate.validator
  hibernate-validator
  6.0.5.Final


  javax.validation
  validation-api
  2.0.0.Final

springboot 配置

resources/application.yml 消息资源文件国际化处理配置

spring:
  messages:
    basename: base,todo # 资源文件 base.properties 和 todo.properties,多个用逗号隔开

    encoding: UTF-8 # 必须指定解析编码,否则中文乱码

在 springboot 启动类里面配置

@SpringBootApplication
public class Application extends WebMvcConfigurerAdapter {
  @Value("${spring.messages.basename}")
  private String basename;
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
  @Bean
  @Primary
  public MessageSource messageSource() {
    ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
    resourceBundleMessageSource.setUseCodeAsDefaultMessage(false);
    resourceBundleMessageSource.setDefaultEncoding("UTF-8"); // 重复定义
    resourceBundleMessageSource.setbasenames(basename.split(","));
    return resourceBundleMessageSource;
  }
  @Bean
  @Primary
  public LocalValidatorFactoryBean validator() {
    LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
    validatorFactoryBean.setProviderClass(Hibernatevalidator.class);
    validatorFactoryBean.setValidationMessageSource(messageSource());
    return validatorFactoryBean;
  }
  @Override
  public Validator getValidator() {
    return validator();
  }
  
  @Bean
  public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
  }
}

我们对于校验参数通过不了抛出的异常进行处理,是通过统一异常捕捉。

@ControllerAdvice
@Component
public class BindValidExceptionHandler {
  @ResponseStatus(value = HttpStatus.OK)
  @ExceptionHandler(ConstraintViolationException.class)
  public @ResponseBody
  Msg handleConstraintViolationException(ConstraintViolationException e) {
    String messageTemplate = e.getConstraintViolations().iterator().next().getMessageTemplate();
    return Msg.error(messageTemplate);
  }
  @ResponseStatus(value = HttpStatus.OK)
  @ExceptionHandler(BindException.class)
  public @ResponseBody
  Msg handleBindException(BindException e) {
    BindingResult bindingResult = e.getBindingResult();
    String className = bindingResult.getTarget().getClass().getName();
    FieldError next = bindingResult.getFieldErrors().iterator().next();
    String fieldName = next.getField();
    String defaultMessage = next.getDefaultMessage();
    if (Pattern.compile("IllegalArgumentException: No enum").matcher(defaultMessage).find()) {
      Matcher matcher = Pattern.compile("for value '(.*?)'").matcher(defaultMessage);
      if (matcher.find()) {
 defaultMessage = "找不到枚举类型【" + matcher.group(1) + "】";
      }
    }
    return Msg.error(defaultMessage);
  }
  @ResponseStatus(value = HttpStatus.OK)
  @ExceptionHandler(ValidError.class)
  public @ResponseBody
  Msg handlevalidError(ValidError e) {
    return Msg.error(e.getMessage());
  }
}

resources/base.propertie

creatorId=创建者 id 不能为小于 {value}。

modifierId=修改者 id 不能为小于 {value}。

resources/todo.properties

todo.privateId.min=私有 id 不能为小于 {value}。

在 bean 字段上使用注解,其中 group 中的 C 和 S 接口是指 Controller 和 Service 的叫法简称,里面分别有 Insert 接口、Update 接口等等,都是自定义约定的东西。


@Min(value = 1, message = "{todo.privateId.min}", groups = {C.Insert.class, C.Update.class, S.Insert.class, S.Update.class})
private long privateId;

@Min(value = 1, message = "{creatorId}", groups = {S.Insert.class})
private long creatorId;

Controller 控制层验证

@Validated
@RestController
@RequestMapping("todo")
public class TodoController {
  @Autowired
  private TodoService todoService;
  @GetMapping("getVo")
  public Msg getVo(
    @Min(value = 1, message = "待办 id 不能小于 1。")
    @RequestParam(required = false, defaultValue = "0")
    long id
  ) {
    return this.todoService.getVo(id);
  }
  @PostMapping("add")
  public Msg add(@Validated({C.Insert.class}) Todo todo) {
    return this.todoService.add(todo);
  }
}

@Validated({C.Insert.class}) 声明启用 bean 注解上的验证组,其他验证组不会进行验证,这样可以区别开来进行单独验证。

而像没有实体,只有一个基础数据类型的,可以进行验证,但是需要满足三个条件:

  • 在启动类配置方法级别验证启用类
  • 在 Controller 类上注解 @Validated
  • 在方法参数里使用验证注解如 @Min,@NotNull 等等

自行验证。

Service 服务层 AOP 验证

ValidUtil 工具类

需要被 springboot 扫描并注册为单例

@Component
public class ValidUtil {
  @Autowired
  private Validator validator;
  public  Set> validate(T object, Class... groups) {
    return validator.validate(object, groups);
  }
  public  Set> validatevalue(Class beanType, String propertyName, Object value, Class... groups) {
    return validator.validatevalue(beanType, propertyName, value, groups);
  }
  
  public  void validAndReturnFirstErrorTips(T t, Class... groups) {
    Set> validate = validator.validate(t, groups);
    if (validate.size() > 0) {
      ConstraintViolation next = validate.iterator().next();
      String message = next.getRootBeanClass().getName() + "-" + next.getPropertyPath() + "-" + next.getMessage();
      throw new ValidError(message);
    }
  }
  
  public  void validAndReturnFirstErrorTips(Class targetClass, String fieldName, Object obj, Class... groups) {
    Set> validate = validator.validatevalue(targetClass, fieldName, obj, groups);
    if (validate.size() > 0) {
      String message = targetClass.getName() + "-" + fieldName + "-" + validate.iterator().next().getMessage();
      throw new ValidError(message);
    }
  }
}

AOP 配置

主要原理是利用 aop 拦截方法执行参数,对参数获取注解。再利用工具类来验证参数,如果验证不通过,直接抛出自定义错误,自定义错误已经全局统一处理了。

@Aspect
@Component
public class ValidatorAOP {
  @Autowired
  private ValidUtil validUtil;
  
  @Pointcut("execution(* com.service..*(..)) and @annotation(org.springframework.stereotype.Service)")
  public void controllerMethodPointcut() {
  }
  
  @Around("controllerMethodPointcut()") // 指定拦截器规则;也可以直接把 “execution(* com.xjj.........)” 写进这里
  public Object Interceptor(ProceedingJoinPoint pjp) {
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
    Method method = methodSignature.getMethod();
    Annotation[][] argAnnotations = method.getParameterAnnotations();
    Object[] args = pjp.getArgs();
    for (int i = 0; i < args.length; i++) {
      for (Annotation annotation : argAnnotations[i]) {
 if (Validated.class.isInstance(annotation)) {
   Validated validated = (Validated) annotation;
   Class[] groups = validated.value();
   validUtil.validAndReturnFirstErrorTips(args[i], groups);
 }
      }
    }
    try {
      return pjp.proceed(args);
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
    return true;
  }
}

验证注解 @Min @NotNull 使用方法

不能写在实现类上,只能在接口中使用注解

与 Controller 使用方式基本一样

@Validated
public interface TodoService {
  
  Msg getVo(@Min(value = 1, message = "待办 id 不能小于 1。") long id);
  
  Msg add(@Validated({S.Insert.class}) Todo todo);
}

分享几个自定义验证注解

字符串判空验证

package javax.validation.constraints;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;

@documented
@Constraint(
    validatedBy = {NotBlank.NotBlankValidator.class}
)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotBlank {
  Class[] groups() default {};
  String message() default "{notBlank}";
  Class[] payload() default {};
  class NotBlankValidator implements ConstraintValidator {
    public NotBlankValidator() {
    }
    @Override
    public void initialize(NotBlank constraintAnnotation) {
    }
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
      return value != null && !value.toString().isEmpty();
    }
  }
}

类型判断,判断 type 是否为其中一个值,可以根据验证组自定义判断

resources/todo.properties

todo.todoType.insert=新增时,待办类型只能是 非项目任务、项目任务、问题 之中一。
todo.todoType.update=修改时,待办类型只能是风险、评审待办问题 之中一。
bean

@TodoTypevalid(value = {"0", "1", "2"}, message = "{todo.todoType.insert}", groups = {C.Insert.class, S.Insert.class})
@TodoTypevalid(value = {"3", "4"}, message = "{todo.todoType.update}", groups = {C.Update.class, S.Update.class})
private String todoType;

自定义注解

@documented
@Constraint(validatedBy = {TodoTypevalid.TodoTypevalidFactory.class})
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(TodoTypevalid.List.class)
public @interface TodoTypevalid {
  String message() default "请输入正确的类型";
  String[] value() default {};
  Class[] groups() default {};
  Class[] payload() default {};
  class TodoTypevalidFactory implements ConstraintValidator {
    private String[] annotationValue;
    @Override
    public void initialize(TodoTypevalid todoStatusValid) {
      this.annotationValue = todoStatusValid.value();
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
      if (Arrays.asList(annotationValue).contains(value))
 return true;
      return false;
    }
  }
  @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
  @Retention(RetentionPolicy.RUNTIME)
  @documented
  @interface List {
    TodoTypevalid[] value();
  }
}

@Repeatable(TodoTypevalid.List.class) 是 JDK8 支持的同一注解多次特性。

根据上面的同样也可以用在枚举类上

resources/todo.properties
todo.todoStatus.insert=新增时,状态只能是未开始。
todo.todoStatus.update=修改时,状态只能是进行中或已完成。
bean

@TodoStatusValid(enums = {TodoStatus.NOT_STARTED}, message = "{todo.todoStatus.insert}", groups = {C.Insert.class, S.Insert.class})
@TodoStatusValid(enums = {TodoStatus.PROCESSING, TodoStatus.COMPLETED}, message = "{todo.todoStatus.update}", groups = {C.Update.class, S.Update.class})
private TodoStatus todoStatus;

自定义注解

@documented
@Constraint(validatedBy = {TodoStatusValid.TodoStatusValidFactory.class})
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(TodoStatusValid.List.class)
public @interface TodoStatusValid {
  String message() default "请输入正确的状态";
  TodoStatus[] enums() default {};
  Class[] groups() default {};
  Class[] payload() default {};
  class TodoStatusValidFactory implements ConstraintValidator {
    private TodoStatus[] enums;
    @Override
    public void initialize(TodoStatusValid todoStatusValid) {
      this.enums = todoStatusValid.enums();
    }
    @Override
    public boolean isValid(TodoStatus value, ConstraintValidatorContext context) {
      TodoStatus[] values = TodoStatus.values();
      if (enums != null && enums.length != 0) {
 values = enums;
      }
      if (Arrays.asList(values).contains(value))
 return true;
      return false;
    }
  }
  @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
  @Retention(RetentionPolicy.RUNTIME)
  @documented
  @interface List {
    TodoStatusValid[] value();
  }
}

总结

以上所述是小编给大家介绍的Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对考高分网网站的支持!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/142553.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号