Spring boot MVC does not use the specified custom StringHttpMessageConverter - java

In the class responsible for app config is this code:
#Bean
public HttpMessageConverter<String> createStringHttpMessageConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter
(Charset.forName("UTF-8"));
return converter;
}
I checked in debugger that it is actually executed and also tried alternative with StringHttpMessageConverter as the return type.
However when I debug WebMvcConfig.extendMessageConverters() I see that the StringHttpMessageConverter with the default charset is used instead of my converter with UTF-8 charset.
Why does not Spring Boot use the specified StringHttpMessageConverter ?
I know the workaround might be to change the converters list according to my needs in WebMvcConfig.extendMessageConverters() but I would like to do it the right way

With Spring Boot try to register array of HttpMessageConverters:
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
#Configuration
public class MyConfiguration {
#Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);
}
}
Or if you are not using Spring Boot's auto-configuration, you can use standard Spring WebMvcConfigurer for registering converters:
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new ParameterNamesModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
}
}

You need 2 steps to support utf-8 StringHttpMessageConverter;
First add StringHttpMessageConverter;Second specify charset in produces,code is below;
#RestController
#RequestMapping(value = "/plain")
public class PlainController {
#RequestMapping(value = "/test", method = RequestMethod.GET,
produces = {"text/plain;charset=UTF-8"})
public Response<String> test() {
return new Response<String>("success");
}
}
#Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastConverter;
StringHttpMessageConverter stringHttpMessageConverter =
new StringHttpMessageConverter(Charset.forName("UTF-8"));
return new HttpMessageConverters(converter, stringHttpMessageConverter);
}

Related

Injection Dependency of spring boot filter usage

