Custom Spring Boot starter:你如何向MessageSource贡献i18n消息?

我正在编写一个自定义的Spring Boot启动程序,其他开发人员将放入他们的应用程序,这个启动程序包含开箱即用的控制器和UI屏幕。

这些UI屏幕是国际化的,i18n键/值位于包文件中: com/foo/wherever/i18n.properties

我想确保在启动时加载我的启动器时,这些i18n.properties在应用程序的MessageSource自动可用,以便我的UI页面工作(通过普通的Spring Controller + ViewResolver + View实现呈现), 而app开发人员不必指定这个文件本身

换句话说,他们应该能够将我的启动器添加到他们的运行时类路径中,并且一切“正常工作”而无需配置任何东西。

现在,我发现app开发人员可以创建自己的src/main/resources/messages.properties文件, application.properties手动配置其他消息文件:

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

这将有效。

但是,这需要以下两个方面:

  1. 他们必须手动配置spring.messages.basename属性 – 它不是自动的。 和
  2. 它们必须在其应用程序类路径中拥有自己的messages.properties文件。 如果messages.properties文件不存在,则spring.messages.basename甚至不起作用。 即使他们不关心i18n,这仍然是必需的 – 不可取。

我想我可以将我的i18n.properties文件移动到启动器.jar中的类路径:/messages.properties文件中,但这似乎不是一个好的解决方案:如果app dev有自己的messages.properties文件只有一个它们将被读取,导致消息值丢失。

看起来好像Spring Boot MessageSourceAutoConfiguration应该有一个CompositeMessageSource的概念,它迭代Spring ApplicationContext中可用(和Order ed)的一个或多个MessageSource实例,并由DispatcherServlet使用。 这将允许任何启动器仅通过在其自动配置中声明MessageSource来贡献可用消息

有可能做我的要求吗? 应用程序开发人员最关心的解决方案是什么?

也许它是远景,但你可以尝试使用BeanFactoryPostProcessor 。

想法如下:

  1. 从应用程序上下文中取出“messageSource”bean。 请注意,如果开发人员想要使用自己的实现并且不使用spring boot autoconfiguration,它可能但不一定是spring boot。

  2. 将其替换为您自己的实现,尝试解析“您的密钥”,其余代理到原始消息源。 反之亦然如果您希望覆盖开发人员的翻译(如果原始消息源不会为未知密钥抛出exception,则可能会出现问题)。

但可能有更好的方法来做到这一点。

我用以下方式设置它。 我目前只支持en_US,但它设置为使用国际化(i18n)处理任意数量的语言。

在此处查看代码

在这里查看代码要点: github gist上的代码

添加消息源和默认区域设置Bean

将这些bean添加到Application.java中以设置默认语言环境并配置消息道具的位置

消息源和默认语言环境


创建消息服务

服务将从会话中获取默认语言环境,然后从您的道具中获取消息文本

设置消息svc


使用Controller中的消息服务

注入消息svc然后传入id以从props文件中获取值

在控制器中使用svc


在语言环境中添加message.properties文件

转到/资源:

  • 创建语言环境文件夹
  • 创建一个名为messages_en_US.properties的文件

消息道具


阅读更多

您可以在此处查看有关此主题的更完整的文章: Spring Boot Internationalization i18n using Message Properties


看看守则

在这里查看代码要点: github gist上的代码

我现在意识到这是一个古老而回答的问题,但是前几天我遇到了同样的问题,写了一篇关于我如何决定解决它的博客文章。 我想我应该在这里分享一下,因为我从这个post中获得了一些灵感。

简而言之,它需要sodik的想法来拦截MessageSource bean的创建,但不是使用BeanFactoryPostProcessor而是使用BeanPostProcessor ,而不是替换应用程序上下文中的原始MessageSource ,我只是添加自己的父作为其父:

 @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; } }; } 

您可以阅读完整的博客文章,其中我解释了有关我的解决方案的一些注意事项: http : //www.thomaskasene.com/2016/08/20/custom-spring-boot-starter-messagesource/

更新

经过一些修补后,我意识到使用BeanFactoryPostProcessor是错误的,因为它会导致原始的MessageSource bean过早地创建并忽略应用程序属性(最重要的是, spring.messages.basename )。 这意味着应用程序将无法配置这些属性。 请参阅下面的BeanFactoryPostProcessor文档的摘录。

BeanFactoryPostProcessor可以与bean定义交互并修改bean定义,但绝不能与bean实例交互。 这样做可能会导致bean过早实例化,违反容器并导致意外的副作用。 如果需要bean实例交互,请考虑实现BeanPostProcessor。

我已经更新了上面的例子来使用BeanPostProcessor ,它改变了bean实例而不是bean定义。