Spring国际化和Validation
SpringBoot国际化和Validation融合
场景
在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。
实现原理
SpringBoot支持国际化和Validation,主要通过MessageSource接口和Validator实现。
国际化配置
- 编写国际化配置文件,如
messages_en_US.properties
和messages_zh_CN.properties
,并置于resources/i18n
目录下。 - 配置
application.yml
或application.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.properties
和messages_zh_CN.properties
。
#messages_en_US.properties
welcome.message=Welcome to our website!#messages_zh_CN.properties
welcome.message=欢迎来到我们的网站!
配置MessageSource
在Spring Boot的配置文件中(application.properties
或application.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
标记,并定义message
、groups
和payload
属性
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
**:如果验证失败,使用ConstraintValidatorContext
的buildConstraintViolationWithTemplate
方法来构建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();}
}
在这个例子中,如果sex
是N
并且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) {}}
}
相关文章:

Spring国际化和Validation
SpringBoot国际化和Validation融合 场景 在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。 实现原…...

②EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关
EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherCAT 转 Modbus TCP (接上一章) GW系列型号 配置说明 上载 网线连接电脑到模块上的 WEB 网页设置网口&#…...

【华为HCIP实战课程八】OSPF网络类型及报文类型详解,网络工程师
一、点到点网络类型 1、两台路由器 2、支持广播、组播 P2P(PPP、HDLC、帧中继子接口) 我们需要三个维度考虑 A、是否自动通过组播发现邻居 B、时间(Hello和Dead) C、DR和BDR----多点接入网络需要用到(广播和NBMA) 点到点是组播自动发现邻居,Hello 10S,Dead 40S…...

信息安全工程师(28)机房安全分析与防护
前言 机房安全分析与防护是一个复杂而细致的过程,涉及到物理安全、环境控制、电力供应、数据安全、设备管理、人员管理以及紧急预案等多个方面。 一、机房安全分析 1. 物理安全威胁 非法入侵:未经授权的人员可能通过门窗、通风口等进入机房,…...

大数据处理从零开始————9.MapReduce编程实践之信息过滤之学生成绩统计demo
1.项目目标 1.1 需求概述 现在我们要统计某学校学生的成绩信息,筛选出成绩在60分及以上的学生。 1.2 业务分析 如果我们想实现该需求,可以通过编写一个MapReduce程序,来处理包含学生信息的文本文件,每行包含【学生的姓名&#x…...

自动化测试 | 窗口截图
driver.get_screenshot_as_file 是 Selenium WebDriver 的一个方法,它允许你将当前浏览器窗口(或标签页)的截图保存为文件。这个方法对于自动化测试中的截图验证非常有用,因为它可以帮助你捕获测试执行过程中的页面状态。 以下是…...

初中数学网上考试系统的设计与实现(论文+源码)_kaic
初中数学网上考试系统的设计与实现 学生: 指导教师: 摘 要:科技在人类的历史长流中愈洗愈精,不仅包括人们日常的生活起居,甚至还包括了考试的变化。之前的考试需要大量的时间和精力,组织者还需要挑选并考查…...

关系运算(3)
关系代数 昨天讲完附加关系代数运算,今天讲扩展关系代数运算。 扩展代数运算 正如其名,这种运算定义了前面基本和附加都没有的运算。 去重运算 可以将关系R中跟查询条件相关但是形成了重复的元组去除,只保留查询结果(简洁&…...

tp6的系统是如何上架的
TP6(ThinkPHP6)的系统上架过程,通常指的是将基于ThinkPHP6框架开发的应用程序部署到生产环境,并使其可以通过互联网访问。以下是一个大致的上架流程,包括准备工作、部署步骤以及后续维护等方面: 一、准备工…...

Vue:开发小技巧
目录 1. Table表格偏移 1. Table表格偏移 通过设置自小的宽度进行控制 :min-width <el-table-column label"操作" align"center" class-name"small-padding fixed-width" fixed"right" min-width"150px"><templa…...

力扣之1369.获取最近第二次的活动
题目: sql建表语句 Create table If Not Exists UserActivity (username varchar(30), activity varchar(30), startDate date, endDate date); Truncate table UserActivity; insert into UserActivity (username, activity, startDate, endDate) values (Alic…...

Python 和 Jupyter Kernel 版本不一致
使用jupyter notebook时明明已经安装了包,但是导入时提示: ModuleNotFoundError: No module named ptitprince 1、检查安装环境 !pip show ptitprince Name: ptitprince Version: 0.2.7 Summary: A Python implementation of Rainclouds, originally…...

Android常用布局
目录 布局文件中常见的属性 1. 基本布局属性 1)android:layout_width 2)android:layout_height 3)android:layout_margin 4)android:padding 2. 线性布局 (LinearLayout) 属性 1)android:orientation 2)and…...

初级网络工程师之从入门到入狱(五)
本文是我在学习过程中记录学习的点点滴滴,目的是为了学完之后巩固一下顺便也和大家分享一下,日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、链路聚合1.1、手动进行链路聚合1.1.1、 拓扑图:1.1.2、 LSW11.1.3、 LSW2 1.2、…...

