Java Spring MessageSource 是否支持多个类路径?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3888832/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does Spring MessageSource Support Multiple Class Path?
提问by banterCZ
I am designing a plugin system for our web based application using Spring framework. Plugins are jars on classpath. So I am able to get sources such as jsp, see below
我正在为我们使用 Spring 框架的基于 Web 的应用程序设计一个插件系统。插件是类路径上的 jar。所以我能够获得诸如jsp之类的资源,见下文
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] pages = resolver.getResources("classpath*:jsp/*jsp");
So far so good. But I have a problem with the messageSource. It seems to me that ReloadableResourceBundleMessageSource#setBasenamedoes NOT supportmultiple class path via the "classpath*:" If I use just "classpath:", I get the messageSource just only from one plugin.
到现在为止还挺好。但是我对 messageSource 有问题。在我看来,ReloadableResourceBundleMessageSource#setBasename不支持通过“classpath*:”的多个类路径,如果我只使用“classpath:”,我只能从一个插件中获取 messageSource。
Does anyone have an idea how to register messageSources from all plugins? Does exist such an implementation of MessageSource?
有没有人知道如何从所有插件注册 messageSources?是否存在这样的 MessageSource 实现?
采纳答案by skaffman
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. 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.
该classpath*
语法是一个弹簧机构,一个允许的代码来加载多个资源对于给定的路径。非常便利。但是,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. ResourceBundle
isn't going to be much help.
我真的没有一个简单的办法给你。我认为您将不得不放弃ResourceBundleMessageSource
并编写用于定位各种资源并通过接口公开它们的自定义实现MessageSource
(最有可能通过子类化AbstractMessageSource
)。不会有太大帮助。PathMatchingResourcePatternResolver
MessageSource
ResourceBundle
回答by Raghuram
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>
回答by seralex.vi
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
:
并将其与 spring 上下文配置一起使用,例如ReloadableResourceBundleMessageSource
:
<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.
我认为这应该可以解决问题。
回答by ajaristi
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>
回答by jglatre
You can take advantage of Java configuration and hierarchical message sources to build a quite simple plugin system. In each pluggable jar drop a class like this:
您可以利用 Java 配置和分层消息源来构建一个非常简单的插件系统。在每个可插入的 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.
如果插件的顺序是相关的,只需@Order
在每个可插入消息源 bean 中添加注释。
回答by Jia Feng
overriding ReloadableResourceBundleMessageSource::calculateFilenamesForLocale
may be better. Then, ReloadableResourceBundleMessageSource::getProperties
can get PropertiesHolder
from cachedProperties
覆盖ReloadableResourceBundleMessageSource::calculateFilenamesForLocale
可能会更好。然后,ReloadableResourceBundleMessageSource::getProperties
可以PropertiesHolder
从cachedProperties