spring之i18n

简介:

国际化信息也称为本地化信息,i18n是“国际化”的简称。在资讯领域,国际化(i18n)指让产品无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。

demo

step 1. 新增国际化资源问题

在这里插入图片描述
分别在三个文件中添加内容如下:
message.properties:表示默认的,里面可以没有值,但必须有这样的一个文件,可以参见源码:MessageSourceAutoConfiguration.ResourceBundleCondition#getMatchOutcomeForBasename
zh_CH

10001=你好,世界
10002=你好 JAVA

en_US

10001=hello word
10002=hello JAVA
step 2. 配置资源文件位置
spring.messages.basename=i18n.message
step 3. 配置解析器

SessionLocaleResolver为spring内置解析器之一,主要处理session会话级语言解析,其他还有CookieLocaleResolverFixedLocaleResolverAcceptHeaderLocaleResolver

@Configuration
public class LocaleConfig {
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.CHINA);//默认语言
        return localeResolver;
    }
}
step 4 配置拦截器

有了解析器,还需要拦截器来对请求的语言参数进行获取,采用默认的LocaleChangeInterceptor作为拦截器来指定切换国际化语言的参数名。比如当请求的url中包含?lang=zh_CN表示读取国际化文件messages_zh_CN.properties。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleInterceptor localeInterceptor = new LocaleInterceptor();
        registry.addInterceptor(localeInterceptor);
    }
}
@Slf4j
public class LocaleInterceptor extends LocaleChangeInterceptor {
    private static final String LOCALE = "X-Locale";
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String newLocale = request.getHeader(LOCALE);
        if (newLocale != null) {
            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 e) {
                if (isIgnoreInvalidLocale()) {
                    log.warn("Ignoring invalid locale value [{}]: ", newLocale, e);
                } else {
                    throw e;
                }
            }
        }
        return true;
    }
}
step 5 . i18n工具类
@Slf4j
@Component
public class LocaleMessageUtils {

   @Autowired
   private MessageSource messageSource;

   public String getMessage(String code, Object[] args, String defaultMessage) {
       try {
           Locale locale = LocaleContextHolder.getLocale();
           return messageSource.getMessage(code, args, defaultMessage, locale);
       } catch (Exception e) {
           log.error("get locale message failed, ErrorMsg:" + e.getMessage());
           return defaultMessage;
       }
   }
}
step 6. 测试

在这里插入图片描述

在这里插入图片描述

源码分析

MessageSourceAutoConfiguration

ResourceBundleCondition作为是否加载该配置类的条件

   //该方法主要根据配置资源路径是否能找到对应的资源,只要能找到资源时才能加载此配置类
	@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;
		}

springboot提供了国际化信息自动配置类,配置类中注册了ResourceBundleMessageSource实现类。

@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());
		}
		//在找不到当前系统对应的资源文件时,如果该属性为 true,则会默认查找当前系统对应的资源文件,否则就返回 null,返回 null 之后,最终又会调用到系统默认的 messages.properties 文件
		messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
		//设置缓存过期时间
		Duration cacheDuration = properties.getCacheDuration();
		if (cacheDuration != null) {
			messageSource.setCacheMillis(cacheDuration.toMillis());
		}
		//该参数控制的是,当输入参数为空时,是否还是使用MessageFormat.format函数对结果进行格式化,默认是 false;
		messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
		//解析不到资源时是否用code作为返回值
		messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
		return messageSource;
	}

RequestContextFilter

springmvc自动装配配置类,注册了一个RequestContextFilter过滤器,一次请求,LocaleContextHolder都会保存当前请求的本地化信息,一般用于设置默认本地化信息。

//处理过滤器
@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
		initContextHolders(request, attributes);

		try {
			filterChain.doFilter(request, response);
		}
		finally {
		  //请求结束清空本次请求本地化信息
			resetContextHolders();
			if (logger.isTraceEnabled()) {
				logger.trace("Cleared thread-bound request context: " + request);
			}
			attributes.requestCompleted();
		}
	}
	
	//初始化本次请求的本地化配置
	private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
		LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		if (logger.isTraceEnabled()) {
			logger.trace("Bound request context to thread: " + request);
		}
	}
   //重置清空吧本地化配置
	private void resetContextHolders() {
		LocaleContextHolder.resetLocaleContext();
		RequestContextHolder.resetRequestAttributes();
	}

