目录- SpringBoot国际化和Validation融合
- 场景
- 实现原理
- 示例
- 引入依赖
- 国际化配置文件
- 配置MessageSource
- 配置LocalValidatorFactoryBean
- 使用校验
- 自定义校验
- Controller层异常处理
- 内部方法校验
- 注意点
- 总结
SpringBoot国际化和Validation融合
场景
在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。
前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。
实现原理
SpringBoot支持国际化和Validation,主要通过MessageSource接口和Validator实现。
国际化配置
- 编写国际化配置文件,如
- messages_en_US.properties
复制代码 和- messages_zh_CN.properties
复制代码 ,并置于目录下。 - 配置或以指定国际化文件的位置,例如
- spring.messages.basename=i18n/messages
复制代码 。 - 配置以解析当前请求的locale,常用的实现是
- AcceptHeaderLocaleResolver
复制代码 ,它通过请求头获取当前的locale。
Validation配置
引入 - spring-boot-starter-validation
复制代码依赖以支持Validation功能
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-validation</artifactId>
- </dependency>
复制代码
配置 - LocalValidatorFactoryBean
复制代码以使用国际化的校验消息,需注入
示例
引入依赖
- <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>
复制代码
国际化配置文件
在 目录下创建两个文件: - 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的配置文件中( 或 ),配置 以指定国际化文件的位置。
如果你打算使用Validation的默认国际化文件,你实际上不需要为Validation单独指定文件,因为 - LocalValidatorFactoryBean
复制代码会自动查找 - ValidationMessages.properties
复制代码。
但是,你可以配置自己的国际化文件,并让 同时服务于你的应用消息和Validation消息。
- # 国际化文件被放置在src/main/resources/i18n目录下,并以messages为前缀
- spring.messages.basename=i18n/messages,org.hibernate.validator.ValidationMessages
- spring.messages.encoding=utf-8
复制代码
注意:上面的配置假设你的自定义消息文件位于 ,而Validation的默认消息文件是 - org.hibernate.validator.ValidationMessages.properties
复制代码。
实际上, - ValidationMessages.properties
复制代码文件位于Hibernate Validator的jar包中,所以你不需要显式地将它包含在你的资源目录中。Spring Boot会自动从classpath中加载它。
配置LocalValidatorFactoryBean
在你的配置类中,创建一个 - LocalValidatorFactoryBean
复制代码的bean,并将 注入到它中。
这样, - LocalValidatorFactoryBean
复制代码就会使用Spring的 来解析校验消息。
- 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
- }
复制代码
文件中,你可以添加
- not.null.message=This field cannot be null.
复制代码
而在Hibernate Validator的 - ValidationMessages.properties
复制代码文件中,已经包含了默认的校验消息,如 - {javax.validation.constraints.NotNull.message}
复制代码的值。
自定义校验
- 定义约束注解:创建一个注解,用标记,并定义、和属性
- 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 {};
- }
复制代码
- 实现约束验证器**:创建一个实现了接口的类,并重写方法
- 在方法中使用
- ConstraintValidatorContext
复制代码 **:如果验证失败,使用- ConstraintValidatorContext
复制代码 的- buildConstraintViolationWithTemplate
复制代码 方法来构建
- 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();
- }
- }
复制代码
在这个例子中,如果 是 并且 小于 ,验证器将使用 - ConstraintValidatorContext
复制代码来构建一个带有错误消息的 。
消息模板 将会在验证失败时被解析,并替换为你在 注解中定义的默认消息或你在 文件中定义的国际化消息。
确保你的 注解定义了一个 属性,并且你在 文件中有一个对应的条目例如:
- 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,会读取 配置。
请求中Locale的获取是通过 进行处理,默认是 - AcceptHeaderLocaleResolver
复制代码,通过 注入,从 请求头中获取locale信息。
此时前端可以在不同语言环境时传入不同的请求头Accept-Language即可达到切换语言的效果
- Accept-Language: en-Us
- Accept-Language: zh-CN
复制代码
默认情况下前端请求中的不用处理,如果约定其他信息传递Local,使用自定义的I18nLocaleResolver替换默认的 - AcceptHeaderLocaleResolver
复制代码,重写 方法就可以自定义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) {
-
- }
- }
- }
复制代码
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持晓枫资讯。 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |