[英]Does Spring MessageSource Support Multiple Class Path?
I am designing a plugin system for our web based application using Spring framework.我正在为我们使用 Spring 框架的基于 Web 的应用程序设计一个插件系统。 Plugins are jars on classpath.
插件是类路径上的 jar。 So I am able to get sources such as jsp, see below
所以我能够获得诸如jsp之类的资源,见下文
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] pages = resolver.getResources("classpath*:jsp/*jsp");
So far so good.到目前为止一切顺利。 But I have a problem with the messageSource.
但是我对 messageSource 有问题。 It seems to me that ReloadableResourceBundleMessageSource#setBasename does NOT support multiple class path via the "classpath*:" If I use just "classpath:", I get the messageSource just only from one plugin.
在我看来, ReloadableResourceBundleMessageSource#setBasename不支持通过“classpath*:”的多个类路径,如果我只使用“classpath:”,我只能从一个插件中获取 messageSource。
Does anyone have an idea how to register messageSources from all plugins?有没有人知道如何从所有插件注册 messageSources? Does exist such an implementation of MessageSource?
是否存在这样的 MessageSource 实现?
With the solution of @seralex-vi basenames /WEB-INF/messages did not function.使用@seralex-vi basenames /WEB-INF/messages 的解决方案不起作用。
I overwrited the method refreshProperties on the class ReloadableResourceBundleMessageSource wich perform both type of basenames (classpath*: and /WEB-INF/)我覆盖了类 ReloadableResourceBundleMessageSource 上的方法 refreshProperties ,它执行两种类型的基本名称(classpath*: 和 /WEB-INF/)
public class SmReloadableResourceBundleMessageSource extends ReloadableResourceBundleMessageSource {
private static final String PROPERTIES_SUFFIX = ".properties";
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@Override
protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
if (filename.startsWith(PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) {
return refreshClassPathProperties(filename, propHolder);
} else {
return super.refreshProperties(filename, propHolder);
}
}
private PropertiesHolder refreshClassPathProperties(String filename, PropertiesHolder propHolder) {
Properties properties = new Properties();
long lastModified = -1;
try {
Resource[] resources = resolver.getResources(filename + PROPERTIES_SUFFIX);
for (Resource resource : resources) {
String sourcePath = resource.getURI().toString().replace(PROPERTIES_SUFFIX, "");
PropertiesHolder holder = super.refreshProperties(sourcePath, propHolder);
properties.putAll(holder.getProperties());
if (lastModified < resource.lastModified())
lastModified = resource.lastModified();
}
} catch (IOException ignored) {
}
return new PropertiesHolder(properties, lastModified);
}
On the spring-context.xml you must have the classpath*: prefix在 spring-context.xml 你必须有classpath*:前缀
<bean id="messageSource" class="SmReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>/WEB-INF/i18n/enums</value>
<value>/WEB-INF/i18n/messages</value>
<value>classpath*:/META-INF/messages-common</value>
<value>classpath*:/META-INF/enums</value>
</list>
</property>
</bean>
The issue here is not with multiple classpaths or classloaders, but with how many resources the code will try and load for a given path.这里的问题不在于多个类路径或类加载器,而在于代码将尝试为给定路径加载多少资源。
The classpath*
syntax is a Spring mechanism, one which allows code to load multiple resources for a given path. classpath*
语法是一种 Spring 机制,它允许代码为给定路径加载多个资源。 Very handy.很方便。 However,
ResourceBundleMessageSource
uses the standard java.util.ResourceBundle
to load the resources, and this is a much simpler, dumber mechanism, which will load the first resource for a given path, and ignore everything else.然而,
ResourceBundleMessageSource
使用标准的java.util.ResourceBundle
来加载资源,这是一种更简单、更笨的机制,它将加载给定路径的第一个资源,并忽略其他所有内容。
I don't really have an easy fix for you.我真的没有一个简单的办法给你。 I think you're going to have to ditch
ResourceBundleMessageSource
and write a custom implementation of MessageSource
(most likely by subclassing AbstractMessageSource
) which uses PathMatchingResourcePatternResolver
to locate the various resources and expose them via the MessageSource
interface.我认为您将不得不放弃
ResourceBundleMessageSource
并编写MessageSource
的自定义实现(最有可能通过继承AbstractMessageSource
)它使用PathMatchingResourcePatternResolver
来定位各种资源并通过MessageSource
接口公开它们。 ResourceBundle
isn't going to be much help. ResourceBundle
不会有太大帮助。
You could do something similar to below - essentially specify each relevant basename explicitly.您可以执行类似于下面的操作 - 基本上明确指定每个相关的基本名称。
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:com/your/package/source1</value>
<value>classpath:com/your/second/package/source2</value>
<value>classpath:com/your/third/package/source3/value>
<value>classpath:com/your/fourth/package/source4</value>
</list>
</property>
</bean>
As alternative, you could override refreshProperties
method from ReloadableResourceBundleMessageSource
class like below example:作为替代方案,您可以覆盖
refreshProperties
从方法ReloadableResourceBundleMessageSource
类像下面的例子:
public class MultipleMessageSource extends ReloadableResourceBundleMessageSource {
private static final String PROPERTIES_SUFFIX = ".properties";
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@Override
protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
Properties properties = new Properties();
long lastModified = -1;
try {
Resource[] resources = resolver.getResources(filename + PROPERTIES_SUFFIX);
for (Resource resource : resources) {
String sourcePath = resource.getURI().toString().replace(PROPERTIES_SUFFIX, "");
PropertiesHolder holder = super.refreshProperties(sourcePath, propHolder);
properties.putAll(holder.getProperties());
if (lastModified < resource.lastModified())
lastModified = resource.lastModified();
}
} catch (IOException ignored) { }
return new PropertiesHolder(properties, lastModified);
}
}
and use it with spring context configuration like ReloadableResourceBundleMessageSource
:并将其与诸如
ReloadableResourceBundleMessageSource
类的 spring 上下文配置一起使用:
<bean id="messageSource" class="common.utils.MultipleMessageSource">
<property name="basenames">
<list>
<value>classpath:/messages/validation</value>
<value>classpath:/messages/messages</value>
</list>
</property>
<property name="fileEncodings" value="UTF-8"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
I think this should do the trick.我认为这应该可以解决问题。
overriding ReloadableResourceBundleMessageSource::calculateFilenamesForLocale
may be better.覆盖
ReloadableResourceBundleMessageSource::calculateFilenamesForLocale
可能会更好。 Then, ReloadableResourceBundleMessageSource::getProperties
can get PropertiesHolder
from cachedProperties
然后,
ReloadableResourceBundleMessageSource::getProperties
可以从cachedProperties
获取PropertiesHolder
You can take advantage of Java configuration and hierarchical message sources to build a quite simple plugin system.您可以利用 Java 配置和分层消息源来构建一个非常简单的插件系统。 In each pluggable jar drop a class like this:
在每个可插入的 jar 中放置一个这样的类:
@Configuration
public class MyPluginConfig {
@Bean
@Qualifier("external")
public HierarchicalMessageSource mypluginMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:my-plugin-messages");
return messageSource;
}
}
and the corresponding my-plugin-messages.properties
files.和相应的
my-plugin-messages.properties
文件。
In the main application Java config class put something like this:在主应用程序 Java 配置类中放置如下内容:
@Configuration
public class MainConfig {
@Autowired(required = false)
@Qualifier("external")
private List<HierarchicalMessageSource> externalMessageSources = Collections.emptyList();
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource rootMessageSource = new ReloadableResourceBundleMessageSource();
rootMessageSource.setBasenames("classpath:messages");
if (externalMessageSources.isEmpty()) {
// No external message sources found, just main message source will be used
return rootMessageSource;
}
else {
// Wiring detected external message sources, putting main message source as "last resort"
int count = externalMessageSources.size();
for (int i = 0; i < count; i++) {
HierarchicalMessageSource current = externalMessageSources.get(i);
current.setParentMessageSource( i == count - 1 ? rootMessageSource : externalMessageSources.get(i + 1) );
}
return externalMessageSources.get(0);
}
}
}
If the order of plugins is relevant, just put @Order
annotations in each pluggable message source bean.如果插件的顺序是相关的,只需在每个可插入消息源 bean 中放置
@Order
注释。
As @Jia Feng said an alternative solution overriding ReloadableResourceBundleMessageSource::calculateFilenamesForLocale.正如@Jia Feng 所说的替代 ReloadableResourceBundleMessageSource::calculateFilenamesForLocale 的替代解决方案。
It works on spring 5.x它适用于 spring 5.x
public class WildcardReloadableResourceBundleMessageSource extends ReloadableResourceBundleMessageSource {
private static final String PROPERTIES_SUFFIX = ".properties";
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@Override
protected List<String> calculateFilenamesForLocale(String basename, Locale locale) {
List<String> filenames = super.calculateFilenamesForLocale(basename, locale);
List<String> add = new ArrayList<>();
for (String filename : filenames) {
try {
Resource[] resources = resolver.getResources(filename + PROPERTIES_SUFFIX);
for (Resource resource : resources) {
String sourcePath = resource.getURI().toString().replace(PROPERTIES_SUFFIX, "");
add.add(sourcePath);
}
} catch (IOException ignored) {
}
}
filenames.addAll(add);
return filenames;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.