Spring國際化和Validation詳解
SpringBoot國際化和Validation融合
場景
在應用交互時,可能需要根據(jù)客戶端得語言來返回不同的語言數(shù)據(jù)。
前端通過參數(shù)、請求頭等往后端傳入locale相關得參數(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以解析當前請求的locale,常用的實現(xiàn)是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目錄下創(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同時服務于你的應用消息和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
在你的配置類中,創(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);
// 設置使用 HibernateValidator 校驗器
factoryBean.setProviderClass(HibernateValidator.class);
// 設置 快速異常返回 只要有一個校驗錯誤就立即返回失敗,其他參數(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標記,并定義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文件中有一個對應的條目例如:
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
注意點
代碼中國際化使用
代碼里響應,手動獲取使用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ù)請求解析當前的語言環(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); // 設置默認語言
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)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
servlet之session工作原理簡介_動力節(jié)點Java學院整理
這篇文章主要介紹了servlet之session工作原理簡介,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
SpringBoot數(shù)據(jù)庫初始化datasource配置方式
這篇文章主要為大家介紹了SpringBoot數(shù)據(jù)庫初始化datasource配置方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
SpringBoot Web工程同時啟動多個HTTP端口的方法
本文介紹了在SpringbootWeb工程中如何配置多個HTTP端口,分別以Tomcat、Jetty和Undertow為例,通過修改配置文件和編寫配置類實現(xiàn)多端口啟動,并提供了詳細的代碼供讀者參考,需要的朋友可以參考下2026-04-04
java通過url讀取遠程數(shù)據(jù)并保持到本地的實例代碼
本文通過實例代碼給大家介紹了java通過url讀取遠程數(shù)據(jù)并保持到本地的方法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-07-07

