Поддерживает ли Spring MessageSource несколько путей класса?

Я разрабатываю систему плагинов для нашего веб-приложения с использованием рамки Spring. Плагины - это банки на пути к классам. Поэтому я могу получить источники, такие как jsp, см. Ниже

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

Пока все хорошо. Но у меня проблема с messageSource. Мне кажется, что ReloadableResourceBundleMessageSource # setBasename поддерживает НЕ поддерживать несколько путей класса через classpath *: "Если я использую просто" classpath:", я получаю messageSource только из одного плагина.

Есть ли у кого-нибудь идея, как регистрировать сообщения из всех плагинов? Существует ли такая реализация MessageSource?

Ответ 1

Проблема здесь заключается не в нескольких классах или классах, а в том, сколько ресурсов код попытается загрузить и загрузить для данного пути.

Синтаксис classpath* - это механизм Spring, который позволяет коду загружать несколько ресурсов для заданного пути. Очень удобно. Тем не менее, ResourceBundleMessageSource использует стандартный java.util.ResourceBundle для загрузки ресурсов, и это гораздо более простой механизм Dumb, который загружает первый ресурс для данного пути и игнорирует все остальное.

У меня нет простого решения для вас. Я думаю, вам придется выбежать ResourceBundleMessageSource и написать специальную реализацию MessageSource (скорее всего, путем подклассификации AbstractMessageSource), которая использует PathMatchingResourcePatternResolver для поиска различных ресурсов и отображения их через интерфейс MessageSource, ResourceBundle не будет большой помощью.

Ответ 2

С решением basenames @seralex-vi/WEB-INF/сообщения не работали.

Я перезаписал метод refreshProperties на классе ReloadableResourceBundleMessageSource, который выполняет оба типа basenames (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>

Ответ 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

В качестве альтернативы вы можете переопределить метод 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);
  }
}

и используйте его с конфигурацией контекста 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>

Я думаю, что это должно сделать трюк.

Ответ 5

Вы можете использовать конфигурацию Java и иерархические источники сообщений для создания довольно простой системы плагинов. В каждом подключаемом банке выпадает класс вроде этого:

@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 config class добавляет что-то вроде этого:

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