JavaScript轮播图实现
这个代码创建了一个简单的轮播图,可以通过点击左右箭头或自动播放来切换图片。 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>js轮播图练习</title><style>.box {width: 60vw;height: 500px;m…...

【LLM开源项目】LLMs-开发框架-Langchain-Tutorials-Basics-v2.0
【1】使用LCEL构建简单的LLM应用程序(Build a Simple LLM Application with LCEL) https://python.langchain.com/docs/tutorials/llm_chain/ 如何使用LangChain构建简单的LLM应用程序。功能:将把文本从英语翻译成另一种语言。 实现:LLM调用加上一些提…...

Python 爬取天气预报并进行可视化分析
今天,我们就来学习如何使用 Python 爬取天气预报数据,并用数据可视化的方式将未来几天的天气信息一目了然地展示出来。 在本文中,我们将分三步完成这一任务: 使用 Python 爬取天气数据数据解析与处理用可视化展示天气趋势 让我…...

最左侧冗余覆盖子串
题目描述 给定两个字符串 s1 和 s2 和正整数 k,其中 s1 长度为 n1,s2 长度为 n2。 在 s2 中选一个子串,若满足下面条件,则称 s2 以长度 k 冗余覆盖 s1 该子串长度为 n1 k 该子串中包含 s1 中全部字母 该子串每个字母出现次数…...

性能测试-JMeter(2)
JMeter JMeter断言响应断言JSON断言断言持续时间 JMeter关联正则表达式提取器正则表达式正则表达式提取器 XPath提取器JSON提取器 JMeter属性JMeter录制脚本 JMeter断言 断言:让程序自动判断预期结果和实际结果是否一致 提示: -Jmeter在请求的返回层面有…...

芯课堂 | Synwit_UI_Creator(μgui)平台之图像处理篇
今天小编给大家介绍的是UI_Creator(μgui)平台下关于图像处理的选项。 UI_Creator(μgui)平台图片类控件有图像控件和分级图像控件,均包含以下选项: 1、消除水波纹: 由于16位真彩色(…...

QT C++ 软键盘/悬浮键盘/触摸屏键盘的制作
目录 1、前言 2、界面设计 3、英文、数字的输入 4、符号的输入 5、中文的输入 6、中文拼音库的选择 7、其他 8、结语 1、前言 使用QT C在带显示器的Linux系统 开发板上(树莓派等)编写操作UI界面时,很多时候都需要一个软键盘来输入文字…...

element-ui点击文字查看图片预览功能
今天做一个点击文字查看图片的功能,大体页面长这样子,点击查看显示对应的图片 引入el-image-viewer,点击的文字时候设置图片预览组件显示并传入图片的地址 关键代码 <el-link v-if"scope.row.fileList.length > 0" type&…...

SpringBoot集成Redis使用Cache缓存
使用SpringBoot集成Redis使用Cache缓存只要配置相应的配置类,然后使用Cache注解就能实现 RedisConfig配置 新建RedisConfig配置类 package com.bdqn.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annota…...

【瑞萨RA8D1 CPK开发板】lcd显示
1.8寸lcd使用gpio模拟spi驱动 由于板子引出的接口限制,故使用gpio模拟spi驱动中景园的1.8寸lcd 1.77寸液晶屏 1.8寸TFT LCD SPI TFT彩屏st7735驱动128x160高清屏-淘宝网 (taobao.com) 使用RASC 的gpio配置 根据厂家提供的驱动文件移植 #define LCD_SCLK_Clr() g…...

算法收敛的一些证明方法与案例
证明一个算法收敛通常涉及多个角度,以下是一些常用的方法和示例: 一、方法 1. 数学归纳法 通过数学归纳法证明算法在每一步的输出结果都在收敛范围内。 示例:考虑一个递归算法,假设我们要证明它在每一步中输出的值逐渐接近目标…...

基于vue框架的蛋糕店网上商城740g7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,店长,商品分类,商品信息,订单投诉,反馈信息 开题报告内容 基于Vue框架的蛋糕店网上商城开题报告 一、项目背景与意义 随着互联网技术的快速发展和普及,电子商务已成为现代商业的重要组成部分。蛋糕作为一种受欢迎的美…...

你真的了解Canvas吗--解密六【ZRender篇】
目录 📚入口 Circle - 图形 Group - 组 事件捕获 - 流程 step - 1 step - 2 总结 这篇文章我们讲讲Circle圆形,Group组的使用以及大家最熟悉又陌生的事件捕获和冒泡在ZRender中的实现,篇幅较长,且听我慢慢分析。 &#x…...

孤独相伴 - 结婚十七年
07年的今天,我和老公请假,去了新加坡的大使馆领证。 17年后的今天,此刻凌晨16分, 这是17年来我第一次这么早写结婚纪念,只是凑巧。 今天的心情莫名其妙。 此刻,两个词出现在我的脑海:孤独 &am…...

json-server,跨域
启动json-serer json-server --watch db.json 注意: db.json为json文件的名称,你自己的文件名叫什么,就启动对应的文件就可以了 启动json-server的时候,必须在你db.json所在的文件夹下进行启动 这样服务器就可以启动成功了&…...

【Conda】修复 Anaconda 安装并保留虚拟环境的详细指南
目录 流程图示1. 下载 Anaconda 安装程序2. 重命名现有的 Anaconda 安装目录Windows 操作系统Linux 操作系统 3. 运行新的 Anaconda 安装程序Windows 操作系统Linux 操作系统 4. 同步原环境使用 robocopy 命令(Windows)使用 rsync 命令(Linux…...