【Spring】LocaleResolver、MessageSources实现国际化显示源码分析
localeResolver: 区域信息解析器
作用:集合MessageSource方便springmvc页面国际化展示
1. 国际化基础案例
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); //baseName: MessageSource基础名称
//规则:<classpath路径>/msg-<locale>.properties前面表示国际化资源存放路径,后面表示资源文件前缀名
//案例:i18n/my/msg:表示资源文件存在classpath目录下 i18n/my目录,资源文件以msg前缀
resourceBundleMessageSource.setBasename("i18n/my/msg");
// resourceBundleMessageSource.setBasename("i18n/msg");
//true:表示匹配到code正常返回,否则直接返回code自身,默认false code,匹配不到则直接抛出异常NoSuchMessageException, 表示code无法解析
resourceBundleMessageSource.setUseCodeAsDefaultMessage(true);
Locale locale = Locale.getDefault();
String pageLanguage = resourceBundleMessageSource.getMessage("page.language", null, locale);
String pageDescription = resourceBundleMessageSource.getMessage("page.description", null, locale);
log.info("language:{}, page.language:{},page.description:{}", locale.getDisplayName(), pageLanguage, pageDescription);
//设置区域信息(模拟美国)
locale = Locale.US;
pageLanguage = resourceBundleMessageSource.getMessage("page.language", null, locale);
pageDescription = resourceBundleMessageSource.getMessage("page.description", null, locale);
log.info("language:{}, page.language:{},page.description:{}", locale.getDisplayName(), pageLanguage, pageDescription);
资源文件内容:
msg_zh_CN.properties:page.language=Simple Chinese
page.description=页面语言
msg_en_US.properties:
page.language=English
page.description=Page Language
日志打印:不同区域实现不同语言展示(国际化展示)
INFO [main] - language:中文 (中国), page.language:Simple Chinese,page.description:页面语言 INFO [main] - language:英文 (美国), page.language:English,page.description:Page Language
2. springboot基础案例
国际化资源配置
/** * @author zhiwei_yang
* @time 2020-6-19-9:06
*/
@Configuration
public class InternationalizationConfig {
/**
* 指定资源绑定消息源:
* 注意:ApplicationContext初始化时指定默认MessageSource Bean名称必须为messageSource,否则无法国际化处理
* 源码:org.springframework.context.support.AbstractApplicationContext#initMessageSource()
* @return
*/
@Bean("messageSource")
public ResourceBundleMessageSource resourceBundleMessageSource(){
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setBasename("i18n/msg");
resourceBundleMessageSource.setUseCodeAsDefaultMessage(true);
return resourceBundleMessageSource;
}
/**
* 区域信息解析器: 替换默认AcceptHeaderLocaleResolver,方便测试
* AcceptHeaderLocaleResolver: 将Locale信息放在请求头 Accept-Language, AcceptHeaderLocaleResolvern不支持自定义设置Locale这里采用SessionLocaleResolver测试
* SessionLocaleResolver: 将locale信息暂存在HttpSession
* FixedLocaleResolver: 固定locale信息:Locale.getDefault(),也就是指定区域访问指定显示指定区域的国际化数据,不饿能跨区域显示(不推荐)
* CookieLocaleResolver: locale信息保存在Cookie
* @return
*/
@Bean
public LocaleResolver localeResolver(){
return new SessionLocaleResolver();
}
/**
* 注入拦截器
* @param registry 拦截器注册表
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
JSP页面跳转控制器:
/** * @author ZHIWEI.YANG
* @createTime 2018-11-17 21:02
* @description
*/
@Controller
@RequestMapping("/page")
@Slf4j
public class PageController {
@Autowired
private LocaleResolver localeResolver;
/**
* 国际化展示测试
* @return
*/
@GetMapping("/i18n")
public String i18n(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
// 手动设置Locale,本质和LocaleChangeInterceptor 工作内容相同
// localeResolver.setLocale(httpServletRequest, httpServletResponse, locale);
log.info("request locale ==> {}", locale.getDisplayName());
return "i18n";
}
}
jsp页面:
<%@ page language="java" contentType="text/html;charset=utf-8" pageEncoding="utf-8" %><%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title><spring:message code="page.title"/></title>
</head>
<body>
<spring:message code="page.description"/> --> <spring:message code="page.language"/><br>
<hr/>
<a href="${pageContext.request.contextPath}/page/i18n?locale=zh_CN">Chinese</a><br/>
<a href="${pageContext.request.contextPath}/page/i18n?locale=en_US">English</a>
</body>
</html>
tips: idea启动springboot jsp项目需要设置working directory 为 $ModuleFileDir$, 否则工作目录为主模块导致web资源无法加载
3. localeResolver工作原理,以SessionLocaleResolver为案例
3.1 messageSource 初始化构造
源码(ApplicationContext初始化):org.springframework.context.support.AbstractApplicationContext.initMessageSource
protected void initMessageSource() { ConfigurableListableBeanFactory beanFactory = getBeanFactory();
//核心代码:messageSource名称规定为 messageSource, 实现自定义国际化资源配置
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// Make MessageSource aware of parent MessageSource.
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// Only set parent context as parent MessageSource if no parent MessageSource
// registered already.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// Use empty MessageSource to be able to accept getMessage calls.
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No "" + MESSAGE_SOURCE_BEAN_NAME + "" bean, using [" + this.messageSource + "]");
}
}
}
3.2 DispatcherServlet Request LocaleResolver配置
springmvc绑定localeResolver到请求属性:org.springframework.web.servlet.DispatcherServlet.doService
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
3.3 拦截器解析Locale处理: 本质 LocaleChangeInterceptor
源码:org.springframework.web.servlet.DispatcherServlet.doDispatch
// 拦截器生命活动,类似AOPif (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
org.springframework.web.servlet.i18n.LocaleChangeInterceptor.preHandle
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException {
// 从请求参数获取新设置的Locale,默认参数名locale
String newLocale = request.getParameter(getParamName());
if (newLocale != null) {
if (checkHttpMethod(request.getMethod())) {
// 从请求属性中获取LocaleResolver对象
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException(
"No LocaleResolver found: not in a DispatcherServlet request?");
}
try {
// 当前Http请求流程设置处理的Locale
localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
}
catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) {
logger.debug("Ignoring invalid locale value [" + newLocale + "]: " + ex.getMessage());
}
else {
throw ex;
}
}
}
}
// Proceed in any case.
return true;
}
SessionLocaleResolver 设置Locale:
org.springframework.web.servlet.i18n.SessionLocaleResolver.setLocaleContext
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
// Session保存当前请求流程的Locale和时区信息,方便后续JSP解析处理
WebUtils.setSessionAttribute(request, this.localeAttributeName, locale);
WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone);
4. JSP国际化处理
标签:spring:message
实例:org.springframework.web.servlet.tags.MessageTag
JSP标签用法:JSP自定义标签
4.1 requestContext初始化,构建请求的工作环境
MessageTag父类方法: org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag
@Overridepublic final int doStartTag() throws JspException {
try {
this.requestContext = (RequestContext) this.pageContext.getAttribute(REQUEST_CONTEXT_PAGE_ATTRIBUTE);
if (this.requestContext == null) {
// 构建请求上下文RequestContext, 相当于单个请求流程工作环境
this.requestContext = new JspAwareRequestContext(this.pageContext);
this.pageContext.setAttribute(REQUEST_CONTEXT_PAGE_ATTRIBUTE, this.requestContext);
}
return doStartTagInternal();
}
catch (JspException | RuntimeException ex) {
logger.error(ex.getMessage(), ex);
throw ex;
}
catch (Exception ex) {
logger.error(ex.getMessage(), ex);
throw new JspTagException(ex.getMessage());
}
}
JspAwareRequestContext父类初始化: org.springframework.web.servlet.support.RequestContext.RequestContext
public RequestContext(HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable ServletContext servletContext, @Nullable Map<String, Object> model) {
this.request = request;
this.response = response;
this.model = model;
// Fetch WebApplicationContext, either from DispatcherServlet or the root context.
// ServletContext needs to be specified to be able to fall back to the root context!
WebApplicationContext wac = (WebApplicationContext) request.getAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (wac == null) {
// 请求属性获取ApplicationContext(IOC容器),容器对象在DispatcherServlet请求处理预先配置
wac = RequestContextUtils.findWebApplicationContext(request, servletContext);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: not in a DispatcherServlet " +
"request and no ContextLoaderListener registered?");
}
}
this.webApplicationContext = wac;
Locale locale = null;
TimeZone timeZone = null;
// Determine locale to use for this RequestContext.
// 请求属性获取LocaleResolver对象,区域信息解析器在DispatcherServlet请求处理预先配置
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver instanceof LocaleContextResolver) {
LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
else if (localeResolver != null) {
// Try LocaleResolver (we"re within a DispatcherServlet request).
locale = localeResolver.resolveLocale(request);
}
// 请求上下文RequestContext设置区域信息和时区信息
this.locale = locale;
this.timeZone = timeZone;
// Determine default HTML escape setting from the "defaultHtmlEscape"
// context-param in web.xml, if any.
this.defaultHtmlEscape = WebUtils.getDefaultHtmlEscape(this.webApplicationContext.getServletContext());
// Determine response-encoded HTML escape setting from the "responseEncodedHtmlEscape"
// context-param in web.xml, if any.
this.responseEncodedHtmlEscape =
WebUtils.getResponseEncodedHtmlEscape(this.webApplicationContext.getServletContext());
this.urlPathHelper = new UrlPathHelper();
if (this.webApplicationContext.containsBean(RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
this.requestDataValueProcessor = this.webApplicationContext.getBean(
RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class);
}
}
4.2 标签消息解析,国际化处理
@Overridepublic int doEndTag() throws JspException {
try {
// Resolve the unescaped message.
String msg = resolveMessage();
// HTML and/or JavaScript escape, if demanded.
msg = htmlEscape(msg);
msg = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(msg) : msg;
// Expose as variable, if demanded, else write to the page.
if (this.var != null) {
this.pageContext.setAttribute(this.var, msg, TagUtils.getScope(this.scope));
}
else {
writeMessage(msg);
}
return EVAL_PAGE;
}
catch (IOException ex) {
throw new JspTagException(ex.getMessage(), ex);
}
catch (NoSuchMessageException ex) {
throw new JspTagException(getNoSuchMessageExceptionDescription(ex));
}
}
// 消息解析:本质调用messageSource进行国际化消息处理显示
protected String resolveMessage() throws JspException, NoSuchMessageException {
MessageSource messageSource = getMessageSource();
// Evaluate the specified MessageSourceResolvable, if any.
if (this.message != null) {
// We have a given MessageSourceResolvable.
return messageSource.getMessage(this.message, getRequestContext().getLocale());
}
if (this.code != null || this.text != null) {
// We have a code or default text that we need to resolve.
Object[] argumentsArray = resolveArguments(this.arguments);
if (!this.nestedArguments.isEmpty()) {
argumentsArray = appendArguments(argumentsArray, this.nestedArguments.toArray());
}
if (this.text != null) {
// We have a fallback text to consider.
String msg = messageSource.getMessage(
this.code, argumentsArray, this.text, getRequestContext().getLocale());
return (msg != null ? msg : "");
}
else {
// We have no fallback text to consider.
// 核心代码:调用Spring messageSource解析spring:message 绑定的code进行数据展示,本质和ResourceBundleMessageSource国际化处理一样
return messageSource.getMessage(this.code, argumentsArray, getRequestContext().getLocale());
}
}
throw new JspTagException("No resolvable message");
}
以上是 【Spring】LocaleResolver、MessageSources实现国际化显示源码分析 的全部内容, 来源链接: utcz.com/z/517616.html