Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Spring Boot starter: how do you contribute i18n messages to the MessageSource?

I'm writing a custom Spring Boot starter that other developers will drop into their applications, and this starter contains out-of-the-box controllers and UI screens.

These UI screens are internationalized and the i18n keys/values are in a package file: com/foo/wherever/i18n.properties.

I want to ensure that when my starter is loaded at startup, that these i18n.properties are available in the application's MessageSource automatically so that my UI pages work (rendered via normal Spring Controller + ViewResolver + View implementations) without the app developer having to specify this file themselves.

In other words, they should be able to add my starter to their runtime classpath and everything 'just works' without the need to configure anything.

Now, I have discovered that the app developer can create their own src/main/resources/messages.properties file and manually configure the additional messages file in application.properties:

spring.messages.basename = messages, com.foo.wherever.i18n

And this will work.

However, this requires both of the following:

  1. They must manually configure the spring.messages.basename property - it's not automatic. and
  2. They must have their own messages.properties file in their application classpath. If a messages.properties file does not exist, spring.messages.basename doesn't even work. Even if they don't care about i18n, this is still required - not desirable.

I suppose I could move my i18n.properties file to a classpath:/messages.properties file in the starter .jar, but that doesn't seem like a good solution: if the app dev has their own messages.properties file only one of them would be read, resulting in missing message values.

It seems as if Spring Boot MessageSourceAutoConfiguration should have a concept of a CompositeMessageSource that iterates over one or more MessageSource instances that are available (and Ordered) in the Spring ApplicationContext and that is used by the DispatcherServlet. This would allow any starter to contribute to the available messages just by declaring a MessageSource in their auto config

Is it possible to do what I ask? What is the most 'hands off' solution for the app developer?

like image 875
Les Hazlewood Avatar asked Feb 26 '15 18:02

Les Hazlewood


People also ask

What is the starter that can be used to add spring boot dependency?

Adding Classpath Dependencies. Spring Boot provides a number of “Starters” that let you add jars to your classpath. Our applications for smoke tests use the spring-boot-starter-parent in the parent section of the POM. The spring-boot-starter-parent is a special starter that provides useful Maven defaults.

What is spring boot I18N?

Internationalization or I18N is a process that makes your application adaptable to different languages and regions without engineering changes on the source code. You can display messages, currencies, date, time etc.

How does spring boot handle internationalization?

By default, a Spring Boot application will look for message files containing internationalization keys and values in the src/main/resources folder. The file for the default locale will have the name messages. properties, and files for each locale will be named messages_XX. properties, where XX is the locale code.

Should I use Springboot starter?

Generally i would agree with your colleague to keep your dependencies as clean as possible. However, if you want to use any features related to Spring Boot, then i would advise you to use the starters, since they include all required dependencies and enable the necessary auto configuration classes.


1 Answers

I realize this is an old and answered question by now, but I encountered the same problem the other day, and wrote a blog post about how I've decided to solve it. I thought I should share it here since I got some inspiration for my solution from this thread.

In short, it takes sodik's idea of intercepting the creation of the MessageSource bean, but instead of using a BeanFactoryPostProcessor I'm using a BeanPostProcessor, and rather than replacing the original MessageSource in the application context, I just add my own as its parent:

@Bean
BeanPostProcessor messageSourceCustomExtender() {
    return new BeanPostProcessor() {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof HierarchicalMessageSource && beanName.equals("messageSource")) {
                ResourceBundleMessageSource parent = new ResourceBundleMessageSource();
                parent.setBasename("custom");

                ((HierarchicalMessageSource) bean).setParentMessageSource(parent);
            }

            return bean;
        }
    };
}

You can read the full blog post where I explain some caveats about my solution: http://www.thomaskasene.com/2016/08/20/custom-spring-boot-starter-messagesource/

Update

After some tinkering I realized that using a BeanFactoryPostProcessor was wrong as it will cause the original MessageSource bean to be created prematurely and ignore application properties (most importantly, spring.messages.basename). That means the application won't be able to configure these properties. See the excerpt from the BeanFactoryPostProcessor documentation below.

A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.

I've updated the example above to use BeanPostProcessor instead, which alters the bean instance rather than the bean definition.

like image 195
Thomas Kåsene Avatar answered Sep 19 '22 21:09

Thomas Kåsene