Strengthen spring series Chapter 17: Spring internationalization (i18n)

Article catalog

Strengthen spring series Chapter 17: Spring internationalization (i18n)

preface

This chapter will discuss the interface and implementation of Spring's internationalization in China and the support of internationalization in Java. Although internationalization is a relatively marginal technology in the Spring system, we can learn and understand it for two reasons

1. In the process of starting abstractapplicationcontext ා refresh application context, initMessageSource() is used to initialize internationalization. As an important part of starting, it is inevitable to learn and understand

2. Many contents in the following chapters are closely integrated with internationalization, which mainly provides some adaptation and support for copywriting

1. Project environment

2.Spring international usage scenario

Four main use scenarios

  • General international copy
  • Bean Validation validates international copywriting
    • This part is discussed in the next chapter
  • Web site page rendering
  • Web MVC error message prompt

3.Spring International interface

Core interface

  • org.springframework.context.MessageSource

Spring provides two out of the box implementations

  • org.springframework.context.support.ResourceBundleMessageSource
  • org.springframework.context.support.ReloadableResourceBundleMessageSource

Key concepts

  • Copy template code
  • Copytemplate parameter (args)
  • Area (Locale)
    • java.util.Locale

3. Hierarchical MessageSource

Spring hierarchical interface review

  • org.springframework.beans.factory.HierarchicalBeanFactory
  • org.springframework.context.ApplicationContext
  • org.springframework.beans.factory.config.BeanDefinition

Spring hierarchical international interface

  • org.springframework.context.HierarchicalMessageSource
public interface HierarchicalMessageSource extends MessageSource {

	/**
	 * Set the parent that will be used to try to resolve messages
	 * that this object can't resolve.
	 * @param parent the parent MessageSource that will be used to
	 * resolve messages that this object can't resolve.
	 * May be {@code null}, in which case no further resolution is possible.
	 */
	void setParentMessageSource(@Nullable MessageSource parent);

	/**
	 * Return the parent of this MessageSource, or {@code null} if none.
	 */
	@Nullable
	MessageSource getParentMessageSource();

}

Hierarchical interfaces have a common feature. Generally, there will be a getParent method that can get information about parents. Parents here may be objects or names of objects.

4.Java internationalization standard implementation

