这可能是你见过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以及国际化,在墙里墙外找了很多很久,可能是因为版本的更新迭代,找到的资料基本都用不了了。自己折腾了半天,终于琢磨出来了,特此记录。
685 0
这可能是你见过hibernate-validator最全国际化方案(下)
|
6天前
|
SQL 缓存 Java
框架分析(9)-Hibernate
框架分析(9)-Hibernate
|
6天前
|
SQL 缓存 Java
Java一分钟之-Hibernate:ORM框架实践
【5月更文挑战第15天】Hibernate是Java的ORM框架,简化数据库操作。本文列举并解决了一些常见问题: 1. 配置SessionFactory,检查数据库连接和JDBC驱动。 2. 实体类需标记主键,属性映射应匹配数据库列。 3. 使用事务管理Session,记得关闭。 4. CRUD操作时注意对象状态和查询结果转换。 5. 使用正确HQL语法,防止SQL注入。 6. 根据需求配置缓存。 7. 懒加载需在事务内处理,避免`LazyInitializationException`。理解和避免这些问题能提升开发效率。
25 0
|
6天前
|
SQL Java 数据库连接
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
|
6天前
|
SQL Java 关系型数据库
数据库访问:什么是Hibernate框架?
【4月更文挑战第15天】Hibernate是开源ORM框架,将Java对象与数据库表映射,简化对象-关系映射,提升开发效率和性能。它自动化处理数据操作,支持多种数据库,自动生成SQL,提供配置选项和高级特性,减少手动SQL编写,便于切换数据库。
26 2
|
10月前
|
SQL XML 存储
Hibernate框架【五】——基本映射——多对多映射
Hibernate框架【五】——基本映射——多对多映射
135 0
|
8月前
|
Java 数据库连接
简述使用Hibernate框架的几个步骤
简述使用Hibernate框架的几个步骤
41 0
|
9月前
|
SQL Java 关系型数据库
Hibernate框架概述
Hibernate框架概述
81 0
|
10月前
|
SQL 缓存 Oracle
Hibernate框架【一】——HIbernate框架介绍
Hibernate框架【一】——HIbernate框架介绍
183 0
|
10月前
|
SQL XML Java
Hibernate框架【四】——基本映射——多对一和一对多映射
Hibernate框架【四】——基本映射——多对一和一对多映射
109 0