I'm trying to implement RFC 7807 in my Spring Boot project using zalando problem-spring-web https://github.com/zalando/problem-spring-web
I've done the setup according to this guide https://github.com/zalando/problem-spring-web/tree/master/problem-spring-web
When an exception is thrown, the Problem instance does get generated, but its serialized JSON form is not as expected, most notably the stack trace is included when it shouldn't.
After some debugging, it seems that the ProblemModule is not registered in the ObjectMapper that is used to serialize the problem (its setupModule method is never called). I was under the impression that declaring a bean of type Module was enough to have it picked up by Spring and registered in the ObjectMapper, but it doesn't happen here.
The doc says
In case you want to enable stack traces, please configure your
ProblemModule as follows:
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ProblemModule().withStackTraces());
which seems to imply that you need to instantiate your own ObjectMapper, but then how to make sure that it's used by the library when deserializing the Problem?
Since I can't get the ObjectMapper to register my Modules I figured I had to do it myself so I came up with this solution that seems to work:
#Configuration
public class ProblemConfiguration implements InitializingBean {
#Autowired
ObjectMapper objectMapper;
#Override
public void afterPropertiesSet() {
objectMapper.registerModules(
new ProblemModule(),
new ConstraintViolationProblemModule()
);
}
}
If someone has an idea why it's not working as expected, I'd be glad to hear it :)
Since Spring boot 1.1.0, the JacksonAutoConfiguration creates an ObjectMapper bean and automatically registers all module found in your registered beans,
so you juste need to create does two beans modules and use de already configured ObjectMapper like this:
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
#Bean
public ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
#Bean
public ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
use configured ObjectMapper for ex in service Classe
#Autowired
ObjectMapper jacksonObjectMapper
Related
We have a Spring-based Java library. There is a code like this:
#Configuration
public class MyConfig {
#Bean
public MyClass createMyClass(#Autowired ObjectMapper objectMapper) {
...
}
}
The code is supposed to be used only as a library, as part of a full Spring Boot application. In that application, there is ensured that an ObjectMapper bean always exists.
Maven build ends up with BUILD SUCCESS. However, IntelliJ IDEA complains:
Could not autowire. No beans of 'ObjectMapper' type found.
What is the correct way of solving the issue?
Is there an annotation parameter something like #Autowired(provided=true) telling the IntelliJ that the bean will be provided in the full program?
Is it OK just to ignore / suppress (and how?) this IntelliJ error, secretly knowing that it will be eventually OK?
Or is it a bad practice and such code should be avoided and replaced by another construct?
EDIT:
As a solution I tried to add to the library a "fake" bean like this:
#Bean
#OnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
This made IDEA happy, however as Spring Boot creates the bean also with #OnMissingBean annotation (or maybe it wasn't ObjectMapper but some other class, it doesn't matter now), I never knew which bean takes precedence and it happened that this fake bean was used.
Is there a way to create two instances of ObjectMapper for different purpose.
Modified ObjectMapper
#Component
class MyObjectMapper extends ObjectMapper{
public MyObjectMapper(){
super();
}
public MyObjectMapper(MyObjectMapper om) {
super(om);
}
#Override
public MyObjectMapper copy() {
return new MyObjectMapper(this);
}
}
Now use it as follows
#Autowired ObjectMapper objectMapper; //performs standard serialization
#Autowire MyObjectMapper myMapper; //i can add custom filters in thiis mapper.
I tried a similar setup but the custom mapper actually affects the original mapper all the rest controllers throw JSON parse error: Unrecognized field
Edit: Not sure if this point is very important but still adding it
Am using spring-boot-starter-json
And that's exactly where you should use #Qualifier annotation.
This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring. It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.
OK. Combining with Answer from Aniket figured out what is wrong and still looking for some more explanation.
Instead of instantiating the ObjectMapper as new ObjectMapper(). Building it with Mapper fixed it.
So, two have multiple instance of ObjectMapper
#Primary
#Bean
public ObjectMapper objectMapper(){
return new Jackson2ObjectMapperBuilder()
.build();
}
#Bean("customMapper")
public ObjectMapper customMapper(){
ObjectMapper customMapper = new Jackson2ObjectMapperBuilder().build();
mapper.<your customization , filters, providers etc;>
return mapper;
}
The #Primary will be used by in all default cases i.e When you simply #Autowire or the default serialization applied to your Response/Request Body by your controller.
To use your Custom Mapper, explicitly use with the Bean ID.
#Autowired #Qualifier("customMapper") ObjectMapper mapper;
I'm trying to create a REST service that is able to produce XML output (I have a custom class that is wrapped inside a HATEOAS object). Mapping is like this:
#GetMapping("/customclass")
Resource<CustomClass> custom() {
return new Resource<CustomClass>(new CustomClass());
}
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not marshal [Resource { content: CustomClass(a=10, string=abc), links: [] }]: null; nested exception is javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.internal.SAXException2: class test.CustomClass nor any of its super class is known to this context.
javax.xml.bind.JAXBException: class test.CustomClass nor any of its super class is known to this context.]]
I'm pretty sure that there is nothing wrong with my CustomClass. If I use the following mapping instead
#GetMapping("/customclass")
CustomClass custom() {
return (new CustomClass());
}
then it works fine.
It also works fine if I try to marshal things manually (by settings things up inside of a main method and then running it). It's also fine then if I wrap the instance of CustomClass inside of a Resource instance.
As far I understand the issue is that the marshaller in SpringApplication is using context that just knows about HATEOAS Resource and I need to some how make it aware of CustomClass.
I tried to use something like this (from https://stackoverflow.com/a/40398632)
#Configuration
public class ResponseResolver {
#Bean
public Marshaller marshaller() {
try {
System.out.println("getting marshaller");
JAXBContext context = JAXBContext.newInstance(CustomClass.class, Resource.class);
return context.createMarshaller();
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
but that didn't work (there was a lot of guessing on my part here, since I don't know that much about the inner workings of Spring Boot).
A promising reply was also in https://stackoverflow.com/a/14073899 , but ContextResolver wasn't in my projects classpath.
I also considered wrapping Resource inside of a another class and then using XmlSeeAlso annotation, but that would mess up my XML and would be somewhat ugly hack.
So is it possible to define a custom JAXBContext that SpringApplication would be able to pick up?
From the Spring Boot Documentation
Spring Message message converters
Spring MVC uses the HttpMessageConverter interface to convert HTTP
requests and responses. Sensible defaults are included out of the box.
For example, objects can be automatically converted to JSON (by using
the Jackson library) or XML (by using the Jackson XML extension, if
available, or by using JAXB if the Jackson XML extension is not
available). By default, Jaxb2RootElementHttpMessageConverter –
converts Java objects to/from XML (added only if JAXB2 is present on
the classpath)
Custom Converters Configuration
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
messageConverters.add(createXmlHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
}
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
MarshallingHttpMessageConverter xmlConverter =
new MarshallingHttpMessageConverter();
XStreamMarshaller xstreamMarshaller = new XStreamMarshaller();
xmlConverter.setMarshaller(xstreamMarshaller);
xmlConverter.setUnmarshaller(xstreamMarshaller);
return xmlConverter;
}
}
I have a multi-module Maven-based Spring Boot Application. In one of my modules' application.properties file, I am setting Jackson to not serialize dates as timestamps (arrays), so that they are always serialized as strings (which is the fallback behavior). The property is this:
spring.jackson.serialization.write_dates_as_timestamps=false
Problem is... the property doesn't seem to be picked up by either Spring Boot/Jackson. The java.time.LocalDate instances I am trying to serialize always get written as timestamps. I have checked the code inside the LocalDateSerializer.serialize(...) method (from Jackson's own JavaTimeModule), and found this:
#Override
public void serialize(LocalDate date, JsonGenerator generator, SerializerProvider provider) throws IOException
{
if (useTimestamp(provider)) { // This always returns true
generator.writeStartArray();
generator.writeNumber(date.getYear());
generator.writeNumber(date.getMonthValue());
generator.writeNumber(date.getDayOfMonth());
generator.writeEndArray();
} else {
String str = (_formatter == null) ? date.toString() : date.format(_formatter);
generator.writeString(str);
}
}
The useTimestamp() method always returns true, no matter what my configuration is. :(
Maybe you can create a custom MappingJackson2HttpMessageConverter and an ObjectMapper. Here is an example configuration class,
#Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter
getMappingJacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
...
ObjectMapper mapper = new ObjectMapper();
...
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//or
// mapper.configure(
// SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(mapper);
return converter;
}
}
Thanks to Indra's answer, I got an important clue that helped me figure out the issue.
Turns out that my Spring Boot application has multiple instances of RestTemplate in its application context (it is a pretty big system and relies upon some custom proprietary frameworks to work). Most of these RestTemplate instances were there just to support my system's architecture. There is only one RestTemplate instance that I should've cared about: The one that I wrote and allows my business logic to work (by enabling my system to consume remote RESTful APIs).
So, when I was trying to configure Jackson and its ObjectMapper, I wasn't really affecting my RestTemplate instance. By the time my configuration was read, that RestTemplate had long ago been initialized with the default settings provided by Spring Boot.
This is how my final configuration class looks:
#Configuration
public class RestConfiguration {
#Bean
public RestTemplate myRestTemplate(HttpMessageConverters customConverters) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(customConverters.getConverters());
return restTemplate;
}
/*
* The following custom HttpMessageConverter is injected into myRestTemplate
* in order to appropriately configure the serialization/deserialization of LocalDate
* instances into/from strings.
*/
#Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(mapper);
return new HttpMessageConverters(converter);
}
}
What I am doing is to explicitly configure the RestTemplate instance that needed to be configured.
The following documentation was also very helpful: HttpMessageConverters. It describes how to declare custom HttpMessageConverters for use in the application context.
I'm using Spring 4 and was following the Rossen Stoyanchev's blog post about using websockets in Spring. I was able to get everything working but I'm not sure what the best way to use a custom object mapper when sending application/json.
I'm injecting a SimpMessageSendingOperations and calling convertAndSend. I'm not positive but I'm pretty sure I'm getting a SimpMessagingTemplate (it implements SimpMessageSendingOperations) which contains a setMessageConverter. This method takes a MessageConverter and there is a MappingJackson2MessageConverter class but of course it uses it's own internal ObjectMapper which cannot be redefined.
So what it looks like I have to do is create a custom MessageConverter and define my custom ObjectMapper within it so I can pass it to an instance of SimpMessagingTemplate that I can then inject into my classes.
This seems like it would work, but also more involved than I expected. Am I overlooking something?
Looks like it is possible, but will be made easier in Spring 4.0.1
See - https://jira.springsource.org/browse/SPR-11184
Quote from the bug report above.
In the mean time, with #EnableWebSocketMessageBroker setup you can:
remove the annotation
extend WebSocketMessageBrokerConfigurationSupport instead of implementing WebSocketMessageBrokerConfigurer
override brokerMessageConverter() method and remember to keep #Bean in the overriding method
Nowadays you can do it like this:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
// Avoid creating many ObjectMappers which have the same configuration.
converter.setObjectMapper(getMyCustomObjectMapper());
messageConverters.add(converter);
// Don't add default converters.
return false;
}
...
}
Unfortunately ObjectMapper cannot be given directly to MappingJackson2MessageConverter's constructor, meaning it will first create a useless ObjectMapper.