设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间18 现在时间18 缓存数据 从头到尾 我要的只有感情 可没人能给我

从头到尾 我要的只有感情 可没人能给我 -- 情深深雨濛濛

查看: 1396|回复: 2

Spring国际化和Validation详解

[复制链接]

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
38
主题
30
精华
0
金钱
111
积分
74
注册时间
2023-10-3
最后登录
2025-9-1

发表于 2024-11-23 20:11:44 来自手机 | 显示全部楼层 |阅读模式
目录
  • SpringBoot国际化和Validation融合
    • 场景
    • 实现原理
      • 国际化配置‌
      • ‌Validation配置‌
    • 示例
      • 引入依赖
      • 国际化配置文件
      • 配置MessageSource‌
      • 配置LocalValidatorFactoryBean‌
      • 使用校验
      • 自定义校验
      • Controller层异常处理
      • 内部方法校验
    • 注意点
      • 代码中国际化使用
      • Locale获取
  • 总结

    SpringBoot国际化和Validation融合

    场景

    在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。

    前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。

    实现原理

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

    国际化配置‌

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

    ‌Validation配置‌

    引入

    1. spring-boot-starter-validation
    复制代码
    依赖以支持Validation功能

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-validation</artifactId>
    4. </dependency>
    复制代码

    配置

    1. LocalValidatorFactoryBean
    复制代码
    以使用国际化的校验消息,需注入
    1. MessageSource
    复制代码

    示例

    引入依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-validation</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>org.springframework.boot</groupId>
    7. <artifactId>spring-boot-starter-web</artifactId>
    8. </dependency>
    复制代码

    国际化配置文件

    1. src/main/resources/i18n
    复制代码
    目录下创建两个文件:
    1. messages_en_US.properties
    复制代码
    1. messages_zh_CN.properties
    复制代码

    1. #messages_en_US.properties
    2. welcome.message=Welcome to our website!
    3. #messages_zh_CN.properties
    4. welcome.message=欢迎来到我们的网站!
    复制代码

    配置MessageSource‌

    在Spring Boot的配置文件中(

    1. application.properties
    复制代码
    1. application.yml
    复制代码
    ),配置
    1. MessageSource
    复制代码
    以指定国际化文件的位置。

    如果你打算使用Validation的默认国际化文件,你实际上不需要为Validation单独指定文件,因为

    1. LocalValidatorFactoryBean
    复制代码
    会自动查找
    1. ValidationMessages.properties
    复制代码

    但是,你可以配置自己的国际化文件,并让

    1. MessageSource
    复制代码
    同时服务于你的应用消息和Validation消息。

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

    注意:上面的配置假设你的自定义消息文件位于

    1. i18n/messages.properties
    复制代码
    ,而Validation的默认消息文件是
    1. org.hibernate.validator.ValidationMessages.properties
    复制代码

    实际上,

    1. ValidationMessages.properties
    复制代码
    文件位于Hibernate Validator的jar包中,所以你不需要显式地将它包含在你的资源目录中。Spring Boot会自动从classpath中加载它。

    配置LocalValidatorFactoryBean‌

    在你的配置类中,创建一个

    1. LocalValidatorFactoryBean
    复制代码
    的bean,并将
    1. MessageSource
    复制代码
    注入到它中。

    这样,

    1. LocalValidatorFactoryBean
    复制代码
    就会使用Spring的
    1. MessageSource
    复制代码
    来解析校验消息。

    1. import org.hibernate.validator.HibernateValidator;
    2. import org.springframework.context.MessageSource;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    6. import java.util.Properties;
    7. @Configuration
    8. public class ValidateConfig {
    9. @Bean
    10. public LocalValidatorFactoryBean validatorFactoryBean(MessageSource messageSource) {
    11. LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    12. factoryBean.setValidationMessageSource(messageSource);
    13. // 设置使用 HibernateValidator 校验器
    14. factoryBean.setProviderClass(HibernateValidator.class);
    15. // 设置 快速异常返回 只要有一个校验错误就立即返回失败,其他参数不在校验
    16. Properties properties = new Properties();
    17. properties.setProperty("hibernate.validator.fail_fast", "true");
    18. factoryBean.setValidationProperties(properties);
    19. // 加载配置
    20. factoryBean.afterPropertiesSet();
    21. return factoryBean;
    22. }
    23. }
    复制代码

    使用校验

    1. import javax.validation.constraints.NotNull;
    2. public class MyModel {
    3. @NotNull(message = "{not.null.message}")
    4. private String field;
    5. // getters and setters
    6. }
    复制代码

    1. messages.properties
    复制代码
    文件中,你可以添加

    1. not.null.message=This field cannot be null.
    复制代码

    而在Hibernate Validator的

    1. ValidationMessages.properties
    复制代码
    文件中,已经包含了默认的校验消息,如
    1. {javax.validation.constraints.NotNull.message}
    复制代码
    的值。

    自定义校验

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

    在这个例子中,如果

    1. sex
    复制代码
    1. N
    复制代码
    并且
    1. age
    复制代码
    小于
    1. 18
    复制代码
    ,验证器将使用
    1. ConstraintValidatorContext
    复制代码
    来构建一个带有错误消息的
    1. ConstraintViolation
    复制代码

    消息模板

    1. "{template1}"
    复制代码
    将会在验证失败时被解析,并替换为你在
    1. MyValidate
    复制代码
    注解中定义的默认消息或你在
    1. messages.properties
    复制代码
    文件中定义的国际化消息。

    确保你的

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

    1. template1=男性要大于18
    2. template2=女性要大于20
    复制代码
    1. import com.example.validate.MyValidate;
    2. import lombok.Getter;
    3. import lombok.Setter;
    4. import org.hibernate.validator.constraints.Length;
    5. import javax.validation.constraints.NotBlank;
    6. import javax.validation.constraints.NotNull;
    7. @Getter
    8. @Setter
    9. @MyValidate
    10. public class ParamVo {
    11. @NotBlank(message = "{javax.validation.constraints.NotNull.message}")
    12. private String sex;
    13. @NotNull(message = "age 不能为空")
    14. private Integer age;
    15. @NotBlank(message = "{name.not.null}")
    16. @Length(max = 3,message = "{name.length.max}")
    17. private String name;
    18. }
    复制代码

    Controller层异常处理

    1. import org.springframework.http.HttpStatus;
    2. import org.springframework.http.ResponseEntity;
    3. import org.springframework.validation.BindingResult;
    4. import org.springframework.validation.FieldError;
    5. import org.springframework.web.bind.MethodArgumentNotValidException;
    6. import org.springframework.web.bind.annotation.ExceptionHandler;
    7. import org.springframework.web.bind.annotation.RestControllerAdvice;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. @RestControllerAdvice
    11. public class GlobalException {
    12. @ExceptionHandler(MethodArgumentNotValidException.class)
    13. public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
    14. Map<String, String> errors = new HashMap<>();
    15. BindingResult result = ex.getBindingResult();
    16. for (FieldError error : result.getFieldErrors()) {
    17. errors.put(error.getField(), error.getDefaultMessage());
    18. }
    19. // 这里可以根据实际需求定制返回的错误信息结构
    20. Map<String, Object> response = new HashMap<>();
    21. response.put("status", HttpStatus.BAD_REQUEST.value());
    22. response.put("errors", errors);
    23. return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    24. }
    25. }
    复制代码

    内部方法校验

    1. import org.springframework.validation.annotation.Validated;
    2. import javax.validation.constraints.Min;
    3. import javax.validation.constraints.NotNull;
    4. //@Validated
    5. @Validated
    6. public interface ServiceIntface {
    7. //校验返回值,校验入参
    8. @NotNull Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
    9. }
    复制代码
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.stereotype.Service;
    3. @Slf4j
    4. @Service
    5. public class ServiceImpl implements ServiceIntface {
    6. @Override
    7. public Object hello(Integer id, String name) {
    8. return null;
    9. }
    10. }
    复制代码
    1. import org.springframework.http.HttpStatus;
    2. import org.springframework.http.ResponseEntity;
    3. import org.springframework.web.bind.annotation.ExceptionHandler;
    4. import org.springframework.web.bind.annotation.RestControllerAdvice;
    5. import javax.validation.ConstraintViolation;
    6. import javax.validation.ConstraintViolationException;
    7. import java.util.HashMap;
    8. import java.util.Map;
    9. import java.util.Set;
    10. @RestControllerAdvice
    11. public class GlobalException {
    12. @ExceptionHandler(ConstraintViolationException.class)
    13. public ResponseEntity<Object> handleValidationExceptions(ConstraintViolationException ex) {
    14. Map<String, String> errors = new HashMap<>();
    15. Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
    16. for (ConstraintViolation<?> constraintViolation : constraintViolations) {
    17. String key = constraintViolation.getPropertyPath().toString();
    18. String message = constraintViolation.getMessage();
    19. errors.put(key, message);
    20. }
    21. // 这里可以根据实际需求定制返回的错误信息结构
    22. Map<String, Object> response = new HashMap<>();
    23. response.put("status", HttpStatus.BAD_REQUEST.value());
    24. response.put("errors", errors);
    25. return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    26. }
    27. }
    复制代码
    • 校验写在接口上的,抛出异常
      1. javax.validation.ConstraintViolationException
      复制代码
    • 校验写在具体实现,抛出异常
      1. javax.validation.ConstraintDeclarationException
      复制代码

    注意点

    代码中国际化使用

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

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

    Locale获取

    默认情况下spring注册的messageSource对象为ResourceBundleMessageSource,会读取

    1. spring.message
    复制代码
    配置。

    请求中Locale的获取是通过

    1. LocaleResolver
    复制代码
    进行处理,默认是
    1. AcceptHeaderLocaleResolver
    复制代码
    ,通过
    1. WebMvcAutoConfiguration
    复制代码
    注入,从
    1. Accept-Language
    复制代码
    请求头中获取locale信息。

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

    1. Accept-Language: en-Us
    2. Accept-Language: zh-CN
    复制代码

    默认情况下前端请求中的不用处理,如果约定其他信息传递Local,使用自定义的I18nLocaleResolver替换默认的

    1. AcceptHeaderLocaleResolver
    复制代码
    ,重写
    1. resolveLocale
    复制代码
    方法就可以自定义Locale的解析逻辑。

    1. import cn.hutool.core.util.StrUtil;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.web.servlet.LocaleResolver;
    5. import javax.servlet.http.HttpServletRequest;
    6. import javax.servlet.http.HttpServletResponse;
    7. import java.util.Locale;
    8. /**
    9. *
    10. */
    11. @Configuration
    12. public class I18nConfig {
    13. @Bean
    14. public LocaleResolver localeResolver() {
    15. return new I18nLocaleResolver();
    16. }
    17. /**
    18. * 获取请求头国际化信息
    19. * 使用自定义的I18nLocaleResolver替换默认的AcceptHeaderLocaleResolver,重写resolveLocale方法就可以自定义Locale的解析逻辑。
    20. *
    21. * 自定义后使用content-language传Locale信息,使用_划分语言个地区。
    22. * content-language: en_US
    23. * content-language: zh_CN
    24. */
    25. static class I18nLocaleResolver implements LocaleResolver {
    26. @Override
    27. public Locale resolveLocale(HttpServletRequest httpServletRequest) {
    28. String language = httpServletRequest.getHeader("content-language");
    29. Locale locale = Locale.getDefault();
    30. if (StrUtil.isNotBlank(language)) {
    31. String[] split = language.split("_");
    32. locale = new Locale(split[0], split[1]);
    33. }
    34. return locale;
    35. }
    36. @Override
    37. public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
    38. }
    39. }
    40. }
    复制代码

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持晓枫资讯。


    免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
    晓枫资讯-科技资讯社区-免责声明
    免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
          1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
          2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
          3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
          4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
          5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
    http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

      离线 

    TA的专栏

    等级头衔

    等級:晓枫资讯-列兵

    在线时间
    0 小时

    积分成就
    威望
    0
    贡献
    0
    主题
    0
    精华
    0
    金钱
    20
    积分
    20
    注册时间
    2022-12-24
    最后登录
    2022-12-24

    发表于 2025-1-1 01:55:17 | 显示全部楼层
    路过,支持一下
    http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

      离线 

    TA的专栏

    等级头衔

    等級:晓枫资讯-列兵

    在线时间
    0 小时

    积分成就
    威望
    0
    贡献
    0
    主题
    0
    精华
    0
    金钱
    13
    积分
    6
    注册时间
    2022-12-24
    最后登录
    2022-12-24

    发表于 2025-7-30 19:44:39 | 显示全部楼层
    顶顶更健康!!!
    http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
    严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    1楼
    2楼
    3楼

    手机版|晓枫资讯--科技资讯社区 本站已运行

    CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

    晓枫资讯--科技资讯社区

    本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

    如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

    Powered by Discuz! X3.5

    快速回复 返回顶部 返回列表