I'm practicing spring boot API access token project and here is my question.
Here is my filter file and I'm trying to extends OncePerRequestFilter and inject three arg in to the filter using #RequiredArgsConstructor annotation.
#Component
#RequiredArgsConstructor
public class LogInFilter extends OncePerRequestFilter {
private final OAuthService oAuthService;
private final AccessTokenService accessTokenService;
private final ObjectMapper mapper;
Here is the #Bean
#Configuration
public class ApplicationConfig {
#Bean
public FilterRegistrationBean logInFilter() {
ObjectMapper mapper = new ObjectMapper();
FilterRegistrationBean bean = new FilterRegistrationBean(new LogInFilter());
bean.addUrlPatterns("/test/api");
bean.setOrder(Integer.MIN_VALUE);
return bean;
}
and I have no idea how to put this three arg as constructor in to new LogInFilter() above
Do this
#Bean
public FilterRegistrationBean logInFilterRegistration(LogInFilter logInFilter) {
FilterRegistrationBean bean = new FilterRegistrationBean(logInFiliter);
bean.addUrlPatterns("/test/api");
bean.setOrder(Integer.MIN_VALUE);
return bean;
}
Your filter is annotated with #Component so Spring will automatically create an instance, which you can use like this.

Retrieve locale based on the Accept-Language in Spring Boot

I have a Spring Boot (2.1.3.RELEASE) application that uses Jersey to define the (RESTful) endpoints. I'm trying to read and propagate some messages based on the locale being sent by the user-agents.
I've configured these beans:
#Bean
public LocaleResolver localeResolver() {
final AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setSupportedLocales(Arrays.asList(Locale.GERMANY, Locale.US));
resolver.setDefaultLocale(Locale.ENGLISH);
return resolver;
}
#Bean
public MessageSource messageSource() { // Not sure if this is needed
final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setCacheSeconds(5);
return messageSource;
}
...and also the bundles (inside ../src/main/resources/) like: messages.properties (fallback), messages_en_US.properties, messages_de_DE.properties, etc.
Now, the challenge is that I'm not sure how to "read" the locale sent by the user-agents in order to read the messages from the bundles appropriately. I'm injecting a MessageSource ms, and programmatically reading messages like:
final Locale locale = ???
ms.getMessage("message.duplicate-token", null, locale);
Any clues?
I've tried LocaleContextHolder.getLocale() but it's always en_US. If I hardcode the corresponding locale for the getMessage call, I'm able to retrieve the correct message(s). So I know the setup/configuration works for the most part.
Clients are sending the locale using the Accept-Language header — and values like: de-DE, en-US, etc.
You need add an LocaleChangeInterceptor and configure the bean as follow:
Refer Spring Boot internationalization for more
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
    LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
    lci.setParamName("lang");
    return lci;
}
If you want to use "Accept-Language" header only, then you can extend AcceptHeaderLocaleResolver and can customize:
package com.deb.demo.config;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
public class CustomLocaleResolver extends AcceptHeaderLocaleResolver {
List<Locale> LOCALES = Arrays.asList(new Locale("en"),new Locale("es"),new Locale("fr"));
#Override
public Locale resolveLocale(HttpServletRequest request) {
if (StringUtils.isEmpty(request.getHeader("Accept-Language"))) {
return Locale.getDefault();
}
List<Locale.LanguageRange> list = Locale.LanguageRange.parse(request.getHeader("Accept-Language"));
Locale locale = Locale.lookup(list,LOCALES);
return locale;
}
}
I m using a bean
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver slr = new AcceptHeaderLocaleResolver();
slr.setDefaultLocale(Locale.UK);
return slr;
}
then another one
#Bean
public LanguageUtil languageUtil() {
return new LanguageUtil();
}
with
private Locale getLocale() {
return LocaleContextHolder.getLocale();
}
public String getLocalizedMessage(String messageKey) {
return messageSource.getMessage(messageKey, null, getLocale());
}
The header is saved into the LocaleContextHolder, and you can use it when you need it.
Create a custom AcceptHeaderLocaleResolver
public class AcceptHeaderResolver extends AcceptHeaderLocaleResolver {
List<Locale> LOCALES = Arrays.asList(new Locale("en"), new Locale("ar"));
#Override
public Locale resolveLocale(HttpServletRequest request) {
String headerLang = request.getHeader("Accept-Language");
return headerLang == null || headerLang.isEmpty()
? Locale.getDefault()
: Locale.lookup(Locale.LanguageRange.parse(headerLang), LOCALES);
}
}
And Don't forgot to use it in #Configuration file
#Bean
public LocaleResolver sessionLocaleResolver() {
AcceptHeaderResolver localeResolver = new AcceptHeaderResolver();
return localeResolver;
}
If you want to get this on a REST controller level, you can directly get the locale instance in the REST methods. Spring does this magic
#GetMapping("/status")
public ResponseEntitiy<String> getStatus(final Locale locale) {
}
The LocaleResolver bean that you create only gets used in Spring MVC and not in the Jersey container. It is the Spring's DispatcherServlet that uses the LocaleResolver.
So LocaleContextHolder.getLocale(); will return different local depending on if call in a Jersey controller or in a Spring MVC controller.
There is no need to extend AcceptHeaderLocaleResolver.
Create a bean definition such as:
#Bean
public LocalResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setSupportedLocales(Arrays.asList(new Locale("fa"), new Locale("en")));
localeResolver.setDefaultLocale(new Locale("fa"));
return localeResolver;
}
I also implemented same scenario and it's works for me. For this, need to override the resolveLocale method in AcceptHeaderLocaleResolver.
Create component LanguageResolver for the custom implementation. Use Locale.forLanguageTag(language) to create locale from accept-header value. This will create a local with language and country code.
#Component
public class LanguageResolver extends AcceptHeaderLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getHeader("Accept-Language");
List<Locale> supportedLocales = getSupportedLocales();
Locale defaultLocale = getDefaultLocale();
Locale requestLocale = Locale.forLanguageTag(language);
if (StringUtils.isEmpty(language)) {
return defaultLocale;
} else if (supportedLocales.contains(requestLocale)) {
return requestLocale;
} else {
return defaultLocale;
}
}
}
In the configuration class create bean using custom LanguageResolver class.
#Configuration
public class Internationalization extends WebMvcConfigurerAdapter {
#Bean
public AcceptHeaderLocaleResolver localeResolver() {
final LanguageResolver resolver = new LanguageResolver();
resolver.setSupportedLocales(Arrays.asList(Locale.GERMANY, Locale.US,Locale.UK));
resolver.setDefaultLocale(Locale.US);
return resolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
final ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("language/messages");
source.setDefaultEncoding("UTF-8");
return source;
}
}
Here LocaleContextHolder.getLocale() will invoke the override method in LanguageResolver class.
#Service
public class LocaleService {
#Autowired
ResourceBundleMessageSource messageSource;
public String getMessage(String code) {
return messageSource.getMessage(code, null, LocaleContextHolder.getLocale());
}
}
And property files are in path of resources -> language
messages_en_US.properties
messages_en_GB.properties
messages_de_DE.properties
Content of the file in the format of below
test.hello=Hello GERMANY
Tested method using below.
#RestController
public class TestController {
private LocaleService localeService;
#Autowired
public TestController(LocaleService localeService) {
this.localeService = localeService;
}
#GetMapping("/local")
public String getMessageForLocal() {
return localeService.getMessage("test.hello");
}
}
You have to add Accept-Language header in your api endpoint to get desired locale output. Then you have to add following configuration to parse and set the Accept-Language header value from incoming request.
#Configuration
public class I18NConfiguration {
#Value("${i18n.locale.default:en-US}")
private String defaultLocale;
#Value("#{'${i18n.locale.supported: }'.split(',\\s*')}")
private List<String> supportedLocales;
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
acceptHeaderLocaleResolver.setDefaultLocale(Locale.forLanguageTag(defaultLocale));
if (supportedLocales != null && !supportedLocales.isEmpty()) {
List<Locale> localeList = supportedLocales.stream().map(Locale::forLanguageTag).collect(Collectors.toUnmodifiableList());
acceptHeaderLocaleResolver.setSupportedLocales(localeList);
}
return acceptHeaderLocaleResolver;
}
}

