栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

locale 国际化配置(springboot)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

locale 国际化配置(springboot)

locale 国际化配置(springboot)
    • 1. 创建文件
      • 1.1 创建 bundle
      • 1.2 添加文件名及语言
      • 1.3 文本编写
    • 2. springboot自带配置类解析
    • 3. 获取值
      • **LocaleContextHolder.getLocale()**
        • debug追踪
          • **解析器(LocaleResolver)的配置**
          • 拦截器的配置
      • RequestContextUtils.getLocale(request)
      • 总结
    • 4. 应用
      • 4.1 配置文件
      • 4.2 WxLocaleConfigs
    • 附录:
      • **HeaderLocaleContextResolver**
      • **HeaderLocaleChangeInterceptor**

1. 创建文件 1.1 创建 bundle

这里是在 resources目录下新建的i18n目录中创建。

1.2 添加文件名及语言

添加文件名:messages(建议,也可以任意),并添加中文(zh_CN),英文(en_US)两种语言。


1.3 文本编写

添加完成,打开任意一个文件,切换编写模式,进行对应的文本编写。这里我们需要的文件已经创建完毕。

2. springboot自带配置类解析

springboot 有很多自动装配类:XXXAutoConfiguration,国际化配置->MessageSourceAutoConfiguration

@Configuration(proxyBeanMethods = false)

@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)

@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

	private static final Resource[] NO_RESOURCES = {};

    
	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

    
	@Bean
	public MessageSource messageSource(MessageSourceProperties properties) {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		if (StringUtils.hasText(properties.getbasename())) {
			messageSource.setbasenames(StringUtils
					.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getbasename())));
		}
		if (properties.getEncoding() != null) {
			messageSource.setDefaultEncoding(properties.getEncoding().name());
		}
		messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
		Duration cacheDuration = properties.getCacheDuration();
		if (cacheDuration != null) {
			messageSource.setCacheMillis(cacheDuration.toMillis());
		}
		messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
		messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
		return messageSource;
	}

    
	protected static class ResourceBundleCondition extends SpringBootCondition {

		private static ConcurrentReferenceHashMap cache = new ConcurrentReferenceHashMap<>();

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypemetadata metadata) {
			String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
			ConditionOutcome outcome = cache.get(basename);
			if (outcome == null) {
				outcome = getMatchOutcomeForbasename(context, basename);
				cache.put(basename, outcome);
			}
			return outcome;
		}

		private ConditionOutcome getMatchOutcomeForbasename(ConditionContext context, String basename) {
			ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
			for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
				for (Resource resource : getResources(context.getClassLoader(), name)) {
					if (resource.exists()) {
						return ConditionOutcome.match(message.found("bundle").items(resource));
					}
				}
			}
			return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
		}

		private Resource[] getResources(ClassLoader classLoader, String name) {
			String target = name.replace('.', '/');
			try {
				return new PathMatchingResourcePatternResolver(classLoader)
						.getResources("classpath*:" + target + ".properties");
			}
			catch (Exception ex) {
				return NO_RESOURCES;
			}
		}

	}

}
3. 获取值

配置类加载好了,那么接下来如何获取值呢?

spring 提供了两种总方式:

LocaleContextHolder.getLocale()

查看源码:重点看 如何获取 LocaleContext


private static final ThreadLocal localeContextHolder =
			new NamedThreadLocal<>("LocaleContext");

private static final ThreadLocal inheritableLocaleContextHolder =
			new NamedInheritableThreadLocal<>("LocaleContext");


public static Locale getLocale() {
	return getLocale(getLocaleContext());
}

public static Locale getLocale(@Nullable LocaleContext localeContext) {
    if (localeContext != null) {
        Locale locale = localeContext.getLocale();
        if (locale != null) {
            return locale;
        }
    }
 	return (defaultLocale != null ? defaultLocale : Locale.getDefault());
}


@Nullable
public static LocaleContext getLocaleContext() {
    LocaleContext localeContext = localeContextHolder.get();
    if (localeContext == null) {
        localeContext = inheritableLocaleContextHolder.get();
    }
    return localeContext;
}