4.1 core interface

  • Abstract implementation- java.util.ResourceBundle

    • Enumerate implementation- java.util.ListResourceBundle (not commonly used)

      • sun.security.util.AuthResources_zh_CN
      • Maintain the information related to the copy into a two-dimensional array by hard coding
      public class AuthResources_zh_CN extends ListResourceBundle {
          private static final Object[][] contents = new Object[][]{
          {"invalid.null.input.value", "Invalid null input: {0}"}, 
          {"NTDomainPrincipal.name", "NTDomainPrincipal: {0}"}, 
          {"NTNumericCredential.name", "NTNumericCredential: {0}"}, 
          {"Invalid.NTSid.value", "invalid NTSid value"}, {"NTSid.name", "NTSid: {0}"}, 
          ...
      
          public AuthResources_zh_CN() {
          }
      
          public Object[][] getContents() {
              return contents;
          }
      }
      
    • Properties resource implementation- java.util.PropertyResourceBundle

      • Use the copycat information in the Properties file to transform accordingly

4.2 core features of resourcebundle

  • Key value design

  • Hierarchy

    • java.util.ResourceBundle#setParent
    • java.util.ResourceBundle#getObject
      • When searching for an object, it will first search from the parent
    • java.util.ResourceBundle#containsKey
  • Cache design

    • java.util.ResourceBundle.CacheKey
        private static final ConcurrentMap<CacheKey, BundleReference> cacheList
            = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
    
  • Field code control- java.util.ResourceBundle.Control(@since 1.6)

  • Control SPI extension- java.util.spi.ResourceBundleContorlProvider(@since 1.8)

5.Java text formatting

Core interface

  • java.text.MessageFormat

Basic Usage

  • Set message format mode - new MessageFormat( )
  • Format - format(new Object [] { }

Message format mode

  • Format element: {ArgumentIndex, (FormatType), (FormatStyle)}
  • FormatType: message format type, optional. Select one of the number, date, time and choice types for each type
  • FormatStyle: message format style, optional, including: short, medium, long, full, integer, current, percent

Advanced features

  • Reset message format mode
  • Reset java.util.Locale
  • Reset java.text.Format

Example

Let's start with the example provided in the java doc

    private static void javaDocDemo() {
        int planet = 7;
        String event = "a disturbance in the Force";

        String result = MessageFormat.format(
                "At {1,time,long} on {1,date,full}, there was {2} on planet {0,number,integer}.",
                planet, new Date(), event);

        System.out.println(result);
    }

Execution result:

At 09:52:06 am on Friday, June 12, 2020, there was a disruption in the force on plane 7

Demonstrate advanced features

  • Reset message format mode
  • Reset java.util.Locale
  • Reset java.text.Format
    public static void main(String[] args) {
        javaDocDemo();

        // Reset MessageFormatPatten
        MessageFormat messageFormat = new MessageFormat("This is a text : {0}");
        messageFormat.applyPattern("This is a new text : {0}");
        String result = messageFormat.format(new Object[]{"hello,world"});
        System.out.println(result);

        // Reset Locale
        messageFormat.setLocale(Locale.ENGLISH);
        messageFormat.applyPattern("At {1,time,long} on {1,date,full}, there was {2} on planet {0,number,integer}.");
        int planet = 7;
        String event = "a disturbance in the Force";
        result = messageFormat.format(new Object[]{planet, new Date(), event});
        System.out.println(result);

        // Reset Format
        // Set Pattern according to parameter index
        messageFormat.setFormat(1,new SimpleDateFormat("YYYY-MM-dd HH:mm:ss"));
        result = messageFormat.format(new Object[]{planet, new Date(), event});
        System.out.println(result);
    }

Execution result:

At 09:52:06 am on 2020 Friday, 12 June, there was a disturbance in the Force on planet 7.
This is a new text : hello,world
At 9:52:06 AM CST on Friday, June 12, 2020, there was a disturbance in the Force on planet 7.
At 9:52:06 AM CST on 2020-06-12 09:52:06, there was a disturbance in the Force on planet 7.

6.MessageSource out of the box implementation

Implementation of MessageSource based on ResourceBundle + MessageFomat

  • org.springframework.context.support.ResourceBundleMessageSource

Overloadable Properties + MessageFormat combination MessageSource implementation

  • org.springframework.context.support.ReloadableResourceBundleMessageSource

7. Built in implementation of MessageSource

MessageSource possible source of built-in Bean

  • Pre registered Bean Name: messageSource, type: MessageSource Bean
  • Default built-in implementation - DelegatingMessageSource
    • Hierarchical lookup of MessageSource object

Source code analysis

  • org.springframework.context.support.AbstractApplicationContext#initMessageSource
    • beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME) determine whether this bean is included in the current context
    • If not, the first time you come in, it's obviously not
      • beanFactory.registerSingleton (MESSAGE_ SOURCE_ BEAN_ NAME, this.messageSource ); register an instance object of MessageSource and put it into the IOC container. This object is created through new DelegatingMessageSource(). You can see that this is an empty implementation through the comment of jdk
      • And then through this.messageSource =DMS; associate the current Application with the new DelegatingMessageSource instance object
    • If it contains, it means that we have registered the MessageSource object into the current context by other means, then we can obtain the bean object by relying on the lookup and associate it with the current Application
protected void initMessageSource() {
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
      this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
      // Make MessageSource aware of parent MessageSource.
      if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
         HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
         if (hms.getParentMessageSource() == null) {
            // Only set parent context as parent MessageSource if no parent MessageSource
            // registered already.
            hms.setParentMessageSource(getInternalParentMessageSource());
         }
      }
      if (logger.isTraceEnabled()) {
         logger.trace("Using MessageSource [" + this.messageSource + "]");
      }
   }
   else {
      // Use empty MessageSource to be able to accept getMessage calls.
      DelegatingMessageSource dms = new DelegatingMessageSource();
      dms.setParentMessageSource(getInternalParentMessageSource());
      this.messageSource = dms;
      beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
      if (logger.isTraceEnabled()) {
         logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
      }
   }
}

8. Application in spring boot

8.1 why does spring boot create a new MessageSource Bean?

  • The implementation of AbstractApplicationContext determines the built-in implementation of MessageSource
    • From the analysis in summary 7, we can see that in the process of spring context startup, when initializing the code related to MessageSource, we will first determine whether we have our own implementation, focusing on our implementation. Spring Boot can use this feature to create its own MessageSource Bean
  • Spring Boot simplifies the construction of MessageSource Bean through external configuration
  • Spring Boot based on Bean Validation validation is very common (main reason)
    • MessageSource can provide relevant copywriting

8.2 automatic assembly of MessageSource

