简体   繁体   English

Spring MessageSource 是否支持多个类路径?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM