Spring國際化和Validation詳解
SpringBoot國際化和Validation融合
場景
在應(yīng)用交互時,可能需要根據(jù)客戶端得語言來返回不同的語言數(shù)據(jù)。
前端通過參數(shù)、請求頭等往后端傳入locale相關(guān)得參數(shù),后端獲取參數(shù),根據(jù)不同得locale來獲取不同得語言得文本信息返回給前端。
實現(xiàn)原理
SpringBoot支持國際化和Validation,主要通過MessageSource接口和Validator實現(xiàn)。
國際化配置
- 編寫國際化配置文件,如
messages_en_US.properties
和messages_zh_CN.properties
,并置于resources/i18n
目錄下。 - 配置
application.yml
或application.properties
以指定國際化文件的位置,例如spring.messages.basename=i18n/messages
。 - 配置
LocaleResolver
以解析當(dāng)前請求的locale,常用的實現(xiàn)是AcceptHeaderLocaleResolver
,它通過請求頭accept-language
獲取當(dāng)前的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
目錄下創(chuàng)建兩個文件:messages_en_US.properties
和messages_zh_CN.properties
。
#messages_en_US.properties welcome.message=Welcome to our website! #messages_zh_CN.properties welcome.message=歡迎來到我們的網(wǎng)站!
配置MessageSource
在Spring Boot的配置文件中(application.properties
或application.yml
),配置MessageSource
以指定國際化文件的位置。
如果你打算使用Validation的默認國際化文件,你實際上不需要為Validation單獨指定文件,因為LocalValidatorFactoryBean
會自動查找ValidationMessages.properties
。
但是,你可以配置自己的國際化文件,并讓MessageSource
同時服務(wù)于你的應(yīng)用消息和Validation消息。
# 國際化文件被放置在src/main/resources/i18n目錄下,并以messages為前綴 spring.messages.basename=i18n/messages,org.hibernate.validator.ValidationMessages spring.messages.encoding=utf-8
注意:上面的配置假設(shè)你的自定義消息文件位于i18n/messages.properties
,而Validation的默認消息文件是org.hibernate.validator.ValidationMessages.properties
。
實際上,ValidationMessages.properties
文件位于Hibernate Validator的jar包中,所以你不需要顯式地將它包含在你的資源目錄中。Spring Boot會自動從classpath中加載它。
配置LocalValidatorFactoryBean
在你的配置類中,創(chuàng)建一個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); // 設(shè)置使用 HibernateValidator 校驗器 factoryBean.setProviderClass(HibernateValidator.class); // 設(shè)置 快速異常返回 只要有一個校驗錯誤就立即返回失敗,其他參數(shù)不在校驗 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
文件中,已經(jīng)包含了默認的校驗消息,如{javax.validation.constraints.NotNull.message}
的值。
自定義校驗
- 定義約束注解:創(chuàng)建一個注解,用
@Constraint
標(biāo)記,并定義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 {}; }
- 實現(xiàn)約束驗證器**:創(chuàng)建一個實現(xiàn)了
ConstraintValidator
接口的類,并重寫isValid
方法 - 在
isValid
方法中使用ConstraintValidatorContext
**:如果驗證失敗,使用ConstraintValidatorContext
的buildConstraintViolationWithTemplate
方法來構(gòu)建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(); } }
在這個例子中,如果sex
是N
并且age
小于18
,驗證器將使用ConstraintValidatorContext
來構(gòu)建一個帶有錯誤消息的ConstraintViolation
。
消息模板"{template1}"
將會在驗證失敗時被解析,并替換為你在MyValidate
注解中定義的默認消息或你在messages.properties
文件中定義的國際化消息。
確保你的MyValidate
注解定義了一個message
屬性,并且你在messages.properties
文件中有一個對應(yīng)的條目例如:
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()); } // 這里可以根據(jù)實際需求定制返回的錯誤信息結(jié)構(gòu) Map<String, Object> response = new HashMap<>(); response.put("status", HttpStatus.BAD_REQUEST.value()); response.put("errors", errors); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } }
內(nèi)部方法校驗
import org.springframework.validation.annotation.Validated; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; //@Validated @Validated public interface ServiceIntface { //校驗返回值,校驗入?yún)? @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); } // 這里可以根據(jù)實際需求定制返回的錯誤信息結(jié)構(gòu) 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
- 校驗寫在具體實現(xiàn),拋出異常
javax.validation.ConstraintDeclarationException
注意點
代碼中國際化使用
代碼里響應(yīng),手動獲取使用MessageSource的getMessage方法即可,也就是spring容器中的getMessage()
# messages_en_US.properties welcome.message=Welcome to our website! # messages_zh_CN.properties welcome.message=歡迎來到我們的網(wǎng)站! #定義消息,并使用占位符{0}、{1}等表示參數(shù)位置 #welcome.message=歡迎{0}來到{1}
//創(chuàng)建一個配置類來配置LocaleResolver,以便根據(jù)請求解析當(dāng)前的語言環(huán)境: 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); // 設(shè)置默認語言 return sessionLocaleResolver; } }
//創(chuàng)建一個控制器來使用國際化的消息 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信息。
此時前端可以在不同語言環(huán)境時傳入不同的請求頭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信息,使用_劃分語言個地區(qū)。 * 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) { } } }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java消息摘要算法MAC實現(xiàn)與應(yīng)用完整示例
這篇文章主要介紹了Java消息摘要算法MAC實現(xiàn)與應(yīng)用,結(jié)合完整實例形式分析了java消息摘要算法MAC的概念、原理、實現(xiàn)方法及相關(guān)操作注意事項,需要的朋友可以參考下2019-09-09MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用詳解
這篇文章主要介紹了MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用詳解,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03使用Apache?POI和SpringBoot實現(xiàn)Excel文件上傳和解析功能
在現(xiàn)代企業(yè)應(yīng)用開發(fā)中,數(shù)據(jù)的導(dǎo)入和導(dǎo)出是一項常見且重要的功能需求,Excel?作為一種廣泛使用的電子表格工具,常常被用來存儲和展示數(shù)據(jù),下面我們來看看如何使用Apache?POI和SpringBoot實現(xiàn)Excel文件上傳和解析功能吧2025-01-01Spring中@RequestParam、@RequestBody和@PathVariable的用法詳解
這篇文章主要介紹了Spring中@RequestParam、@RequestBody和@PathVariable的用法詳解,后端使用集合來接受參數(shù),靈活性較好,如果url中沒有對參數(shù)賦key值,后端在接收時,會根據(jù)參數(shù)值的類型附,賦一個初始key,需要的朋友可以參考下2024-01-01使用Spring自定義實現(xiàn)IOC和依賴注入(注解方式)
這篇文章主要介紹了使用Spring自定義實現(xiàn)IOC和依賴注入(注解方式),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08spring?kafka?@KafkaListener詳解與使用過程
這篇文章主要介紹了spring-kafka?@KafkaListener詳解與使用,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02SpringCloud災(zāi)難性雪崩效應(yīng)處理方法之降級實現(xiàn)流程詳解
這篇文章主要介紹了SpringCloud災(zāi)難性雪崩效應(yīng)處理方法之降級,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧<BR>2022-11-11