I'm having some trouble with using Spring Boot with Kotlin to handle locale. I created these settings in my application.properties file:
spring.messages.basename=messages/messages
spring.messages.cache-seconds=-1
spring.messages.encoding=UTF-8
Then created an autowired instance of MessageSource in my controller:
#Autowired
lateinit var messageSource: MessageSource
When put a locale in the url as a parameter, the parameter doesn't seem to get picked up when I call LocaleContextHolder.getLocale(), so it is always en_US. Though, I can manually pick it up using #RequestParam(value="locale") locale: Locale as a parameter to my controller function and use it from there in the controller function, but not in other functions. I thought that spring boot LocaleContextHolder was supposed to hold the current locale based on the request URL automatically for the whole session.
I read an older article that mentioned using a LocaleChangeInterceptor bean as well as beans for MessageSource and LocaleResolver in your main class, but another article said Spring Boot doesn't require that. I tried it anyway with no difference. These are the functions I used:
#Bean
open fun localeResolver(): LocaleResolver {
val slr = SessionLocaleResolver()
slr.setDefaultLocale(Locale.US)
return slr
}
#Bean
open fun localeChangeInterceptor(): LocaleChangeInterceptor {
val localeChangeInterceptor = LocaleChangeInterceptor()
localeChangeInterceptor.paramName = "locale"
return localeChangeInterceptor
}
#Bean
open fun messageSource(): ResourceBundleMessageSource {
val source = ResourceBundleMessageSource()
source.setBasenames("messages/messages")
source.setDefaultEncoding("UTF-8")
return source
}
Any suggestions on what to try next other than capturing the locale manually and making it a parameter in every function that gets called by the controller? Thanks!
OK, looks like the missing piece was implementing a WebMvcConfigurerAdapter, setting up a SessionLocaleResolver bean, and overriding the addInterceptors function to manually add my LocaleChangeInterceptor. I used a separate Configuration class to do this.
#Configuration
open class CustomWebMvcConfigurerAdapter : WebMvcConfigurerAdapter() {
//internationalization beans
#Bean
open fun localeResolver(): LocaleResolver {
val slr = SessionLocaleResolver()
slr.setDefaultLocale(Locale.US)
return slr
}
#Bean
open fun localeChangeInterceptor(): LocaleChangeInterceptor {
val localeChangeInterceptor = LocaleChangeInterceptor()
localeChangeInterceptor.paramName = "locale"
return localeChangeInterceptor
}
override fun addInterceptors(registry: InterceptorRegistry?) {
registry?.addInterceptor(localeChangeInterceptor())
super.addInterceptors(registry)
}
}
I guess I misunderstood, thinking that Spring Boot handled the LocaleChangeInterceptor on it's own if you created the proper bean, but I guess you have to still override a WebMvcConfigurerAdapter and force the interceptor in there. If I've missed something and someone has a cleaner solution, I'd be happy to give you the accepted answer since what I'm doing here just feels like a messy workaround.
Related
Spring + thymeleaf
I want to display the message from Messages_pl.properties or Messages_en.properties depending on the need. And here's my problem as I don't know what to do when I want to view a message from the second file ( Messages_pl.properties is taken into account by default).
To access resource bundles by using specified basename I added the bean below to my #Configuration class:
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("Messages");
return messageSource;
}
Messages_pl.properties:
welcome.message=siemanko
Messages_en.properties:
welcome.message=hello
The fragment of my html file where I use this property:
<h1 th:text="#{message.welcome}"></h1>
Result: siemanko
what should I do to get the result as hello?
You can define on the same file Messages.properties the following :
welcome.message.pl=siemanko
welcome.message.en=hello
Then you can can a local resolver to make your project capable of determining the locale which is currently being used :
#Bean
public LocaleResolver localeResolver() {
return new CookieLocaleResolver();
}
Then add an interceptor of your language :
#Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
After that its simple to switch between languages , you just have to change the value of parameter lang on your link :
localhost:8080/your_page?lang=pl // will show siemanko on your page
localhost:8080/your_page?lang=en // will show hello on your page
I am running into this issue since I have an app that has restful services but I need to serve up some static ones too. In this app I also use the EnableSwagger2 annotation. It looks like this class is public and I could subclass it but I am not sure how to configure that. My goal is to override this line so I can have control over 404s.
So I finally did do this and it worked successfully for me. The config got me for a bit but here it is. Say you subclass ResourceHttpRequestHandler with one named CustomResourceHandler. In my Application.java this wired it up properly:
#Bean
public ResourceHttpRequestHandler resourceHttpRequestHandler() {
ResourceHttpRequestHandler requestHandler = new CustomResourceHandler();
requestHandler.setLocations(Arrays.<Resource>asList(applicationContext.getResource("/")));
return requestHandler;
}
#Bean
public SimpleUrlHandlerMapping sampleServletMapping(){
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Integer.MAX_VALUE - 2);
Properties urlProperties = new Properties();
urlProperties.put("/**", "resourceHttpRequestHandler");
mapping.setMappings(urlProperties);
return mapping;
}
#Autowired
ApplicationContext applicationContext;
This Answer helped me with the proper config of the mapper.
This Answer about context helped me get the locations set properly in my handler. I was setting them a different way but it wasn't working 100% properly for all my resources
In Kotlin function parameters are always values which prevents #Qualifier() from being used at the parameter level.
If someone wanted to create multiple DataSources in a config class for different databases frequently accessed within the same application what is the recommended way to organize this?
Doing the following which seemed fairly common in Java isn't allowed in Kotlin.
#Configuration
class DatabaseAdminConfig {
#Bean
#ConfigurationProperties(prefix = "spring.ds_admin")
fun adminDataSource(): DataSource {
return DataSourceBuilder.create().build()
}
#Bean
fun adminJdbcTemplate(#Qualifier("adminDataSource") dsAdminDb: DataSource): JdbcTemplate {
return JdbcTemplate(dsAdminDb)
}
#ConfigurationProperties(prefix = "spring.ds_widget")
fun widgetDataSource(): DataSource {
return DataSourceBuilder.create().build()
}
#Bean
fun widgetJdbcTemplate(#Qualifier("widgetDataSource") widgetDataSource: DataSource): JdbcTemplate {
return JdbcTemplate(widgetDataSource)
}
}
There is no need to inject the dependency from the same class into the method that requires that dependency in Spring. You can just call the method directly.
#Configuration
class DatabaseAdminConfig {
#Bean
#ConfigurationProperties(prefix = "spring.ds_admin")
fun adminDataSource() = DataSourceBuilder.create().build()
#Bean
fun adminJdbcTemplate() = JdbcTemplate(adminDataSource())
#Bean
#ConfigurationProperties(prefix = "spring.ds_widget")
fun widgetDataSource() = DataSourceBuilder.create().build()
#Bean
fun widgetJdbcTemplate() = JdbcTemplate(widgetDataSource())
}
PS: function expression bodies (as above) make Spring Configuration classes look even more concise.
PPS: #Qualifier works totally fine for me. I just tested it.
PPPS: And for completeness to address the specific problem, as I commented: You must have accidentally imported the javax.inject.Qualifier annotation, which will not work. You need to import the org.springframework.beans.factory.annotation.Qualifier which works
Back-end, Spring boot project(v1.3.0.RELEASE), supply Rest JSON Api to fron-end, just now encountered an error:
Infinite recursion (StackOverflowError)
I decide to change to a custom FastJsonHttpMessageConverter, and code is below
#Bean
public HttpMessageConverter httpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
return fastJsonHttpMessageConverter;
}
but it does not work, in real it uses a default HttpMessageConverter. Although does not have above error, the output is not as I expected. e.g.
suppliers: [
{
$ref: "$.value"
}
]
Now change above code
#Bean
public HttpMessageConverter mappingJackson2HttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
return fastJsonHttpMessageConverter;
}
This time it works, I want to know why the method name have to be mappingJackson2HttpMessageConverter? If use another method name how to configure it?
After seeing this offical document, I know how to customize converters.
#Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new FastJsonHttpMessageConverter();
return new HttpMessageConverters(additional);
}
A Revise to my main post, actually below code does not work.
#Bean
public HttpMessageConverter mappingJackson2HttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
return fastJsonHttpMessageConverter;
}
Spring boot never enter this method if you set breakpoint inside it.
And below code also works.
#SpringBootApplication
public class FooApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(FooApplication.class, args);
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(new FastJsonHttpMessageConverter());
}
}
Spring boot says (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-responsebody-rendering):
If a bean you add is of a type that would have been included by default anyway (like MappingJackson2HttpMessageConverter for JSON conversions) then it will replace the default value.
The bean you are adding is not of the same type, so the above does not happen. Your converter goes somewhere in the list of converters (probably the end), and the first suitable converter (the old one) does the job.
Beans produced by the Java configuration have the name of the method, so when you create a second bean named mappingJackson2HttpMessageConverter, it overrides the one created by spring boot's JacksonHttpMessageConvertersConfiguration and takes it place.
Instead of adding a converter bean, you might prefer to override the whole list of converters:
As in normal MVC usage, any WebMvcConfigurerAdapter beans that you provide can also contribute converters by overriding the configureMessageConverters method,
I am very new to LocalContexHandler. I have read about it. From what I understood it is used to retrieve locale information in a java application.
I am passing the locale from the url as follows:
url?lang = fr
When I am trying to retrieve the locale in java application as follows:
Locale locale = LocaleContextHolder.getLocale();
It is giving null value. Can someone help me understand it's usage or is there any other alternative for the same?
If you want to pass locale in URL you must register LocaleChangeInterceptor interceptor and create LocaleResolver bean. For example SessionLocaleResolver stores chosen locale in session. Then LocaleContextHolder#getLocale will return not-null value. Take a look in Spring documentation to section Using locales. Below you can see basic Java configuration for you example.
If you want just use LocaleContextHolder you have to call LocaleContextHolder#setLocale from your code before calling LocaleContextHolder#getLocale. It's just simple holder class that stores LocaleContext in ThreadLocal variable.
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// Rest of Web MVC configuration omitted
#Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(localeChangeInterceptor());
}
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale("fr");
return sessionLocaleResolver;
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
}