org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration

  • First, prefix the Properties file with spring.messages Encapsulate related content as MessageSourceProperties object
  • Then inject the MessageSource Bean object through @ Bean
    • The best way here is @ Bean (Abstra ctApplicationContext.MESSAGE_ SOURCE_ Bean_ Name) use constant name qualification to ensure that the Bean must meet the judgment conditions in abstractapplicationcontext ා initmessagesource
@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;
	}
    ...

8.3 examples

8.3.1 condition assembly analysis

There are two conditional assemblies in the above implementation of MessageSource auto assembly

  • @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
  • @Conditional(ResourceBundleCondition.class)

The first conditional assembly means that if Bean name = Abstra does not exist in the current context ctApplicationContext.MESSAGE_ SOURCE_ Bean_ Name, in other words, if we register a name of Abstra ctApplicationContext.MESSAGE_ SOURCE_ Bean_ When the name Bean object is in the current context, the automatic assembly of MessageSource in Spring Boot will fail

The second conditional assembly means that you need to create a messages.properties For details, please refer to Towards automatic assembly Chapter 3 Spring Boot conditional assembly)

  • MessageSourceAutoConfiguration.ResourceBundleCondition#getMatchOutcome

8.3.2 test code

@EnableAutoConfiguration
public class CustomizedMessageSourceBeanDemo {

    /**
     * In the Spring Boot scenario, the Primary Configuration Sources(Classes) are higher than * AutoConfiguration
     * @return
     */
    @Bean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME)
    public MessageSource messageSource(){
        return new ReloadableResourceBundleMessageSource();
    }
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(CustomizedMessageSourceBeanDemo.class, args);
        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
        if (beanFactory.containsLocalBean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME)) {
            MessageSource messageSource = applicationContext.getBean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
            System.out.println(messageSource);
        }
        applicationContext.close();
    }
}

Execution result:

You can see that the MessageSource object in the current context is the ReloadableResourceBundleMessageSource object defined by ourselves.

If we need to see the default implementation DelegatingMessageSource in Spring

You can comment out @ EnableAutoConfiguration and @ bean (Abstra ctApplicationContext.MESSAGE_ SOURCE_ BEAN_ NAME)

Execution result:

Delegatingmessagesourceාtostring source code is as follows: so the output is Empty MessageSource

	@Override
	public String toString() {
		return this.parentMessageSource != null ? this.parentMessageSource.toString() : "Empty MessageSource";
	}


The default implementation of ResourceBundleMessageSource in Spring Boot

You can annotate @ bean (Abstra ctApplicationContext.MESSAGE_ SOURCE_ BEAN_ NAME)

Execution result:

9. Interview questions

9.1 what are the Spring International interfaces?

Core interface

  • org.springframework.context.MessageSource

Hierarchical interface

  • org.springframework.context.HierarchicalMessageSource

9.2 what are spring's built-in MessageSource implementations?

  • org.springframework.context.support.ResourceBundleMessageSource
  • org.springframework.context.support.ReloadableResourceBundleMessageSource
  • org.springframework.context.support.StaticMessageSource
  • org.springframework.context.support.DelegatingMessageSource

9.3 how to configure automatic update of MessageSource?

Main technologies

  • Java NIO 2 : java.nio.file.WatchService
  • Java Concurrency : java.util.concurrent.ExecutorService
  • Spring : org.springframework.context.support.AbstractMessageSource

The implementation steps are roughly divided into six steps

  • 1. Locate the resource location (properties file)
  • 2. Initialize the Properties object
  • 3. Implement abstractmessagesource and resolvecode
  • 4. Listening to resource files (Java NIO 2 WatchService)
  • 5. Thread pool processing file changes
  • 6. Reload the Properties object

Related implementation code address

github related implementation class dynamicr esourceMessageSource.java

Call code

    public static void main(String[] args) throws InterruptedException {
        DynamicResourceMessageSource source = new DynamicResourceMessageSource();
        for (int i = 0; i < 10000; i++) {
            System.out.println(source.getMessage("name", new Object[]{}, Locale.getDefault()));
            Thread.sleep(1000L);
        }
    }

After starting, find the msg.properties File modification: save the file ctrl+s. after saving the real-time modification, the print result of the console will change in real time

Console output:

10. References

  • Geek time - brother pony talks about Spring core programming ideas

Tags: Spring Java github JDK

Posted on Fri, 12 Jun 2020 04:00:41 -0400 by BluntedbyNature