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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 06:24:11  来源:igfitidea点击:

Does Spring MessageSource Support Multiple Class Path?

javaspringpluginsclasspath

提问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, ResourceBundleMessageSourceuses the standard java.util.ResourceBundleto 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 ResourceBundleMessageSourceand write a custom implementation of MessageSource(most likely by subclassing AbstractMessageSource) which uses PathMatchingResourcePatternResolverto locate the various resources and expose them via the MessageSourceinterface. ResourceBundleisn't going to be much help.

我真的没有一个简单的办法给你。我认为您将不得不放弃ResourceBundleMessageSource并编写用于定位各种资源并通过接口公开它们的自定义实现MessageSource(最有可能通过子类化AbstractMessageSource)。不会有太大帮助。PathMatchingResourcePatternResolverMessageSourceResourceBundle

回答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 refreshPropertiesmethod from ReloadableResourceBundleMessageSourceclass 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.propertiesfiles.

以及相应的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 @Orderannotations in each pluggable message source bean.

如果插件的顺序是相关的,只需@Order在每个可插入消息源 bean 中添加注释。

回答by Jia Feng

overriding ReloadableResourceBundleMessageSource::calculateFilenamesForLocalemay be better. Then, ReloadableResourceBundleMessageSource::getPropertiescan get PropertiesHolderfrom cachedProperties

覆盖ReloadableResourceBundleMessageSource::calculateFilenamesForLocale可能会更好。然后,ReloadableResourceBundleMessageSource::getProperties可以PropertiesHoldercachedProperties