我正在使用Spring框架为基于Web的应用程序设计插件系统。插件是类路径上的jar。所以我能够得到像jsp这样的资源,见下文
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] pages = resolver.getResources("classpath*:jsp/*jsp");
到目前为止一切顺利。但我的messageSource有问题。在我看来ReloadableResourceBundleMessageSource#setBasename通过“classpath *:”不支持多个类路径如果我只使用“classpath:”,我只从一个插件获取messageSource。 / p>
有没有人知道如何从所有插件注册messageSources?是否存在MessageSource的这种实现?
答案 0 :(得分:15)
使用@ seralex-vi basenames / WEB-INF / messages的解决方案无效。
我在类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);
}
在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>
答案 1 :(得分:9)
您可以执行与以下类似的操作 - 基本上明确指定每个相关的基本名称。
<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>
答案 2 :(得分:8)
这里的问题不在于多个类路径或类加载器,而在于代码将尝试为给定路径加载多少资源。
classpath*
语法是一种Spring机制,允许代码为给定路径加载多个资源。非常便利。但是,ResourceBundleMessageSource
使用标准java.util.ResourceBundle
来加载资源,这是一个更简单的dumber机制,它将加载给定路径的第一个资源,并忽略其他所有内容。
我真的没有一个容易解决的问题。我认为你将不得不放弃ResourceBundleMessageSource
并编写MessageSource
的自定义实现(很可能是通过子类化AbstractMessageSource
),它使用PathMatchingResourcePatternResolver
来查找各种资源和通过MessageSource
接口公开它们。 ResourceBundle
不会有太多帮助。
答案 3 :(得分:2)
作为替代方案,您可以覆盖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);
}
}
并将其与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>
我认为这应该可以解决问题。
答案 4 :(得分:1)
覆盖ReloadableResourceBundleMessageSource::calculateFilenamesForLocale
可能会更好。然后,ReloadableResourceBundleMessageSource::getProperties
可以从PropertiesHolder
cachedProperties
答案 5 :(得分:0)
您可以利用Java配置和分层消息源来构建一个非常简单的插件系统。在每个可插入的jar中放一个这样的类:
@Configuration
public class MyPluginConfig {
@Bean
@Qualifier("external")
public HierarchicalMessageSource mypluginMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:my-plugin-messages");
return messageSource;
}
}
以及相应的my-plugin-messages.properties
个文件。
在主应用程序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);
}
}
}
如果插件的顺序是相关的,只需在每个可插入的消息源bean中添加@Order
注释。
答案 6 :(得分:0)
正如@Jia Feng 所说的替代 ReloadableResourceBundleMessageSource::calculateFilenamesForLocale 的替代解决方案。
适用于 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;
}
}