How does spring boot autoconfigure works if the class is not present in class path

I was look at the Kafka Auto configure in spring-boot.
Reading the artile Spring-boot auto-configure
The below is the code copied from github Here.
My question is if the jar containing KafkaTemplate.class not present in class path, how does the spring is manage to execute the below code while auto configuring. Since the class is not present should not it lead to Error? or does the spring won't execute this at all while auto-configuring?
#Configuration
#ConditionalOnClass(KafkaTemplate.class)
#EnableConfigurationProperties(KafkaProperties.class)
#Import({ KafkaAnnotationDrivenConfiguration.class,
KafkaStreamsAnnotationDrivenConfiguration.class })
public class KafkaAutoConfiguration {
private final KafkaProperties properties;
private final RecordMessageConverter messageConverter;
public KafkaAutoConfiguration(KafkaProperties properties,
ObjectProvider<RecordMessageConverter> messageConverter) {
this.properties = properties;
this.messageConverter = messageConverter.getIfUnique();
}
#Bean
#ConditionalOnMissingBean(KafkaTemplate.class)
public KafkaTemplate<?, ?> kafkaTemplate(
ProducerFactory<Object, Object> kafkaProducerFactory,
ProducerListener<Object, Object> kafkaProducerListener) {
KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate<>(
kafkaProducerFactory);
if (this.messageConverter != null) {
kafkaTemplate.setMessageConverter(this.messageConverter);
}
kafkaTemplate.setProducerListener(kafkaProducerListener);
kafkaTemplate.setDefaultTopic(this.properties.getTemplate().getDefaultTopic());
return kafkaTemplate;
}
#Bean
#ConditionalOnMissingBean(ProducerListener.class)
public ProducerListener<Object, Object> kafkaProducerListener() {
return new LoggingProducerListener<>();
}
#Bean
#ConditionalOnMissingBean(ConsumerFactory.class)
public ConsumerFactory<?, ?> kafkaConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(
this.properties.buildConsumerProperties());
}
#Bean
#ConditionalOnMissingBean(ProducerFactory.class)
public ProducerFactory<?, ?> kafkaProducerFactory() {
DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory<>(
this.properties.buildProducerProperties());
String transactionIdPrefix = this.properties.getProducer()
.getTransactionIdPrefix();
if (transactionIdPrefix != null) {
factory.setTransactionIdPrefix(transactionIdPrefix);
}
return factory;
}
#Bean
#ConditionalOnProperty(name = "spring.kafka.producer.transaction-id-prefix")
#ConditionalOnMissingBean
public KafkaTransactionManager<?, ?> kafkaTransactionManager(
ProducerFactory<?, ?> producerFactory) {
return new KafkaTransactionManager<>(producerFactory);
}
#Bean
#ConditionalOnProperty(name = "spring.kafka.jaas.enabled")
#ConditionalOnMissingBean
public KafkaJaasLoginModuleInitializer kafkaJaasInitializer() throws IOException {
KafkaJaasLoginModuleInitializer jaas = new KafkaJaasLoginModuleInitializer();
Jaas jaasProperties = this.properties.getJaas();
if (jaasProperties.getControlFlag() != null) {
jaas.setControlFlag(jaasProperties.getControlFlag());
}
if (jaasProperties.getLoginModule() != null) {
jaas.setLoginModule(jaasProperties.getLoginModule());
}
jaas.setOptions(jaasProperties.getOptions());
return jaas;
}
#Bean
#ConditionalOnMissingBean
public KafkaAdmin kafkaAdmin() {
KafkaAdmin kafkaAdmin = new KafkaAdmin(this.properties.buildAdminProperties());
kafkaAdmin.setFatalIfBrokerNotAvailable(this.properties.getAdmin().isFailFast());
return kafkaAdmin;
}
}
Thanks for help
Referring from spring boot documentation:
The #ConditionalOnClass and #ConditionalOnMissingClass annotations let #Configuration classes be included based on the presence or absence of specific classes. Due to the fact that annotation metadata is parsed by using ASM, you can use the value attribute to refer to the real class, even though that class might not actually appear on the running application classpath. You can also use the name attribute if you prefer to specify the class name by using a String value.
This mechanism does not apply the same way to #Bean methods where typically the return type is the target of the condition: before the condition on the method applies, the JVM will have loaded the class and potentially processed method references which will fail if the class is not present
To answer your question, spring doesn't even include KafkaAutoConfiguration class.

