这可能是你见过hibernate-validator最全国际化方案(上)

简介: 为了实现hibernate-validator国际化差点要了老命,最近在研究hibernate-validator以及国际化,在墙里墙外找了很多很久,可能是因为版本的更新迭代,找到的资料基本都用不了了。自己折腾了半天,终于琢磨出来了,特此记录。

1、原生SpringBoot环境


环境信息


  • SpringBoot : 2.3.8.RELEASE
  • hibernate-validator : 6.1.7.Final


验证


hibernate-validator主要用于验证前段请求过来的参数是否满足条件


controller层:


@RestController
@RequestMapping("valid")
public class TestController {
    @PostMapping("/test")
    public Person test(@Valid @RequestBody Person person) {
        return person;
    }
}


实体类


public class Person {
    @NotBlank()
    private String name;
    @Range(min = 2, max = 100)
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


从实体类中可以看出主要有两个验证条件


  • person.name不能为空
  • person.age在2~100之间


如果在此时用不满足条件的数据直接调用接口会返回404,看不到验证出错信息,这是因为Spring拦截了验证异常,直接返回了404


image.png

image.png


想要不返回404需要自定义异常拦截器,把验证异常自己来处理掉


@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({BindException.class})
    public String bindExceptionHandler(final BindException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(" ; "));
        return "{\"errors\":\"" + message + "\"}";
    }
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String handler(final MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(" ; "));
        return "{\"errors\":\"" + message + "\"}";
    }
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ConstraintViolationException.class)
    public String handler(final ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(" ; "));
        return "{\"errors\":\"" + message + "\"}";
    }
}


验证异常就是这三种情况:


  • BindException
  • MethodArgumentNotValidException
  • ConstraintViolationException


增加全局异常处理后,再次调用接口:


image.png

这种处理后报错信息存在两个问题


  • 如何国际化,返回英文和中文
  • 提示信息不明确,能不能自定义


国际化问题


Spring提供了i18N解决方式,跟断点也能发现校验失败后也是走的这套流程,核心类为AcceptHeaderLocaleResolver


image.png


从代码中可以看到语言是通过获取header中的Accept-Language切换的,这里也是固定写死的,如果要切换成其他方式或者其他header值,可以通过继承LocaleChangeInterceptor 覆盖preHandle方法,装备自定义bean使用


image.png


自定义校验信息


1、如果不考虑国际化


这种情况最简单,直接在验证Bean中修改


@NotBlank(message = "名字不能为空吆")
private String name;
@Range(min = 2, max = 100, message = "年龄需要在2~100岁之间")
private Integer age;

image.png


2、考虑国际化


这种情况就不能简单的写死message了,需要通过hibernate-valid提供的国际化方式

image.png

通过hibernate源码可以看到国际化信息都存在一系列的properties文件中,这就给我提供了覆盖的机会,通过在resource下定义同名的国际化文件

首先,修改bean的message为el表达式方式

@NotBlank(message = "{person.name}")
private String name;
@Range(min = 2, max = 100, message = "{person.age}")
private Integer age;

然后,把key定义在自己的国际化文件中:(一定要注意编码格式)

image.png


调用接口后结果:


image.png

如果此时需要自定义国际化文件名和位置,则需要增加如下配置类

@Configuration
public class MessageConfig {
    public ResourceBundleMessageSource getMessageSource() throws Exception {
        ResourceBundleMessageSource resourceBundle = new ResourceBundleMessageSource();
        resourceBundle.setDefaultEncoding(StandardCharsets.UTF_8.name());
        //指定国际化文件路径
        resourceBundle.setBasenames("i18n/valid");
        return resourceBundle;
    }
    @Bean
    public Validator getValidator() throws Exception {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(getMessageSource());
        return validator;
    }
    @Bean
    public MethodValidationPostProcessor validationPostProcessor() throws Exception {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        //指定请求验证器
        processor.setValidator(getValidator());
        return processor;
    }
}


通过上述配置就可以达到使用指定国际化文件实现验证消息自定义的效果


2、DropWizard工程


DropWizard是一个轻量级的微服务开发框架,本身对hibernate-valid也做了一层封装,但是没有国际化方案


需要使用hibernate-valid原始国际化方式,通过设置默认语言来实现,


如果不需要默认方式,可以跳过此章节


实体类:


