让国际友人也能快乐的访问的你的服务
spring-boot-starter-web
里面有国际化需要的API,因此我们不需要引入其他的API了。
定义国际化的配置文件
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import java.util.Locale;
/**
* @author fangxi
*/
@Configuration
public class I18nConfiguration {
/**
* Accept-Language Header 区域解析器
*/
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
acceptHeaderLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return acceptHeaderLocaleResolver;
}
/**
* 加载国际化配置文件资源
*/
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
//设置国际化配置文件存放目录
messageSource.setBasename("classpath:i18n/messages");
//设置加载资源的缓存失效时间,-1表示永久有效,默认为-1
messageSource.setCacheSeconds(-1);
//设定 Resource Bundle 编码方式,默认为UTF-8
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
AcceptHeaderLocaleResolver
:Spring内置的国际化处理,获取Http请求头中的Accept-Language
属性,判断当前环境是哪种语言。
在resources
目录下,创建i18n
目录和两个国际化的文件,要以message开头
messages_en_US.properties,英文提示
delete.success=delete success.
save.success=Save Success.
update.success=Update Success.
messages_zh_CN.properties,中文提示
delete.success=删除成功
save.success=保存成功
update.success=更新成功
定义一个工具类,根据当前语言环境和国际化提示中的key获取对应的提示
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Component;
import java.util.Locale;
/**
* @author fangxi
*/
@Component
public class I18nUtils {
private static ReloadableResourceBundleMessageSource messageSource;
@Autowired
I18nUtils(ReloadableResourceBundleMessageSource messageSource) {
I18nUtils.messageSource = messageSource;
}
public static String toLocale(String message) {
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(message, null, locale);
}
}
为了统一管理key,我们用枚举保存。
import lombok.AllArgsConstructor;
import java.util.stream.Stream;
/**
* @author fangxi
*/
@AllArgsConstructor
public enum I18nEnum {
SAVE_SUCCESS("save.success"),
UPDATE_SUCCESS("update.success"),
DELETE_SUCCESS("delete.success"),
;
private final String message;
public String message() {
return this.message;
}
public static boolean exists(String s) {
return Stream.of(I18nEnum.values())
.map(I18nEnum::message)
.anyMatch(s::equals);
}
}
注:用枚举是因为我们在全局异常处理的时候,要获遍历枚举里面的常量,从而判断是否要国际化。
使用
@PostMapping
public ResponseEntity<String> save(@RequestBody @Validated UserRequestVO requestVO) {
userService.save(requestVO);
return ResponseEntity.status(HttpStatus.CREATED)
.body(I18nUtils.toLocale(I18nEnum.SAVE_SUCCESS.message()));
}
这样一来,会就完成了提示消息国际化功能。
上面的做法不能让入参校验进行国际化,比如 @NotNull(message = "请传入xxxx")
。因此,我们还需要改造,定义两组提示信息
messages_zh_CN.properties
paging.tip=请传入分页参数
messages_en_US.properties
paging.tip=Please pass in paging parameters
改造注解上的提示信息:@NotNull(message = "paging.tip")
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author fangxi
*/
@Data
public class PageRequestVO {
@NotNull(message = "paging.tip")
private Integer page;
@NotNull(message = "paging.tip")
private Integer size;
}
现在一般提交数据的方式用两种:表单提交和JSON格式。
如果是表单提交,校验不通过的话,会抛出BindException
。JSON格式的提交,校验不通过则会抛出MethodArgumentNotValidException
。所以,我们要拦截这两种异常处理
import com.isecstar.print.common.enums.I18nEnum;
import com.isecstar.print.common.utils.I18nUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author fangxi
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException methodArgumentNotValidException, HttpServletRequest request) {
return getError(methodArgumentNotValidException, request);
}
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> bindException(BindException bindException, HttpServletRequest request) {
return getError(bindException, request);
}
private Map<String, Object> getError(Exception e, HttpServletRequest request) {
log.error("", e);
Map<String, Object> error = new HashMap<>(5);
String message = e.getMessage();
error.put("message", I18nEnum.exists(message) ? I18nUtils.toLocale(message) : message);
error.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
// messageList就是写在@NotNull(message = "")message的信息, 这里获取到的是 paging.tip
Set<String> messageList = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.toSet());
List<String> globalizationMessage = messageList.stream().map(I18nUtils::toLocale).collect(Collectors.toList());
error.put("message", globalizationMessage);
error.put("httpStatus", HttpStatus.BAD_REQUEST);
error.put("code", HttpStatus.BAD_REQUEST.value());
} else if (e instanceof BindException) {
BindException bindException = (BindException) e;
// messageList就是写在@NotNull(message = "")message的信息, 这里获取到的是 paging.tip
Set<String> messageList = bindException.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toSet());
List<String> globalizationMessage = messageList.stream().map(I18nUtils::toLocale).collect(Collectors.toList());
error.put("message", globalizationMessage);
error.put("httpStatus", HttpStatus.BAD_REQUEST);
error.put("code", HttpStatus.BAD_REQUEST.value());
}
error.put("path", request.getRequestURI());
error.put("timestamp", LocalDateTime.now());
return error;
}
}
这样一来,我们就可以在校验中,对外国际化之后的异常信息。