Why doesn't #Getmapping work in some instances?

In my controller, the following use of #GetMapping works:
#GetMapping(value = "/new")
public String newEssay(){
return "articles/essay_new";
}
But it doesn't work like this:
#GetMapping(value = "/essays/{essayId: [0-9]+}")
//#RequestMapping(value = "/essays/{essayId:[0-9]+}", method = RequestMethod.GET)
public String getEssay(Model model,
#PathVariable("essayId") long essayId) throws NoFindException, ForBiddenException, ParseException {
JsEssay jsEssay = jsBiz.get(JsEssay.class, essayId);
model.addAttribute("jsEssay", jsEssay);
return "articles/essay";
}
I tried it with Spring 4.3.3 and 5.0.0.M5.
Config:
#Configuration
#ComponentScan( basePackages = {"me.freezehome.blog"},
excludeFilters = {
#ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
}
)
public class RootConfig {
}
#Configuration
#EnableWebMvc
#Import({WebSecurityConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter{
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
Google results:
Add support for #GetMapping, #PostMapping etc. introduced in Spring 4.3 in ControllerLinkBuilder #471
GetMapping and PostMapping annotations Ask
github source: lbfreeze-blog-develop
Please remove the space after essayId:
Also, you don't need to write value = for #GetMapping.

Combining Spring MVC 3.2 with Coda Hales metrics

i am trying to combine a Spring Web Application (completed Annotation Based configuration, no xml configuration) with metrics 3.0.
I am running the application inside a jetty.
This is my current configuration for the default DispatcherServlet:
public class WebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[] { characterEncodingFilter };
}
}
This is the WebConfig:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.rebuy.silo.amqpredelivery")
#EnableJpaRepositories(basePackages = "com.rebuy.silo.amqpredelivery.domain")
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(objectMapper());
converters.add(jacksonConverter);
super.configureMessageConverters(converters);
}
#Bean
public ObjectMapper objectMapper() {
SimpleDateFormat format = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssXXX");
format.setTimeZone(TimeZone.getTimeZone("GMT+1"));
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(format);
return mapper;
}
}
I want to add these two Servlets:
https://github.com/codahale/metrics/blob/master/metrics-servlets/src/main/java/com/codahale/metrics/servlets/HealthCheckServlet.java
https://github.com/codahale/metrics/blob/master/metrics-servlets/src/main/java/com/codahale/metrics/servlets/MetricsServlet.java
What is the best way to do this? I think there should be some spring magic to make this extremly easy to do! But I was not able to find it :(
Thanks in advance
Björn
You can follow this codebase https://github.com/spiritedtechie/metrics-examples.
Or use this library called metrics-spring http://ryantenney.github.io/metrics-spring/
If you are using Spring and Metrics you should also be using #RyanTenney's Metrics-Spring module. It will simplify your Java config and make your Metrics usage much cleaner.
Take a look at the code behind the MetricsServlet and HealthCheckServlet. In my opinion its easier to just write your own Spring Controller to do the same thing than to figure out how to embed and wrap those old servlets.
Its easy!
Create a metrics specific config:
#Configuration
#EnableMetrics
public class MetricsConfig extends MetricsConfigurerAdapter {
#Override
public void configureReporters(MetricRegistry metricRegistry) {
registerReporter(ConsoleReporter
.forRegistry(metricRegistry)
.build()).start(5, TimeUnit.MINUTES);
}
}
And include it from your existing config by adding:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.rebuy.silo.amqpredelivery")
#EnableJpaRepositories(basePackages = "com.rebuy.silo.amqpredelivery.domain")
#EnableAspectJAutoProxy
#EnableTransactionManagement
#Import({MetricsConfig.class})
public class WebConfig extends WebMvcConfigurerAdapter {
...
The above config changes make it trivial to inject a MetricRegistry in any Spring component. All the MetricsServlet does is send the registry in response to the request. That is really easy to accomplish in a simple controller. For example:
#Controller
public class AdminMetricsController
{
#Autowired
MetricRegistry metricRegistry;
#RequestMapping(value = "/admin/metrics/", produces = {APPLICATION_JSON_VALUE})
public #ResponseBody MetricRegistry getMetrics(final HttpServletResponse response)
{
response.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
return metricRegistry;
}
}
A HealthCheckRegistry can be injected in a similar way and another method added which would respond to /admin/health/ or whatever url you wanted.
Take a look at the following answer. It explains how to register a Servlet via JavaConfig:
Spring JavaConfig: Add mapping for custom Servlet

Categories

Resources