【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

// 拦截器生命活动,类似AOP

if (!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

@Override

public 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 标签消息解析,国际化处理

@Override

public 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

回到顶部