Spring MessageSource是否支持多类路径?

时间:2010-10-08 08:24:30

标签: java spring plugins classpath

我正在使用Spring框架为基于Web的应用程序设计插件系统。插件是类路径上的jar。所以我能够得到像jsp这样的资源,见下文

ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] pages = resolver.getResources("classpath*:jsp/*jsp");

到目前为止一切顺利。但我的messageSource有问题。在我看来ReloadableResourceBundleMessageSource#setBasename通过“classpath *:”不支持多个类路径如果我只使用“classpath:”,我只从一个插件获取messageSource。 / p>

有没有人知道如何从所有插件注册messageSources?是否存在MessageSource的这种实现?

7 个答案:

答案 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;
    }
    
}