public static void setLocaleContext(@Nullable LocaleContext localeContext, boolean inheritable) {
    if (localeContext == null) {
        resetLocaleContext();
    }
    else {
        if (inheritable) {
            inheritableLocaleContextHolder.set(localeContext);
            localeContextHolder.remove();
        }
        else {
            localeContextHolder.set(localeContext);
            inheritableLocaleContextHolder.remove();
        }
    }
}
debug追踪
  1. 进入过滤器RequestContextFilter.initContextHolders()方法,获取request头中的accept-language来初始化 locale(默认是zh_CN),此时为SimpleLocaleContext


实际就是将国际化上下文localeContext放入 ThreadLocal,再使用的时候取出来。

  1. 进入DispatcherServlet.buildLocaleContext()方法,此时注意到LocaleContextResolver 进入我们的视野

解析器(LocaleResolver)的配置

查看源码得知:localeResolver 来自项目初始化,会获取LocaleResolver 实例注入。此时我们就需要创建一个属于我们自己的解析器。HeaderLocaleContextResolver

public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";

private void initLocaleResolver(ApplicationContext context) {
    try {
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.localeResolver);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
                         "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
        }
    }
}

HeaderLocaleContextResolver.resolveLocaleContext() 设置我们自定义的LocaleContext放入ThreadLocal中。

package com.wx.wxcommonlocale.support.i18n;


@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
    return new TimeZoneAwareLocaleContext() {
        @Override
        @Nullable
        public Locale getLocale() {
            Object attribute = request.getAttribute(localeAttributeName);
            return attribute != null ? (Locale) attribute : getDefaultLocale();
        }

        @Override
        @Nullable
        public TimeZone getTimeZone() {
            Object attribute = request.getAttribute(timeZoneAttributeName);
            return attribute != null ? (TimeZone) attribute : getDefaultTimeZone();
        }
    };
}

接下来继续追踪,进入frameworkServlet.processRequest()方法的initContextHolders()将上一步自定义的国际化上下文放入ThreadLocal中。【DispatcherServlet 继承了 frameworkServlet,在processRequest方法中,调用上一步buildLocaleContext()方法】

private void initContextHolders(HttpServletRequest request,
			@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

		if (localeContext != null) {
			LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
		}
		if (requestAttributes != null) {
			RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		}
	}

此时在看LocaleContextHolder.getLocale()方法如何获取值:实际上是从 自定义的方法中 获取。

package com.wx.wxcommonlocale.support.i18n;

@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
    return new TimeZoneAwareLocaleContext() {
        @Override
        @Nullable
        public Locale getLocale() {
            
            Object attribute = request.getAttribute(localeAttributeName);
            return attribute != null ? (Locale) attribute : getDefaultLocale();
        }

        @Override
        @Nullable
        public TimeZone getTimeZone() {
            Object attribute = request.getAttribute(timeZoneAttributeName);
            return attribute != null ? (TimeZone) attribute : getDefaultTimeZone();
        }
    };
}
拦截器的配置

当过滤器执行完成,就要执行拦截器的代码,这里可以看到,是获取自定义的头getParamName()【locale】,来判断是那种语言。

package com.wx.wxcommonlocale.support.i18n;

