Springboot国际化信息(i18n)解析
国际化信息理解
国际化信息也称为本地化信息 。 Java 通过 java.util.Locale 类来表示本地化对象,它通过 “语言类型” 和 “国家/地区” 来创建一个确定的本地化对象 。举个例子吧,比如在发送一个具体的请求的时候,在header中设置一个键值对:"Accept-Language":"zh",通过Accept-Language对应值,服务器就可以决定使用哪一个区域的语言,找到相应的资源文件,格式化处理,然后返回给客户端。
MessageSource
Spring 定义了 MessageSource 接口,用于访问国际化信息。
- getMessage(String code, Object[] args, String defaultMessage, Locale locale)
- getMessage(String code, Object[] args, Locale locale)
- getMessage(MessageSourceResolvable resolvable, Locale locale)
MessageSourceAutoConfiguration
springboot提供了国际化信息自动配置类,配置类中注册了ResourceBundleMessageSource实现类。
@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, 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<String, ConditionOutcome> 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;
}
} } }
首先MessageSource配置生效依靠一个ResourceBundleCondition条件,从环境变量中读取spring.messages.basename对应的值,默认值是messages,这个值就是MessageSource对应的资源文件名称,资源文件扩展名是.properties,然后通过PathMatchingResourcePatternResolver从“classpath*:”目录下读取对应的资源文件,如果能正常读取到资源文件,则加载配置类。
springmvc自动装配配置类,注册了一个RequestContextFilter过滤器。
每一次请求,LocaleContextHolder都会保存当前请求的本地化信息。
通过MessageSourceAccessor根据code获取具体信息时,如果默认配置的本地化对象为空,则通过LocaleContextHolder获取。
上图的messageSource是应用程序上下文对象(本文创建的是GenericWebApplicationContext实例),该messageSource对象会调用ResourceBundleMessageSource实例获取具体信息。
ValidationAutoConfiguration
参数校验hibernate-validator是通过这个自动装配加载进来的。
@Configuration
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration { @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(Validator.class)
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
} @Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(
Environment environment, @Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = environment
.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
} }
MethodValidationPostProcessor这个后置处理处理方法里单个参数校验的注解(JSR和Hibernate validator的校验只能对Object的属性(也就是Bean的域)进行校验,不能对单个的参数进行校验。)。
LocalValidatorFactoryBean实现了javax.validation.ValidatorFactory和javax.validation.Validator这两个接口,以及Spring的org.springframework.validation.Validator接口,你可以将这些接口当中的任意一个注入到需要调用验证逻辑的Bean里。
默认情况下,LocalValidatorFactoryBean创建的validator使用PlatformResourceBundleLocator获取资源的绑定关系,获取的资源名称是:ValidationMessages
用户自定义的校验信息放在项目classpath目录下。
另外hibernate-validator还会加载默认的校验资源文件,名称是:org.hibernate.validator.ValidationMessages。可以看到,默认的校验资源捆绑文件包含了不同区域的信息的配置。
通过LocalValidatorFactoryBean获取的validator是如何根据不同的地区加载不同校验资源文件呢?hibernate-validator暴露了一个消息插补器(MessageInterpolator),spring正是重新代理这个消息插补器。
通过LocaleContextMessageInterpolator源码,可以看到最终还是通过LocaleContextHolder获取当前时区信息。
是否可以自定义国际化校验的资源信息呢?当然是肯定的,我们只需要重写LocalValidatorFactoryBean类型bean的创建过程,通过setValidationMessageSource方法指定自定义的资源信息。
MessageSource测试
基础测试
建立Resouce bundle messages
编写message source测试方法,从request中获取当前Locale值
编写测试类,指定当前请求的Locale值或者设置请求头的header值:Accept-Language:zh
根据测试类中请求的Locale值不同,获取到的文本也不同。
格式化测试
建立Resouce bundle messages
编写message source测试方法,从request中获取当前Locale值
编写测试类,指定当前请求的Locale值或者设置请求头的header值:Accept-Language:zh
根据测试类中请求的Locale值不同,获取到的格式化的文本也不同。
静态message source测试
动态注册message(可区分Locale),可用于自定义message source。
编写测试的方法,通过MessageSourceAccessor访问。
编写测试类,获取自定义message source中的信息。
根据测试类中请求的Locale值不同,获取到的文本也不同。
最新文章
- PHP设计模式笔记
- [转]DAO层,Service层,Controller层、View层
- Sql视图创建语句
- android自定义控件(7)-获取自定义ImageView的src属性
- Like ruby of SBM Crusher zip to dict
- open_table与opened_table --2
- AppDelegate 里一个基本的跳转方法,用来在rootView崩溃的时候直接调试我自己的页面
- JS——实现短信验证码的倒计时功能(没有验证码,只有倒计时)
- NodeJS 阻塞/非阻塞
- 关于微信小程序拒绝授权后,重新授权并获取用户信息
- Struts2简诉
- bzoj:1723: [Usaco2009 Feb]The Leprechaun 寻宝
- C++ 排列最优解算法思想
- [Swift]LeetCode1009. 十进制整数的补码 | Complement of Base 10 Integer
- Codeforces 1097G
- 全网搜歌神器Listen1 Mac中文版
- SQLServer数据库增删改查
- 第二十八节:Java基础-进阶继承,抽象类,接口
- 一不小心发现了个Asp.Net Bug
- 基于alpine用dockerfile创建的tomcat镜像
热门文章
- 【Kubernetes 系列一】Kubernetes 概述
- 洛谷 P1903 [国家集训队]数颜色
- (二十一)c#Winform自定义控件-气泡提示
- django分页的写法,前端后端!
- JMeter使用JSON Extractor插件实现将一个接口的JSON返回值作为下一个接口的入参
- 实战redhat6.5离线升级openssl&;openssh
- CentOS -- RocketMQ HA &; Monitoring
- 分布式日志收集系统 —— Flume
- 根据图中的盲点坐标,弹出div层
- LeetCode 笔记