2010-10-08 8 views
14

Je suis en train de concevoir un système de plugin pour notre application web en utilisant Spring framework. Les plugins sont des pots sur classpath. Donc, je suis en mesure d'obtenir des sources telles que JSP, voir ci-dessousSpring MessageSource prend-il en charge le chemin de classes multiples?

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

Jusqu'ici tout va bien. Mais j'ai un problème avec le messageSource. Il me semble que ReloadableResourceBundleMessageSource#setBasename ne prend pas en charge chemin de classe multiples via le "classpath *:" Si j'utilise simplement "classpath:", je reçois le messageSource seulement à partir d'un plugin.

Est-ce que quelqu'un a une idée de comment enregistrer des sources de message à partir de tous les plugins? Existe-t-il une telle implémentation de MessageSource?

Répondre

8

La question n'est pas ici avec plusieurs classpaths ou classloaders, mais avec combien de ressources le code va essayer de charge pour un chemin donné.

La syntaxe classpath* est un mécanisme Spring, qui permet au code de charger plusieurs ressources pour un chemin donné. Très utile. Cependant, ResourceBundleMessageSource utilise la norme java.util.ResourceBundle pour charger les ressources, et il s'agit d'un mécanisme de dumper beaucoup plus simple, qui chargera la première ressource pour un chemin donné, et ignorera tout le reste.

Je n'ai pas vraiment de solution facile pour vous. Je pense que vous allez devoir abandonner ResourceBundleMessageSource et écrire une implémentation personnalisée de MessageSource (très probablement en sous-classant AbstractMessageSource) qui utilise PathMatchingResourcePatternResolver pour localiser les différentes ressources et les exposer via l'interface MessageSource. ResourceBundle ne va pas être beaucoup d'aide.

+0

Merci! C'est quelque chose dont je m'inquiétais. – banterCZ

+0

Pour une solution qui fonctionne regarder [réponse de l'ajaristi] (http://stackoverflow.com/a/27532814/606662) –

9

Vous pouvez faire quelque chose de similaire à ci-dessous - spécifiez essentiellement chaque nom de base pertinent explicitement.

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

Oui, c'est vrai. Mais vous devez d'abord connaître tous les plugins. La soulution devrait être universelle pour les plugins. – banterCZ

+4

Vous venez de m'apprendre à entrer des chemins de paquets dans des valeurs. –

2

Comme alternative, vous pouvez remplacer refreshProperties méthode de ReloadableResourceBundleMessageSource classe comme exemple ci-dessous:

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); 
    } 
} 

et l'utiliser avec la configuration du contexte de printemps comme 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> 

Je pense que cela devrait faire l'affaire .

10

Avec la solution de @ seralex-vi, les noms de base/WEB-INF/messages ne fonctionnaient pas.

I la méthode refreshProperties écrasent les sur les ReloadableResourceBundleMessageSource de classe Wich effectuer deux types de noms de base (classpath *: et/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); 
} 

vous devez Au printemps context.xml ont le classpath *: préfixe

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

Cela devrait être la réponse, il donne une solution et cela fonctionne. Merci – Don

0

Vous pouvez profiter de la configuration Java et des sources de messages hiérarchiques pour construire un système de plugin assez simple. Dans chaque pot connectable laisser tomber une classe comme ceci:

@Configuration 
public class MyPluginConfig { 
    @Bean 
    @Qualifier("external") 
    public HierarchicalMessageSource mypluginMessageSource() { 
     ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 
     messageSource.setBasenames("classpath:my-plugin-messages"); 
     return messageSource; 
    } 
} 

et les my-plugin-messages.properties fichiers correspondants.

Dans la classe principale de configuration Java d'application mis quelque chose comme ceci:

@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); 
     } 
    } 
} 

Si l'ordre des plugins est pertinent, il suffit de mettre @Order annotations dans chaque grain de source du message connectable.