java - example - resourcebundlemessagesource spring boot
¿Admite Spring MessageSource la ruta de clases múltiples? (6)
Estoy diseñando un sistema de complementos para nuestra aplicación basada en web utilizando Spring Framework. Los plugins son jars en classpath. Así que puedo obtener fuentes como jsp, ver más abajo
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] pages = resolver.getResources("classpath*:jsp/*jsp");
Hasta ahora tan bueno. Pero tengo un problema con el messageSource. Me parece que ReloadableResourceBundleMessageSource#setBasename NO admite múltiples rutas de clases a través de "classpath *:" Si solo uso "classpath:", obtengo messageSource solo desde un complemento.
¿Alguien tiene una idea de cómo registrar MessageSources de todos los complementos? ¿Existe tal implementación de MessageSource?
Como alternativa, podría anular el método refreshProperties
de la clase refreshProperties
como el siguiente ejemplo:
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);
}
}
y úselo con la configuración de contexto de Spring como 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>
Creo que esto debería hacer el truco.
Con la solución de @ seralex-vi, los nombres de base / WEB-INF / messages no funcionaron.
Sobrescribí el método refreshProperties de la clase ReloadableResourceBundleMessageSource que realiza ambos tipos de nombres de base (classpath *: y / 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);
}
En el spring-context.xml debe tener el classpath *: prefix
<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>
El problema aquí no es con múltiples classpaths o classloaders, sino con cuántos recursos intentará cargar el código para una ruta determinada.
La sintaxis de classpath*
es un mecanismo Spring, uno que permite que el código cargue múltiples recursos para una ruta determinada. Muy útil. Sin embargo, ResourceBundleMessageSource
usa el java.util.ResourceBundle
estándar para cargar los recursos, y este es un mecanismo mucho más simple y tonto, que cargará el primer recurso para una ruta determinada e ignorará todo lo demás.
Realmente no tengo una solución fácil para ti. Creo que tendrá que abandonar ResourceBundleMessageSource
y escribir una implementación personalizada de MessageSource
(probablemente mediante la subclasificación de AbstractMessageSource
) que utiliza PathMatchingResourcePatternResolver
para ubicar los diversos recursos y exponerlos a través de la interfaz MessageSource
. ResourceBundle
no va a ser de mucha ayuda.
Podría hacer algo similar a lo siguiente: esencialmente, especifique explícitamente cada nombre base relevante.
<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>
Puede aprovechar la configuración de Java y las fuentes de mensajes jerárquicos para crear un sistema de complementos bastante simple. En cada jarra enchufable suelta una clase como esta:
@Configuration
public class MyPluginConfig {
@Bean
@Qualifier("external")
public HierarchicalMessageSource mypluginMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:my-plugin-messages");
return messageSource;
}
}
y los correspondientes archivos my-plugin-messages.properties
.
En la aplicación principal, la clase de configuración de Java puso algo como esto:
@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 el orden de los complementos es relevante, simplemente coloque @Order
anotaciones de @Order
en cada bean de origen de mensajes conectables.
anular ReloadableResourceBundleMessageSource::calculateFilenamesForLocale
puede ser mejor. Luego, ReloadableResourceBundleMessageSource::getProperties
puede obtener PropertiesHolder
desde cachedProperties