Spring国际化和Validation

news2024/10/8 18:29:52

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 {

    @Bean
    public 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> {
    @Override
    public void initialize(MyConstraint constraintAnnotation) {
        // 初始化代码(如果需要的话)
    }
    @Override
    public 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 {
    @Override
    public 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 {
    
        @Bean
        public 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 {
    
        @Autowired
        private MessageSource messageSource;
    
        @GetMapping
        public 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 {
    @Bean
    public 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 {

        @Override
        public 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;
        }

        @Override
        public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

        }
    }
}

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

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

相关文章

刚转Mac的新手如何卸载不需要的应用程序

最开始转Mac系统的时候很是苦恼&#xff0c;到底该怎么卸载App啊&#xff0c;App直接拖到废纸篓真的能卸载干净吗&#xff0c;卸载App时会不会留下一些文件残留&#xff0c;慢慢的会不会占满内存&#xff0c;于是我找到了一个免费的卸载工具——XApp。 这是一款Mac应用程序卸载…

《贪吃蛇小游戏 1.0》源码

好久不见&#xff01; 终于搞好了简易版贪吃蛇小游戏&#xff08;C语言版&#xff09;&#xff0c;邀请你来玩一下~ 目录 Snake.h Snake.c test.c Snake.h #include<stdio.h> #include<windows.h> #include<stdbool.h> #include<stdlib.h> #inclu…

某国有资本运营中心人才选拔项目纪实

某国有资本运营中心人才选拔项目纪实 【客户行业】 政府与事业单位 【问题类型】 人才招聘选拔 【客户背景】 在三年国企改革过程中&#xff0c;南方某省政府为响应国家政策&#xff0c;提出组建专业化国有资本投资运营公司&#xff0c;大力开展专业化资本运营&#xff0c;…

016 规格参数

文章目录 新增AttrController.javaAttrVo.javaAttrServiceImpl.javaAttrAttrgroupRelationEntity.javaAttrEntity.javaAttrGroupEntity.java 查询AttrController.javaAttrServiceImpl.javaAttrRespVo.java 修改回显AttrController.javaAttrServiceImpl.java 修改提交AttrContro…

京东云主机和云服务器有啥区别?轻量云主机就是轻量应用服务器吗?

京东云主机和云服务器有啥区别&#xff1f;轻量云主机就是轻量应用服务器吗&#xff1f;云主机就是云服务器的意思&#xff0c;是京东云给自家云服务器取的名字&#xff0c;阿里云叫云服务器ECS&#xff0c;腾讯云叫云服务器CVM&#xff0c;京东云服务器叫云主机&#xff0c;京…

C++ osgEarth 多窗口 同步绘制geometry

开发环境&#xff1a; win10 64bit、Qt5.15.2、C 、MSVC2019 、osg3.5.6、 osgEarth3.1 接触osgEarth不久&#xff0c;贴出来&#xff0c;希望大家指正。 注意osgEarth版本。 采用观察者设计模式&#xff0c;设置 master 和 slave 窗口&#xff0c;通过管理类和信号槽维护窗…

_c++11

嗨喽大家好呀&#xff0c;今天阿鑫给大家带来的是c进阶——c11的内容&#xff0c;好久不见啦&#xff0c;下面让我们进入本节博客的内容吧&#xff01; _c11 统一的列表初始化右值引用可变模板参数(了解&#xff0c;不常接触)lambda表达式function和bind包装器 1. 统一的列表…

JavaWeb 15.详解Servlet及其源码

所有受过的委屈&#xff0c;都在提醒你 要好好争气。 —— 24.10.7 一、Servlet简介 1.动态资源和静态资源 静态资源 无需在程序运行时通过代码运行生成的资源&#xff0c;在程序运行之前就写好的资源&#xff0c;例如&#xff1a;html、css、js、img、音频文件和视频文件 …

职场秘籍:面试加薪,竟然拥有不同的技巧!

假如你是一位测试主管&#xff0c;去评价一名测试工程师是否优秀&#xff0c;那么你将如何去判断呢&#xff1f;你最看重的是哪方面的能力呢&#xff1f; 对于这个问题&#xff0c;是不能一概而论的&#xff0c;要分为两种情况&#xff0c;情况不同&#xff0c;答案一定是不同…

高校新生报道管理系统使用SpringBootSSM框架开发

&#xff01;&#xff01;&#xff01;页面底部,文章结尾,加我好友,获取计算机毕设开发资料 目录 一、引言 二、相关技术介绍 三、系统需求分析 四、系统设计 五、关键技术实现 六、测试与优化 七、总结与展望 一、引言 当前高校新生报到过程中存在许多问题&#xff0c;…

从0到1:用Python构建你人生中的第一个人工智能AI模型

文章目录 摘要引言数据预处理&#xff1a;为模型打下坚实基础数据预处理的步骤Python示例代码说明&#xff1a;注意事项&#xff1a; 模型建立&#xff1a;选择合适的模型神经网络示例代码说明&#xff1a; 模型训练与测试训练示例代码说明&#xff1a; 解读模型结果性能指标 深…

原生小程序开发|小程序卡片(Widget) 开发指南

开发 Widget 代表应用的一个小程序卡片&#xff0c;负责小程序卡片的展示和交互。 小程序卡片(Widget) 的开发在智能小程序的基础上增加一个目录即可&#xff0c;用于存放小程序卡片(Widget)的代码。并在 project.tuya.json 中增加一个声明。 创建小程序卡片(Widget)项目 在 …

九、Drf序列化器

九、序列化器 9.1序列化 从数据库取QuerySet或数据对象转换成JSON 9.1.1序列化器的简易使用 #新建一张部门表 class Depart(models.Model):title=models.CharField(verbose_name=部门,max_length=32)order=models.IntegerField(verbose_name=顺序)count=models.IntegerFiel…

vscode中安装python的包

首先需要调出命令行。然后运行代码&#xff0c;找到你所需要的环境。 PS C:\Users\Administrator\AppData\Local\ESRI\conda\envs\arcgispro-env> conda env list # conda environments: #C:\ProgramData\Anaconda3 base * C:\Users\Administrator\.con…

【无人机设计与控制】无人机三维路径规划,对比蚁群算法,ACO_Astar_RRT算法

摘要 本文探讨了三种不同的无人机三维路径规划算法&#xff0c;即蚁群算法&#xff08;ACO&#xff09;、A算法&#xff08;Astar&#xff09;以及快速随机树算法&#xff08;RRT&#xff09;。通过仿真实验对比了各算法在不同环境下的性能&#xff0c;包括路径长度、计算效率…

软考越来越难了,2024年软考究竟还值不值得考?

最近不少同学沟通&#xff0c;聊到软考现在越来越难了&#xff0c;考了两三次都没过&#xff0c;也有不少新同学咨询软考考试的一些福利政策&#xff0c;投入大量的物力&#xff0c;财力&#xff0c;精力&#xff0c;那么到底软考值不值得考呢&#xff1f; 01 / 关于软考 软考…

【FlagScale】异构算力混合训练方案

背景以及必要性 算力需求的高峰&#xff1a;随着人工智能&#xff08;AI&#xff09;和生成内容&#xff08;AIGC&#xff09;的发展&#xff0c;对计算资源的需求急剧增加。尤其是参数规模达到数百亿的大模型训练&#xff0c;需要大量的计算资源。 算力市场供应紧张&#xff…

一键拯救废片!3个在线教程,实现光线重塑、表情迁移、模糊图像修复

每逢国庆「黄金周」&#xff0c;都是旅游业的高光时刻。根据研判&#xff0c;今年国庆假期全社会跨区域人员流动量将达到 19.4 亿人次&#xff0c;平均每天 2.77 亿人次。 与旅游业同步增长的还有摄影行业&#xff0c;旅拍带动的妆造、服饰租赁等相关环节发展火热&#xff0c;…

Linux安装Redis7.40

一、环境检查 1.1 查看是否已经安装了Redis应用 ps -ef |grep redis或者 whereis redis1.2 若已经安装了redis应用或者有遗留的Redis文件&#xff0c;进行移除或者启动即可。 二、下载&安装 2.1 找到对应的安装包资源&#xff0c;使用wget命令下载&#xff0c;这里安装…

小众交友软件有哪些?小众交友APP排行榜前十名推荐

在网络的广袤天地中&#xff0c;小众交友软件如隐藏的宝藏&#xff0c;散发着独特魅力。它们为人们提供别样的社交舞台&#xff0c;让孤独的灵魂有处可栖。今天&#xff0c;就让我们一同探寻那些小众交友软件的奇妙世界。 1. 咕哇找搭子小程序&#xff1a;这是一个实名制的找搭…