Spring国际化和Validation

SpringBoot国际化和Validation融合

场景

在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。

实现原理

SpringBoot支持国际化和Validation,主要通过MessageSource接口和Validator实现。

国际化配置

  • 编写国际化配置文件,如messages_en_US.propertiesmessages_zh_CN.properties,并置于resources/i18n目录下。
  • 配置application.ymlapplication.properties以指定国际化文件的位置,例如spring.messages.basename=i18n/messages
  • 配置LocaleResolver以解析当前请求的locale,常用的实现是AcceptHeaderLocaleResolver,它通过请求头accept-language获取当前的locale。

Validation配置

  • 引入spring-boot-starter-validation依赖以支持Validation功能

  • 		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
    
  • 配置LocalValidatorFactoryBean以使用国际化的校验消息,需注入MessageSource

示例

引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

国际化配置文件

src/main/resources/i18n目录下创建两个文件:messages_en_US.propertiesmessages_zh_CN.properties

#messages_en_US.properties
welcome.message=Welcome to our website!#messages_zh_CN.properties
welcome.message=欢迎来到我们的网站!

配置MessageSource

在Spring Boot的配置文件中(application.propertiesapplication.yml),配置MessageSource以指定国际化文件的位置。如果你打算使用Validation的默认国际化文件,你实际上不需要为Validation单独指定文件,因为LocalValidatorFactoryBean会自动查找ValidationMessages.properties。但是,你可以配置自己的国际化文件,并让MessageSource同时服务于你的应用消息和Validation消息。

# 国际化文件被放置在src/main/resources/i18n目录下,并以messages为前缀
spring.messages.basename=i18n/messages,org.hibernate.validator.ValidationMessages
spring.messages.encoding=utf-8

注意:上面的配置假设你的自定义消息文件位于i18n/messages.properties,而Validation的默认消息文件是org.hibernate.validator.ValidationMessages.properties。实际上,ValidationMessages.properties文件位于Hibernate Validator的jar包中,所以你不需要显式地将它包含在你的资源目录中。Spring Boot会自动从classpath中加载它。

配置LocalValidatorFactoryBean

在你的配置类中,创建一个LocalValidatorFactoryBean的bean,并将MessageSource注入到它中。这样,LocalValidatorFactoryBean就会使用Spring的MessageSource来解析校验消息。

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;import java.util.Properties;@Configuration
public class ValidateConfig {@Beanpublic LocalValidatorFactoryBean validatorFactoryBean(MessageSource messageSource) {LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();factoryBean.setValidationMessageSource(messageSource);//        设置使用 HibernateValidator 校验器factoryBean.setProviderClass(HibernateValidator.class);
//        设置 快速异常返回 只要有一个校验错误就立即返回失败,其他参数不在校验Properties properties = new Properties();properties.setProperty("hibernate.validator.fail_fast", "true");factoryBean.setValidationProperties(properties);
//        加载配置factoryBean.afterPropertiesSet();return factoryBean;}
}

使用校验

import javax.validation.constraints.NotNull;public class MyModel {@NotNull(message = "{not.null.message}")private String field;// getters and setters
}

messages.properties文件中,你可以添加

not.null.message=This field cannot be null.

而在Hibernate Validator的ValidationMessages.properties文件中,已经包含了默认的校验消息,如{javax.validation.constraints.NotNull.message}的值。

自定义校验