@Slf4j
public class HeaderLocaleChangeInterceptor extends LocaleChangeInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws ServletException {
        Locale locale = null;
        TimeZone timeZone = null;
        //从请求头中获取信息格式:zh_CN Asia/Shanghai      zh_CN GMT+8
        String newLocale = request.getHeader(getParamName());
        ...
            /
    @Override
    public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
        Locale locale = null;
        TimeZone timeZone = null;
        if (localeContext != null) {
            locale = localeContext.getLocale();
            if (localeContext instanceof TimeZoneAwareLocaleContext) {
                timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
            }
            //设置响应头,微服务之间传递
            response.setHeader(getLocaleHeadName(),
                    (locale != null ? locale.toString() : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
        }
        /
    public static final String LOCALE_REQUEST_ATTRIBUTE_NAME = HeaderLocaleContextResolver.class.getName() + ".LOCALE";

    public static final String TIME_ZONE_REQUEST_ATTRIBUTE_NAME = HeaderLocaleContextResolver.class.getName() + ".TIME_ZONE";

    private String localeAttributeName = LOCALE_REQUEST_ATTRIBUTE_NAME;

    private String timeZoneAttributeName = TIME_ZONE_REQUEST_ATTRIBUTE_NAME;


    private String localeHeadName;

    public String getLocaleHeadName() {
        return localeHeadName;
    }

    public void setLocaleHeadName(String localeHeadName) {
        this.localeHeadName = localeHeadName;
    }

    
    @Override
    public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
        return new TimeZoneAwareLocaleContext() {
            @Override
            @Nullable
            public Locale getLocale() {
                Object attribute = request.getAttribute(localeAttributeName);
                return attribute != null ? (Locale) attribute : getDefaultLocale();
            }

            @Override
            @Nullable
            public TimeZone getTimeZone() {
                Object attribute = request.getAttribute(timeZoneAttributeName);
                return attribute != null ? (TimeZone) attribute : getDefaultTimeZone();
            }
        };
    }


    
    @Override
    public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
        Locale locale = null;
        TimeZone timeZone = null;
        if (localeContext != null) {
            locale = localeContext.getLocale();
            if (localeContext instanceof TimeZoneAwareLocaleContext) {
                timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
            }
            //设置响应头,微服务之间传递
            response.setHeader(getLocaleHeadName(),
                    (locale != null ? locale.toString() : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
        }
        request.setAttribute(localeAttributeName,
                (locale != null ? locale : getDefaultLocale()));
        request.setAttribute(timeZoneAttributeName,
                (timeZone != null ? timeZone : getDefaultTimeZone()));

    }


    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        TimeZone timeZone = (TimeZone) request.getAttribute(HeaderLocaleContextResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
        setLocaleContext(request, response, (locale != null ? timeZone != null ? new SimpleTimeZoneAwareLocaleContext(locale, timeZone) : new SimpleLocaleContext(locale) : null));
    }
}


HeaderLocaleChangeInterceptor
@Slf4j
public class HeaderLocaleChangeInterceptor extends LocaleChangeInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws ServletException {
        Locale locale = null;
        TimeZone timeZone = null;
        //从请求头中获取信息格式:zh_CN Asia/Shanghai      zh_CN GMT+8
        String newLocale = request.getHeader(getParamName());
        if (newLocale != null) {
            //过滤不需要变化的方法
            if (checkHttpMethod(request.getMethod())) {
                String localePart = newLocale;
                String timeZonePart = null;
                int spaceIndex = localePart.indexOf(' ');
                if (spaceIndex != -1) {
                    localePart = newLocale.substring(0, spaceIndex);
                    timeZonePart = newLocale.substring(spaceIndex + 1);
                }
                try {
                    locale = (!"-".equals(localePart) ? parseLocalevalue(localePart) : null);
                    if (!StringUtils.isEmpty(timeZonePart)) {
                        timeZone = StringUtils.parseTimeZoneString(timeZonePart);
                    }
                } catch (IllegalArgumentException ex) {
                    if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                        // Error dispatch: ignore locale/timezone parse exceptions
                        log.info("ignore locale/timezone parse exceptions");
                    } else {
                        throw new IllegalStateException("Invalid locale Header '" + getParamName() +
                                "' with value [" + newLocale + "]: " + ex.getMessage());
                    }
                }

                LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
                if (localeResolver == null) {
                    throw new IllegalStateException(
                            "No LocaleResolver found: not in a DispatcherServlet request?");
                }
                try {
                    //设置地区
                    localeResolver.setLocale(request, response, locale);
                    if(locale != null){
                        request.setAttribute(HeaderLocaleContextResolver.LOCALE_REQUEST_ATTRIBUTE_NAME,locale);
                    }
                    //设置区时
                    if(timeZone != null){
                        request.setAttribute(HeaderLocaleContextResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME,timeZone);
                    }
                }
                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;
    }

    private boolean checkHttpMethod(String currentMethod) {
        String[] configuredMethods = getHttpMethods();
        if (ObjectUtils.isEmpty(configuredMethods)) {
            return true;
        }
        for (String configuredMethod : configuredMethods) {
            if (configuredMethod.equalsIgnoreCase(currentMethod)) {
                return true;
            }
        }
        return false;
    }

}


springcloud 脚手架(wx-cloud)

  • spring boot 2.3.3.RELEASE
  • nacos
  • geteway
  • oauth2
  • cache(spring+caffeine)
  • mybaits

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/390205.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号