【问题标题】:Does Spring MessageSource Support Multiple Class Path?Spring MessageSource 是否支持多类路径?
【发布时间】:2010-10-08 08:24:30
【问题描述】:

我正在使用 Spring 框架为我们的基于 Web 的应用程序设计一个插件系统。插件是类路径上的 jar。这样我就可以得到jsp之类的源码了,见下文

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

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

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

【问题讨论】:

    标签: java spring plugins classpath


    【解决方案1】:

    用@seralex-vi basenames /WEB-INF/messages 的解决方法没有起作用。

    我覆盖了 ReloadableResourceBundleMessageSource 类的 refreshProperties 方法,该方法执行两种类型的基本名称(类路径*:和 /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>
    

    【讨论】:

    • 这应该是答案,它提供了解决方案并且有效。谢谢
    • 在 2021 年,这似乎仍然是最好的工作答案。谢谢!
    【解决方案2】:

    这里的问题不在于多个类路径或类加载器,而是代码将尝试为给定路径加载多少资源。

    classpath* 语法是一种 Spring 机制,它允许代码为给定路径加载多个资源。非常便利。然而,ResourceBundleMessageSource 使用标准的java.util.ResourceBundle 加载资源,这是一种更简单、更笨的机制,它将加载给定路径的第一个资源,而忽略其他所有内容。

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

    【讨论】:

    • 谢谢!这是我担心的事情。
    • 有关有效的解决方案,请查看ajaristi's answer
    【解决方案3】:

    您可以执行类似于以下的操作 - 基本上明确指定每个相关的基本名称。

     <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>
    

    【讨论】:

    • 是的,没错。但是您必须事先了解所有插件。插件的解决方案应该是通用的。
    • 你刚刚教我如何在值中输入包路径。
    【解决方案4】:

    作为替代方案,您可以覆盖 ReloadableResourceBundleMessageSource 类中的 refreshProperties 方法,如下例所示:

    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 之类的 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>
    

    我认为这应该可以解决问题。

    【讨论】:

      【解决方案5】:

      覆盖ReloadableResourceBundleMessageSource::calculateFilenamesForLocale 可能会更好。那么ReloadableResourceBundleMessageSource::getProperties可以从cachedProperties得到PropertiesHolder

      【讨论】:

      • 覆盖“calculateFilenamesForLocale”确实更好,因为它使您仍然可以使用 ReloadableResourceBundleMessageSource 的“Reloadble”部分
      【解决方案6】:

      您可以利用 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);
              }
          }
      }
      

      如果插件的顺序相关,只需将@Order注解放在每个可插入消息源bean中即可。

      【讨论】:

        【解决方案7】:

        正如@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;
            }
            
        }
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-09-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-26
          • 2015-12-18
          • 2011-06-28
          相关资源
          最近更新 更多