public class Person {
    @NotBlank()
    private String name;
    @Range(min = 2, max = 100)
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


资源类


@Service
@Path("/test")
public class TestResource {
    @POST
    @Path("/test")
    @Consumes({APPLICATION_JSON})
    @Produces({APPLICATION_JSON})
    public String test(Person person, @Context final HttpServletRequest request) {
        Set<ConstraintViolation<Person>> validate = getValidator().validate(person);
        if (!validate.isEmpty()) {
            StringJoiner message = new StringJoiner(";");
            for (Object aValidate : validate) {
                message.add(((ConstraintViolation) aValidate).getMessage());
            }
            return "{\"result\":\"" + message.toString() + "\"}";
        }
        return "{\"result\":\"ok\"}";
    }
}


1、只需要自定义消息体


这种场景只需要在实体类中修改message即可


public class Person {
    @NotBlank(message = "姓名不能为空")
    private String name;
    @Range(min = 2, max = 100, message = "年龄需要在2~100之间")
    private Integer age;
}


调用接口会就会返回固定的信息:


image.png


2、只需要国际化


这种场景时使用hibernate-valid自带的国际化消息,可以通过header中Accept-Language来控制语言类型。比如en-US,zh-CN

然后再后端获取header后,设置语言


修改实体类


public class Person {
    @NotBlank()
    private String name;
    @Range(min = 2, max = 100)
    private Integer age;
}


修改资源类


@POST
@Path("/test")
@Consumes({APPLICATION_JSON})
@Produces({APPLICATION_JSON})
public String test(Person person, @Context final HttpServletRequest request) {
    //获取header中语言
    String language = request.getHeader("Accept-Language");
    Set<ConstraintViolation<Person>> validate = getValidator(language).validate(person);
    if (!validate.isEmpty()) {
        StringJoiner message = new StringJoiner(";");
        for (Object aValidate : validate) {
            message.add(((ConstraintViolation) aValidate).getMessage());
        }
        return "{\"result\":\"" + message.toString() + "\"}";
    }
    return "{\"result\":\"ok\"}";
}
private static Validator getValidator(String language) {
    // 设置语言
    Locale.setDefault(language.contains("zh") ? Locale.CHINA : Locale.ENGLISH);
    return Validation.byDefaultProvider().configure()
        .buildValidatorFactory()
        .getValidator();
}


调用接口效果:


image.png

目录
相关文章
|
编解码 Java Spring
这可能是你见过hibernate-validator最全国际化方案(下)
为了实现hibernate-validator国际化差点要了老命,最近在研究hibernate-validator以及国际化,在墙里墙外找了很多很久,可能是因为版本的更新迭代,找到的资料基本都用不了了。自己折腾了半天,终于琢磨出来了,特此记录。
777 0
这可能是你见过hibernate-validator最全国际化方案(下)
|
7月前
|
SQL 缓存 Java
框架分析(9)-Hibernate
框架分析(9)-Hibernate
|
1月前
|
缓存 Java 数据库连接
Hibernate:Java持久层框架的高效应用
通过上述步骤,可以在Java项目中高效应用Hibernate框架,实现对关系数据库的透明持久化管理。Hibernate提供的强大功能和灵活配置,使得开发者能够专注于业务逻辑的实现,而不必过多关注底层数据库操作。
14 1
|
4月前
|
SQL Java 数据库连接
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。通过映射机制,它可以自动处理对象与数据库表之间的转换,支持主流数据库,提高了代码的可移植性和可维护性。其核心接口包括 SessionFactory、Session 和 Transaction 等,通过它们可以执行数据库的 CRUD 操作。配置方面,需在项目中引入 Hibernate 及数据库驱动依赖,并创建 `hibernate.cfg.xml` 配置文件来设置数据库连接和 Hibernate 行为参数。
51 1
|
4月前
|
数据库 Java 数据库连接
Struts 2 与 Hibernate 的完美邂逅:如何无缝集成两大框架,轻松玩转高效 CRUD 操作?
【8月更文挑战第31天】本文通过具体示例介绍了如何在 Struts 2 中整合 Hibernate,实现基本的 CRUD 操作。首先创建 Maven 项目并添加相关依赖,接着配置 Hibernate 并定义实体类及其映射文件。然后创建 DAO 接口及实现类处理数据库操作,再通过 Struts 2 的 Action 类处理用户请求。最后配置 `struts.xml` 文件并创建 JSP 页面展示用户列表及编辑表单。此示例展示了如何配置和使用这两个框架,使代码更加模块化和可维护。
107 0
|
5月前
|
SQL Java 数据库连接
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
82 0
|
6月前
|
Java 数据库连接 数据库
探索JPA生态:Hibernate与其他ORM框架的对比分析
【6月更文挑战第25天】**JPA标准下的Hibernate是流行的ORM实现,提供丰富功能如二级缓存和延迟加载,但其学习曲线较陡,性能优化复杂。相比如MyBatis,Hibernate的JPQL更面向对象,MyBatis则接近SQL。选择ORM需考虑项目需求和个人偏好。**
90 0
|
6月前
|
Java 数据库连接
杨老师课堂之JavaEE三大框架Hibernate入门第一课
杨老师课堂之JavaEE三大框架Hibernate入门第一课
32 0
|
7月前
|
SQL Java 数据库连接
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
|
7月前
|
SQL Java 关系型数据库
数据库访问:什么是Hibernate框架?
【4月更文挑战第15天】Hibernate是开源ORM框架,将Java对象与数据库表映射,简化对象-关系映射,提升开发效率和性能。它自动化处理数据操作,支持多种数据库,自动生成SQL,提供配置选项和高级特性,减少手动SQL编写,便于切换数据库。
73 2