• 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实现类。

     1 @Configuration
     2 @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
     3 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
     4 @Conditional(ResourceBundleCondition.class)
     5 @EnableConfigurationProperties
     6 public class MessageSourceAutoConfiguration {
     7 
     8     private static final Resource[] NO_RESOURCES = {};
     9 
    10     @Bean
    11     @ConfigurationProperties(prefix = "spring.messages")
    12     public MessageSourceProperties messageSourceProperties() {
    13         return new MessageSourceProperties();
    14     }
    15 
    16     @Bean
    17     public MessageSource messageSource(MessageSourceProperties properties) {
    18         ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    19         if (StringUtils.hasText(properties.getBasename())) {
    20             messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
    21                     StringUtils.trimAllWhitespace(properties.getBasename())));
    22         }
    23         if (properties.getEncoding() != null) {
    24             messageSource.setDefaultEncoding(properties.getEncoding().name());
    25         }
    26         messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    27         Duration cacheDuration = properties.getCacheDuration();
    28         if (cacheDuration != null) {
    29             messageSource.setCacheMillis(cacheDuration.toMillis());
    30         }
    31         messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    32         messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    33         return messageSource;
    34     }
    35 
    36     protected static class ResourceBundleCondition extends SpringBootCondition {
    37 
    38         private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();
    39 
    40         @Override
    41         public ConditionOutcome getMatchOutcome(ConditionContext context,
    42                 AnnotatedTypeMetadata metadata) {
    43             String basename = context.getEnvironment()
    44                     .getProperty("spring.messages.basename", "messages");
    45             ConditionOutcome outcome = cache.get(basename);
    46             if (outcome == null) {
    47                 outcome = getMatchOutcomeForBasename(context, basename);
    48                 cache.put(basename, outcome);
    49             }
    50             return outcome;
    51         }
    52 
    53         private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context,
    54                 String basename) {
    55             ConditionMessage.Builder message = ConditionMessage
    56                     .forCondition("ResourceBundle");
    57             for (String name : StringUtils.commaDelimitedListToStringArray(
    58                     StringUtils.trimAllWhitespace(basename))) {
    59                 for (Resource resource : getResources(context.getClassLoader(), name)) {
    60                     if (resource.exists()) {
    61                         return ConditionOutcome
    62                                 .match(message.found("bundle").items(resource));
    63                     }
    64                 }
    65             }
    66             return ConditionOutcome.noMatch(
    67                     message.didNotFind("bundle with basename " + basename).atAll());
    68         }
    69 
    70         private Resource[] getResources(ClassLoader classLoader, String name) {
    71             String target = name.replace('.', '/');
    72             try {
    73                 return new PathMatchingResourcePatternResolver(classLoader)
    74                         .getResources("classpath*:" + target + ".properties");
    75             }
    76             catch (Exception ex) {
    77                 return NO_RESOURCES;
    78             }
    79         }
    80 
    81     }
    82 
    83 }
    View Code

    首先MessageSource配置生效依靠一个ResourceBundleCondition条件,从环境变量中读取spring.messages.basename对应的值,默认值是messages,这个值就是MessageSource对应的资源文件名称,资源文件扩展名是.properties,然后通过PathMatchingResourcePatternResolver从“classpath*:”目录下读取对应的资源文件,如果能正常读取到资源文件,则加载配置类。

     springmvc自动装配配置类,注册了一个RequestContextFilter过滤器。

     每一次请求,LocaleContextHolder都会保存当前请求的本地化信息。

     通过MessageSourceAccessor根据code获取具体信息时,如果默认配置的本地化对象为空,则通过LocaleContextHolder获取。

     上图的messageSource是应用程序上下文对象(本文创建的是GenericWebApplicationContext实例),该messageSource对象会调用ResourceBundleMessageSource实例获取具体信息。

    ValidationAutoConfiguration

    参数校验hibernate-validator是通过这个自动装配加载进来的。

     1 @Configuration
     2 @ConditionalOnClass(ExecutableValidator.class)
     3 @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
     4 @Import(PrimaryDefaultValidatorPostProcessor.class)
     5 public class ValidationAutoConfiguration {
     6 
     7     @Bean
     8     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
     9     @ConditionalOnMissingBean(Validator.class)
    10     public static LocalValidatorFactoryBean defaultValidator() {
    11         LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    12         MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
    13         factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
    14         return factoryBean;
    15     }
    16 
    17     @Bean
    18     @ConditionalOnMissingBean
    19     public static MethodValidationPostProcessor methodValidationPostProcessor(
    20             Environment environment, @Lazy Validator validator) {
    21         MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
    22         boolean proxyTargetClass = environment
    23                 .getProperty("spring.aop.proxy-target-class", Boolean.class, true);
    24         processor.setProxyTargetClass(proxyTargetClass);
    25         processor.setValidator(validator);
    26         return processor;
    27     }
    28 
    29 }
    View Code

    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值不同,获取到的文本也不同。

  • 相关阅读:
    ajax的一些知识
    前端性能优化汇总
    jquery实现一些小动画二
    python简单日志处理
    逆波兰式---C实现
    java常见异常
    Hive与HBase集成及常见问题解决
    SQL for HBase
    Demystifying the Skip Scan in Phoenix
    Difference between DDL, DML and DCL commands
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/11037577.html
Copyright © 2020-2023  润新知