欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-13 09:24:40
导包和配置 导入 jsr 303 的包、hibernate valid 的包 org....

导包和配置

导入 jsr 303 的包、hibernate valid 的包

<dependency>
  <groupid>org.hibernate.validator</groupid>
  <artifactid>hibernate-validator</artifactid>
  <version>6.0.5.final</version>
</dependency>
<dependency>
  <groupid>javax.validation</groupid>
  <artifactid>validation-api</artifactid>
  <version>2.0.0.final</version>
</dependency>

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 接口等等,都是自定义约定的东西。

/**
 * 私有 id,是代表项目任务/非项目任务/风险/问题/评审待办问题等多张表的外键
 */
@min(value = 1, message = "{todo.privateid.min}", groups = {c.insert.class, c.update.class, s.insert.class, s.update.class})
private long privateid;
/**
 * 创建者id
 */
@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 <t> set<constraintviolation<t>> validate(t object, class<?>... groups) {
    return validator.validate(object, groups);
  }
  public <t> set<constraintviolation<t>> validatevalue(class<t> beantype, string propertyname, object value, class<?>... groups) {
    return validator.validatevalue(beantype, propertyname, value, groups);
  }
  /**
   * 校验参数,并返回第一个错误提示
   * @param t   验证的对象
   * @param groups 验证的组别
   * @param <t>  对象擦除前原类型
   * @return 第一个错误提示
   */
  public <t> void validandreturnfirsterrortips(t t, class<?>... groups) {
    set<constraintviolation<t>> validate = validator.validate(t, groups);
    if (validate.size() > 0) {
      constraintviolation<t> next = validate.iterator().next();
      string message = next.getrootbeanclass().getname() + "-" + next.getpropertypath() + "-" + next.getmessage();
      throw new validerror(message);
    }
  }
  /**
   * 校验参数,并返回第一个错误提示
   * @param targetclass 验证的对象的 class 类型
   * @param fieldname  需要验证的名字
   * @param obj     需要属性值
   * @param groups   验证的组别
   * @param <t>     对象擦除前原类型
   * @return 第一个错误提示
   */
  public <t> void validandreturnfirsterrortips(class targetclass, string fieldname, object obj, class<?>... groups) {
    set<constraintviolation<t>> 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;
  /**
   *  定义拦截规则:拦截 com.servic  包下面的所有类中,有 @service 注解的方法。
   */
  @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 {
  /**
   * 查询 单个待办
   * @param id 序号
   * @return 单个待办
   */
  msg getvo(@min(value = 1, message = "待办 id 不能小于 1。") long id);
  /**
   * 添加数据
   * @param todo 对象
   */
  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.*;
/**
 * 字符串判空验证,hibernate 自带的可能有问题,使用不了,需要重写,package 是不能变的。
 */
@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<? extends payload>[] payload() default {};
  class notblankvalidator implements constraintvalidator<notblank, object> {
    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
/**
 * 待办类型0非项目任务1项目任务2问题3风险4评审待办问题
 */
@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<? extends payload>[] payload() default {};
  class todotypevalidfactory implements constraintvalidator<todotypevalid, string> {
    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
/**
 * 待办状态0未开始1进行中2已完成
 */
@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<? extends payload>[] payload() default {};
  class todostatusvalidfactory implements constraintvalidator<todostatusvalid, todostatus> {
    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 校验 使用消息资源文件对消息国际化,希望对大家有所帮助

上一篇:

下一篇: