SpringMVC-国际化
一、国际化例子
例子来自于https://blog.csdn.net/qq_41541619/article/details/80459932
在resources目录下新增以下两个文件:
language_en_US.properties
language.cn = \u4e2d\u6587
language.en = English
internationalisation = \u0020Internationalisation
welcome = This is the English environment
introduce= This is I18N Demo
language_en_US.properties
language.cn = \u4e2d\u6587
language.en = English
internationalisation = \u56fd\u9645\u5316
welcome = \u8fd9\u662f\u4e2d\u6587\u73af\u5883
introduce= \u8fd9\u662f\u56fd\u9645\u5316\u7684\u4e8b\u4f8b
SpringMVC配置文件新增:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 表示多语言配置文件在根路径下,以language开头的文件 -->
<property name="basename" value="classpath:language" />
<property name="useCodeAsDefaultMessage" value="true" />
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />
<mvc:interceptors>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang" />
</bean>
</mvc:interceptors>
页面hello.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title>SpringMVC<spring:message code="internationalisation"/></title>
</head>
<body>
Language:
<a href="?lang=zh_CN"><spring:message code="language.cn"/></a>
<a href="?lang=en_US"><spring:message code="language.en"/></a>
<h1>
<spring:message code="welcome"/>
</h1>
当前语言: ${pageContext.response.locale }
</body>
</html>
二、SpringMVC解析流程
DispatcherServlet在Spring IOC容器初始化完成后,会调用initStrategies获取SpringMVC的一堆组件:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
其中initLocaleResolver会从bean工厂中获取名为localeResolver,类型为LocaleResolver的bean。如果获取不到则从类路径下的DispatcherServlet.properties文件中获取。
Spring-webmvc类路径下resources/org/springframework/web/servlet/DispatcherServlet.properties
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
如果没有配置LocaleResolver则将AcceptHeaderLocaleResolver设为默认的LocaleResolver。
当从客户端发起一个请求时,DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)方法中:
mappedHandler = getHandler(processedRequest);
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
getHandler获取拦截器链,applyPreHandle执行拦截器的applyPreHandle方法。
Locale拦截器LocaleChangeInterceptor.preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException {
String newLocale = request.getParameter(getParamName());
if (newLocale != null) {
if (checkHttpMethod(request.getMethod())) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException(
"No LocaleResolver found: not in a DispatcherServlet request?");
}
try {
localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
}
catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring invalid locale value [" + newLocale + "]: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
// Proceed in any case.
return true;
}
1、通过paramName获取Locale,默认的paramName是locale。
2、若Locale不为空,检查请求方法,如果支持请求方法。则从HttpServletRequest获取LocaleResolver。调用localeResolver.setLocale设置locale。localeResolver是CookieLocaleResolver。
CookieLocaleResolver.setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale)
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
CookieLocaleResolver.setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,@Nullable LocaleContext localeContext)
public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext) {
Assert.notNull(response, "HttpServletResponse is required for CookieLocaleResolver");
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
addCookie(response,
(locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? '/' + timeZone.getID() : ""));
}
else {
removeCookie(response);
}
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
(locale != null ? locale : determineDefaultLocale(request)));
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
将locale设置到cookie中或者从cookie中移除locale。
CookieGenerator.addCookie(HttpServletResponse response, String cookieValue)
public void addCookie(HttpServletResponse response, String cookieValue) {
Assert.notNull(response, "HttpServletResponse must not be null");
Cookie cookie = createCookie(cookieValue);
Integer maxAge = getCookieMaxAge();
if (maxAge != null) {
cookie.setMaxAge(maxAge);
}
if (isCookieSecure()) {
cookie.setSecure(true);
}
if (isCookieHttpOnly()) {
cookie.setHttpOnly(true);
}
response.addCookie(cookie);
if (logger.isTraceEnabled()) {
logger.trace("Added cookie [" + getCookieName() + "=" + cookieValue + "]");
}
}
将locale设置到cookie中。
现在看下默认的LocaleResolver。默认的LocaleResolver是AcceptHeaderLocaleResolver。
AcceptHeaderLocaleResolver.setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale)
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
throw new UnsupportedOperationException(
"Cannot change HTTP accept header - use a different locale resolution strategy");
}
不支持设置locale。
还有比较常用的SessionLocaleResolver。
AbstractLocaleContextResolver.setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale)
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
SessionLocaleResolver. setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,@Nullable LocaleContext localeContext)
public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext) {
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
WebUtils.setSessionAttribute(request, this.localeAttributeName, locale);
WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone);
}
WebUtils.setSessionAttribute(request, this.localeAttributeName, locale);将locale设置到HttpSession中。
在解析spring:message标签时,获取locale后从messageSource中通过code属性获取对应的值后返回。spring:message标签由spring-webmvc下resources/META-INF/spring.tld文件中:
<name>message</name>
<tag-class>org.springframework.web.servlet.tags.MessageTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>A MessageSourceResolvable argument (direct or through JSP EL).
Fits nicely when used in conjunction with Spring's own validation error classes
which all implement the MessageSourceResolvable interface. For example, this
allows you to iterate over all of the errors in a form, passing each error
(using a runtime expression) as the value of this 'message' attribute, thus
effecting the easy display of such error messages.</description>
<name>message</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
MessageTag类解析spring:message标签。
MessageTag.doEndTag()
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));
}
}
resolveMessage解析出message后写入到response中。
MessageTag.resolveMessage()
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.
return messageSource.getMessage(
this.code, argumentsArray, getRequestContext().getLocale());
}
}
throw new JspTagException("No resolvable message");
}
获取messageSource后调用messageSource.getMessage。此处获取的messageSource实际是webApplicationContext。
AbstractApplicationContext.getMessage(String code, @Nullable Object[] args, Locale locale)
public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
return getMessageSource().getMessage(code, args, locale);
}
从AbstractApplicationContext获取messageSource后调用getMessage。
AbstractMessageSource.getMessage(String code, @Nullable Object[] args, Locale locale)
public final String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
String msg = getMessageInternal(code, args, locale);
if (msg != null) {
return msg;
}
String fallback = getDefaultMessage(code);
if (fallback != null) {
return fallback;
}
throw new NoSuchMessageException(code, locale);
}
调用getMessageInternal获取msg。
AbstractMessageSource.getMessageInternal(@Nullable String code, @Nullable Object[] args, @Nullable Locale locale)
protected String getMessageInternal(@Nullable String code, @Nullable Object[] args, @Nullable Locale locale) {
if (code == null) {
return null;
}
if (locale == null) {
locale = Locale.getDefault();
}
Object[] argsToUse = args;
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
// Optimized resolution: no arguments to apply,
// therefore no MessageFormat needs to be involved.
// Note that the default implementation still uses MessageFormat;
// this can be overridden in specific subclasses.
String message = resolveCodeWithoutArguments(code, locale);
if (message != null) {
return message;
}
}
else {
// Resolve arguments eagerly, for the case where the message
// is defined in a parent MessageSource but resolvable arguments
// are defined in the child MessageSource.
argsToUse = resolveArguments(args, locale);
MessageFormat messageFormat = resolveCode(code, locale);
if (messageFormat != null) {
synchronized (messageFormat) {
return messageFormat.format(argsToUse);
}
}
}
// Check locale-independent common messages for the given message code.
Properties commonMessages = getCommonMessages();
if (commonMessages != null) {
String commonMessage = commonMessages.getProperty(code);
if (commonMessage != null) {
return formatMessage(commonMessage, args, locale);
}
}
// Not found -> check parent, if any.
return getMessageFromParent(code, argsToUse, locale);
}
1、如果不需要使用MessageFormat且参数为null则resolveCodeWithoutArguments获取message。
2、否则resolveArguments解析参数,调用resolveCode获取message后调用MessageFormat格式化message。
ReloadableResourceBundleMessageSource.resolveCodeWithoutArguments(String code, Locale locale)
protected String resolveCodeWithoutArguments(String code, Locale locale) {
if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
String result = propHolder.getProperty(code);
if (result != null) {
return result;
}
}
else {
for (String basename : getBasenameSet()) {
List<String> filenames = calculateAllFilenames(basename, locale);
for (String filename : filenames) {
PropertiesHolder propHolder = getProperties(filename);
String result = propHolder.getProperty(code);
if (result != null) {
return result;
}
}
}
}
return null;
}
如果有缓存的PropertiesHolder则从cachedMergedProperties获取PropertiesHolder后调用getProperty获取结果。否则遍历basename集合,通过basename和locale从类路径下加载语言属性文件到cachedProperties中缓存下来,同时将结果返回。
总结
国际化主要的组件是localeResolver和messageSource。通过LocaleChangeInterceptor-locale拦截器拦截请求后通过localeResolver设置locale。在解析spring:message时通过locale和messageSource从类路径下的语言属性文件中获取值。messageSource通过locale加载对应的语言属性文件并缓存下来。通过属性名获取属性值。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」