  • 定义约束注解:创建一个注解,用@Constraint标记,并定义messagegroupspayload属性
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyValidateContent.class)
public @interface MyValidate {String message() default "";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
  • 实现约束验证器**:创建一个实现了ConstraintValidator接口的类,并重写isValid方法
  • isValid方法中使用ConstraintValidatorContext**‌:如果验证失败,使用ConstraintValidatorContextbuildConstraintViolationWithTemplate方法来构建ConstraintViolation
import com.example.dto.ParamVo;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class MyValidateContent implements ConstraintValidator<MyValidate, ParamVo> {@Overridepublic void initialize(MyConstraint constraintAnnotation) {// 初始化代码(如果需要的话)}@Overridepublic boolean isValid(ParamVo paramVo, ConstraintValidatorContext constraintValidatorContext) {if ("N".equals(paramVo.getSex())) {if (paramVo.getAge() < 18) {buildMessage(constraintValidatorContext, "template1");return false;}} else {if (paramVo.getAge() <  20) {buildMessage(constraintValidatorContext, "template2");return false;}}return true;}private void buildMessage(ConstraintValidatorContext context, String key) {String template = ('{'+key+'}').intern();context.buildConstraintViolationWithTemplate(template).addConstraintViolation();}
}

在这个例子中,如果sexN并且age小于18,验证器将使用ConstraintValidatorContext来构建一个带有错误消息的ConstraintViolation。消息模板"{template1}"将会在验证失败时被解析,并替换为你在MyValidate注解中定义的默认消息或你在messages.properties文件中定义的国际化消息。

确保你的MyValidate注解定义了一个message属性,并且你在messages.properties文件中有一个对应的条目例如:

template1=男性要大于18
template2=女性要大于20
import com.example.validate.MyValidate;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;@Getter
@Setter
@MyValidate
public class ParamVo {@NotBlank(message = "{javax.validation.constraints.NotNull.message}")private String sex;@NotNull(message = "age 不能为空")private Integer age;@NotBlank(message = "{name.not.null}")@Length(max = 3,message = "{name.length.max}")private String name;
}

Controller层异常处理

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;
import java.util.Map;@RestControllerAdvice
public class GlobalException {@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {Map<String, String> errors = new HashMap<>();BindingResult result = ex.getBindingResult();for (FieldError error : result.getFieldErrors()) {errors.put(error.getField(), error.getDefaultMessage());}// 这里可以根据实际需求定制返回的错误信息结构Map<String, Object> response = new HashMap<>();response.put("status", HttpStatus.BAD_REQUEST.value());response.put("errors", errors);return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);}
}

内部方法校验

import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
//@Validated 
@Validated
public interface ServiceIntface {//校验返回值,校验入参@NotNull Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class ServiceImpl implements ServiceIntface {@Overridepublic Object hello(Integer id, String name) {return null;}
}
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;@RestControllerAdvice
public class GlobalException {@ExceptionHandler(ConstraintViolationException.class)public ResponseEntity<Object> handleValidationExceptions(ConstraintViolationException ex) {Map<String, String> errors = new HashMap<>();Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();for (ConstraintViolation<?> constraintViolation : constraintViolations) {String key = constraintViolation.getPropertyPath().toString();String message = constraintViolation.getMessage();errors.put(key, message);}// 这里可以根据实际需求定制返回的错误信息结构Map<String, Object> response = new HashMap<>();response.put("status", HttpStatus.BAD_REQUEST.value());response.put("errors", errors);return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);}
}
  • 校验写在接口上的,抛出异常javax.validation.ConstraintViolationException
  • 校验写在具体实现,抛出异常javax.validation.ConstraintDeclarationException

注意点

代码中国际化使用

  • 代码里响应,手动获取使用MessageSource的getMessage方法即可,也就是spring容器中的getMessage()

  • # messages_en_US.properties
    welcome.message=Welcome to our website!# messages_zh_CN.properties
    welcome.message=欢迎来到我们的网站!#定义消息,并使用占位符{0}、{1}等表示参数位置
    #welcome.message=欢迎{0}来到{1}
  • //创建一个配置类来配置LocaleResolver,以便根据请求解析当前的语言环境:
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.LocaleResolver;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.servlet.i18n.SessionLocaleResolver;import java.util.Locale;@Configuration
    public class WebConfig implements WebMvcConfigurer {@Beanpublic LocaleResolver localeResolver() {SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();sessionLocaleResolver.setDefaultLocale(Locale.US); // 设置默认语言return sessionLocaleResolver;}
    }
  • //创建一个控制器来使用国际化的消息
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.MessageSource;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
    import java.util.Locale;@RestController
    @RequestMapping("/hello")
    public class HelloController {@Autowiredprivate MessageSource messageSource;@GetMappingpublic String hello(HttpServletRequest request) {Locale locale = (Locale) request.getAttribute(org.springframework.web.servlet.LocaleResolver.LOCALE_RESOLVER_ATTRIBUTE);//messageSource.getMessage("welcome.message", new Object[]{"张三", "中国"}, Locale.CHINA)。return messageSource.getMessage("welcome.message", null, locale);}
    }

Locale获取

默认情况下spring注册的messageSource对象为ResourceBundleMessageSource,会读取spring.message配置。

请求中Locale的获取是通过LocaleResolver进行处理,默认是AcceptHeaderLocaleResolver,通过WebMvcAutoConfiguration注入,从Accept-Language请求头中获取locale信息。

此时前端可以在不同语言环境时传入不同的请求头Accept-Language即可达到切换语言的效果

Accept-Language: en-Us
Accept-Language: zh-CN

默认情况下前端请求中的不用处理,如果约定其他信息传递Local,使用自定义的I18nLocaleResolver替换默认的AcceptHeaderLocaleResolver,重写resolveLocale方法就可以自定义Locale的解析逻辑。

import cn.hutool.core.util.StrUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;/****/
@Configuration
public class I18nConfig {@Beanpublic LocaleResolver localeResolver() {return new I18nLocaleResolver();}/*** 获取请求头国际化信息* 使用自定义的I18nLocaleResolver替换默认的AcceptHeaderLocaleResolver,重写resolveLocale方法就可以自定义Locale的解析逻辑。** 自定义后使用content-language传Locale信息,使用_划分语言个地区。* content-language: en_US* content-language: zh_CN*/static class I18nLocaleResolver implements LocaleResolver {@Overridepublic Locale resolveLocale(HttpServletRequest httpServletRequest) {String language = httpServletRequest.getHeader("content-language");Locale locale = Locale.getDefault();if (StrUtil.isNotBlank(language)) {String[] split = language.split("_");locale = new Locale(split[0], split[1]);}return locale;}@Overridepublic void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/55757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pymeshlab 学习笔记

目录 安装&#xff1a; pymeshlab不能直接可视化点云&#xff0c;用open3d可视化 pymeshlab保存物体的横截面&#xff08;compute planar section&#xff09; 点云去噪&#xff1a; 安装&#xff1a; pip install pymeshlab pip install open3d pymeshlab不能直接可视化…

Pandas 时间序列处理

Pandas 时间序列处理 说明&#xff1a; 请回答以下问题&#xff0c;以展示您对 pandas 中时间序列处理的全面理解。请在适用时提供代码示例。 问题 1 如何将日期字符串列表 [2023-01-01, 2023-01-02, 2023-01-03] 转换为 pandas 的 DatetimeIndex&#xff1f; 问题 2 给定一…

使用 Nginx 和 Gunicorn 部署 Flask 项目详细教程

使用 Nginx 和 Gunicorn 部署 Flask 项目详细教程 在这篇文章中&#xff0c;我们将介绍如何使用 Nginx 和 Gunicorn 来部署一个 Flask 项目。这种部署方式非常适合在生产环境中使用&#xff0c;因为它能够提供更好的性能和更高的稳定性。 目录 Flask 项目简介环境准备Gunico…

centos 7.9系统redis6.2.6哨兵模式部署

由于系统需要处理大量的数据并发请求,所以借助于Redis的高性能,可以有效提升整个系统的处理效率。这里采用redis6.2版本源码编译部署哨兵模式,提高整个系统的可用性,避免单点故障。 1. Redis基本环境安装 centos7安装redis 6.2.6 采用源码编译方式安装。 服务器主机名:…

思科dhcp的配置

以路由器为例 让pc3 自动获取ip地址并获取的网段为172.16.4.100-172.16.4.200 配置如下&#xff1a; R1(config)#interface GigabitEthernet0/2 R1(config)#ip address 172.16.4.254 255.255.255.0 R1(config)# no shutdown R1(config)#ip dhcp pool 4_pool //创建dhcp地址池…

如何使用 Python 读取数据量庞大的 excel 文件

使用 pandas.read_excel 读取大文件时&#xff0c;的确会遇到性能瓶颈&#xff0c;特别是对于10万行20列这种规模的 .xlsx 文件&#xff0c;常规的 pandas 方法可能会比较慢。 要提高读取速度&#xff0c;关键是找到更高效的方式处理 Excel 文件&#xff0c;特别是在 Python 的…

毕业设计项目——基于transformer的中文医疗领域命名实体识别(论文/代码)

完整的论文代码见文章末尾 以下为核心内容 摘要 近年来&#xff0c;随着深度学习技术的发展&#xff0c;基于Transformer和BERT的模型在自然语言处理领域取得了显著进展。在中文医疗领域&#xff0c;命名实体识别(Named Entity Recognition, NER)是一项重要任务&#xff0c;旨…

uniapp实战教程:如何封装一个可复用的表单组件

在uniapp开发过程中&#xff0c;表单组件的使用场景非常广泛。为了提高开发效率&#xff0c;我们可以将常用的表单组件进行封装。本文将带你了解如何在uniapp中封装一个表单组件&#xff0c;让你只需要通过属性配置轻松实现各种表单&#xff0c;效果图如下&#xff1a; 一、准备…

如何利用免费音频剪辑软件制作出精彩音频

现在有许多免费的音频剪辑软件可供选择&#xff0c;它们为广大用户提供了丰富的功能和便捷的操作体验&#xff0c;让音频编辑变得更加轻松和有趣。接下来&#xff0c;让我们一起走进这些免费音频剪辑软件的世界&#xff0c;探索它们的独特魅力和强大功能。 1.福昕音频剪辑 链…

【Nacos入门到实战十四】Nacos配置管理:集群部署与高可用策略

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

点云补全 学习笔记

目录 Depth completion with convolutions and vision transformers 依赖项&#xff1a; DCNv2 softpoolnet Depth completion with convolutions and vision transformers Zhang, Y., Guo, X., Poggi, M., Zhu, Z., Huang, G., Mattoccia, S.: Completionformer: Depth co…

力扣10.6

134. 加油站 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。 给定两个整数数组 …

docker运行arm64架构的镜像、不同平台镜像构建

背景 Docker 允许开发者将应用及其依赖打包成一个轻量级、可移植的容器&#xff0c;实现“一次构建&#xff0c;到处运行”的目标。然而&#xff0c;不同的操作系统和硬件架构对容器镜像有不同的要求。例如&#xff0c;Linux 和 Windows 系统有不同的文件系统和系统调用&#…

【预备理论知识——2】深度学习:线性代数概述

简单地说&#xff0c;机器学习就是做出预测。 线性代数 线性代数是数学的一个分支&#xff0c;主要研究向量空间、线性方程组、矩阵理论、线性变换、特征值和特征向量、内积空间等概念。它是现代数学的基础之一&#xff0c;并且在物理学、工程学、计算机科学、经济学等领域有着…

css3-----2D转换、动画

2D 转换&#xff08;transform&#xff09; 转换&#xff08;transform&#xff09;是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 移动&#xff1a;translate旋转&#xff1a;rotate缩放&#xff1a;scale 二维坐标系 2D 转换之移动 trans…

OpenGL笔记十九之相机系统

OpenGL笔记十九之相机系统 —— 2024-10-02 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记十九之相机系统1.运行1.1.游戏相机1.2.轨迹球相机 2.游戏相机与轨迹球相机切换3.博主笔记本要运行需要更改的文件更改1:28_OpenGL_CameraSystem/applicat…

C语言文件操作(下)(28)

文章目录 前言一、文件的打开和关闭打开打开模式相对路径和绝对路径 关闭 二、文件操作正确流程三、文件顺序读写函数fopenfclosefputcfgetcfputsfgetsfprintffscanfsprintfsscanffwritefread 四、文件随机读写函数fseekftellrewind 五、文件读取结束时候的判断feofferror具体例…

高级 Java Redis 客户端 有哪些?

高级Java Redis客户端主要包括以下几种&#xff1a; 1. Redisson &#xff08;https://github.com/redisson/redisson&#xff09; 特点&#xff1a;Redisson是一个在Redis的基础上实现的Java驻留数据网格&#xff08;In-Memory Data Grid&#xff09;。它不仅是一个Redis的J…

4个顶级的大模型推理引擎

LLM 在文本生成应用中表现出色&#xff0c;例如具有高理解度和流畅度的聊天和代码完成模型。然而&#xff0c;它们的庞大规模也给推理带来了挑战。基本推理速度很慢&#xff0c;因为 LLM 会逐个生成文本标记&#xff0c;需要对每个下一个标记进行重复调用。随着输入序列的增长&…

什么是 Tammann temperature

Tammann temperature (Tt_tt​) 是材料科学中一个重要的概念&#xff0c;它通常用于描述材料的热力学特性和相变行为。其定义与玻璃态和晶态材料的内部原子运动相关。Tammann 温度在研究材料的扩散、再结晶、以及玻璃化转变过程中具有重要意义。 1. Tammann 温度的定义 Tamma…