buildLocaleContext

DispatcherServlet#LocaleContextHolder

在每次请求都会用注册的LocaleResolver构造LocaleContext实例,如SessionLocaleResolver#resolveLocaleContext。如果不是LocaleContextResolver的话就直接取HttpServletRequest中的Locale返回,然后this.initContextHolders方法将解析后的Locale对象设值到LocaleContextHolder中:

@Override
	protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
		LocaleResolver lr = this.localeResolver;
		if (lr instanceof LocaleContextResolver) {
			return ((LocaleContextResolver) lr).resolveLocaleContext(request);
		}
		else {
			return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
		}
	}

LocaleContextHolder是用来处理Local的上下文容器(其实就是内部维护了一个ThreadLocal),其中LocaleContext是一个用来获取Locale的接口

//设置当前请求上下文本地化信息
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();
			}
		}
	}

ResourceBundleMessageSource

首先遍历各个basename, 如果资源文件没加载首先会根据basename、locale去加载资源,并将加载的内容缓存起来,然后从加载的资源文件中根据code查找对应的本地化message。该实接口实现类还有一个与之相近的版本ReloadableResourceBundleMessageSource,支持内容的过期个刷新。

@Override
	protected String resolveCodeWithoutArguments(String code, Locale locale) {
		Set<String> basenames = getBasenameSet();
		for (String basename : basenames) {
			ResourceBundle bundle = getResourceBundle(basename, locale);
			if (bundle != null) {
				String result = getStringOrNull(bundle, code);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}

MessageSourceControl

负责资源的具体加载逻辑

@Nullable
		public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
				throws IllegalAccessException, InstantiationException, IOException {
			//资源格式:properties
			if (format.equals("java.properties")) {
			   //拼接资源名
				String bundleName = toBundleName(baseName, locale);
				final String resourceName = toResourceName(bundleName, "properties");
				final ClassLoader classLoader = loader;
				final boolean reloadFlag = reload;
				InputStream inputStream;
				try {
				    //加载资源文件流
					inputStream = AccessController.doPrivileged((PrivilegedExceptionAction<InputStream>) () -> {
						InputStream is = null;
						if (reloadFlag) {
							URL url = classLoader.getResource(resourceName);
							if (url != null) {
								URLConnection connection = url.openConnection();
								if (connection != null) {
									connection.setUseCaches(false);
									is = connection.getInputStream();
								}
							}
						}
						else {
							is = classLoader.getResourceAsStream(resourceName);
						}
						return is;
					});
				}
				catch (PrivilegedActionException ex) {
					throw (IOException) ex.getException();
				}
				if (inputStream != null) {
					String encoding = getDefaultEncoding();
					//设置文件编码
					if (encoding != null) {
						try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
						  //用Properties在家文件流,在资源转换成key-value形式
							return loadBundle(bundleReader);
						}
					}
					else {
						try (InputStream bundleStream = inputStream) {
						  //用Properties在家文件流,在资源转换成key-value形式
							return loadBundle(bundleStream);
						}
					}
				}
				else {
					return null;
				}
			}
			else {
				//文件格式是class
				return super.newBundle(baseName, locale, format, loader, reload);
			}
		}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Springi18n国际化功能可以通过配置properties资源文件来实现。通常,我们会在src/main/resources目录下创建一个i18n文件夹,并在其中创建各类语言的properties资源文件,例如i18n/messages.properties、i18n/messages_en_US.properties、i18n/messages_zh_CN.properties等。\[1\] 在配置文件中,我们可以使用yaml格式进行配置。例如,可以使用spring.messages.basename属性来指定properties文件的路径,如spring.messages.basename: i18n.login。这样,Spring就会根据配置的路径去读取相应的国际化资源文件。\[2\] 另外,如果你在前端使用jQuery,你也可以使用jQuery.i18n.properties插件来实现国际化。该插件可以根据用户指定的语言和国家编码来解析对应的.properties资源文件,从而实现前端的国际化功能。\[3\] #### 引用[.reference_title] - *1* [springi18n国际化处理多语言](https://blog.csdn.net/shenyunsese/article/details/128326378)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SpringBoot -> 国际化(i18n)](https://blog.csdn.net/rod0320/article/details/110086280)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Spring国际化i18n](https://blog.csdn.net/daobuxinzi/article/details/127982064)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值