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;
}
}
Related
I am using a Jackson for converting a json configuration into an object that represents a Spring Batch's job (list of steps to execute). Each step is either a Tasklet, or Chunk Step (reader - processor - writer).
private ObjectMapper objectMapper;
public JobDefinition convert(String jobConfiguration) {
try {
return objectMapper.reader()
.forType(JobDefinition.class)
.readValue(jobConfiguration);
} catch (IOException exception) {
throw new RuntimeException("Error while building a JobDefinition!", exception);
}
}
What I need to do, is to deserialize a JSON configuration, but during the deserialization I need to autowire all of the dependencies from the Spring's context e.g.:
public class CustomProcessor implements ItemProcessor<Row, Row> {
#JsonProperty("someField")
private String attributeFromJson;
private SomeDependency someDependencyFromSpringContext;
#Override
public Row process(Row row) throws Exception {
// business logic
return row;
}
}
The String value is taken from the provided JSON configuration, but the SomeDependency should be taken from the Spring's context. It also needs to properly handle the Spring's Scopes, so for scope=prototype I should always give me a new instance of SomeDependency.class, but for singleton - it will always be the same object.
Is it possible to achieve that in Jackson in a simple way?
In the past I was trying to use a Jackson's InjectableValues and simply copy all of the dependencies from the ApplicationContext into the ObjectMapper and use #JacksonInject. However the InjectableValues have been fully initialized immediately the app started, so it always gave me the same object's references and didn't work properly with the #JobScope, #StepScope or simply with #Scope(BeanDefinition.SCOPE_PROTOTYPE).
I've noticed that there is also something called SpringHandlerInstantiator that can be passed to ObjectMapper, and it gives a direct access into the ApplicationContext, but somehow it doesn't work properly as dependencies are not autowired at all.
Is there anything else I can do to solve that?
Thanks in advance for all of the tips!
I have created this class to add GSON to the spring-boot classpath. The problem, I cannot view any Html/text, or images content since there is not a valid converter.
I am relatively new to Spring-Boot.
I would like to to add Gson instead of Jackson to spring-boot and also be able to brow the web app using chrome.
Here is my class:
#Configuration
#EnableWebMvc
public class GsonSpringBootConfigurer implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(SyncController.class);
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for(HttpMessageConverter httpMessageConverter : converters){
log.info(httpMessageConverter.toString());
}
converters.add(createGsonHttpMessageConverter());
}
private GsonHttpMessageConverter createGsonHttpMessageConverter() {
Gson gson = JsonUtils.getGsonBuilder();
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
}
Note the Javadoc of configureMessageConverters states
Configure the HttpMessageConverters to use for reading or writing to
the body of the request or response. If no converters are added, a
default list of converters is registered.
Note that adding converters to the list, turns off default converter
registration. To simply add a converter without impacting default
registration, consider using the method
extendMessageConverters(java.util.List) instead.
In other words, you've removed all the converters that handle other content types.
Note that Spring MVC only registers the Jackson HttpMessageConverter (MappingJackson2HttpMessageConverter) if the corresponding Jackson libraries are in your classpath. You could remove them and, assuming you have Gson in your classpath, a GsonHttpMessageConverter will be registered for you.
From your code, it seems you want to create a custom GsonHttpMessageConverter. In that case, you can follow the Javadoc instructions and use extendMessageConverters.
A hook for extending or modifying the list of converters after it has
been configured. This may be useful for example to allow default
converters to be registered and then insert a custom converter through
this method.
You'd first want to remove the existing instance, then add your own. For example,
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// remove the Jackson version if Jackson is still in your classpath
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
// remove the existing instance (from defaults)
converters.removeIf(converter -> converter instanceof GsonHttpMessageConverter);
// add your custom
converters.add(createGsonHttpMessageConverter());
}
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
I writing application using spring-boot-starter-jdbc (v1.3.0).
The problem that I met: Instance of BeanPropertyRowMapper fails as it cannot convert from java.sql.Timestamp to java.time.LocalDateTime.
In order to copy this problem, I implemented
org.springframework.core.convert.converter.Converter for these types.
public class TimeStampToLocalDateTimeConverter implements Converter<Timestamp, LocalDateTime> {
#Override
public LocalDateTime convert(Timestamp s) {
return s.toLocalDateTime();
}
}
My question is: How do I make available TimeStampToLocalDateTimeConverter for BeanPropertyRowMapper.
More general question, how do I register my converters, in order to make them available system wide?
The following code bring us to NullPointerException on initialization stage:
private Set<Converter> getConverters() {
Set<Converter> converters = new HashSet<Converter>();
converters.add(new TimeStampToLocalDateTimeConverter());
converters.add(new LocalDateTimeToTimestampConverter());
return converters;
}
#Bean(name="conversionService")
public ConversionService getConversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(getConverters());
bean.afterPropertiesSet();
return bean.getObject();
}
Thank you.
All custom conversion service has to be registered with the FormatterRegistry. Try creating a new configuration and register the conversion service by implementing the WebMvcConfigurer
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new TimeStampToLocalDateTimeConverter());
}
}
Hope this works.
I'll copy my answer from https://stackoverflow.com/a/72781591/140707 since I think the two questions are similar (so the answer applies to both).
Existing answers didn't work for me:
Customizing via WebMvcConfigurerAdapter.addFormatters (or simply annotating the converter with #Component) only works in the WebMvc context and I want my custom converter to be available everywhere, including #Value injections on any bean.
Defining a ConversionService bean (via ConversionServiceFactoryBean #Bean or #Component) causes Spring Boot to replace the default ApplicationConversionService on the SpringApplication bean factory with the custom bean you've defined, which will probably be based on DefaultConversionService (in AbstractApplicationContext.finishBeanFactoryInitialization). The problem is that Spring Boot adds some handy converters such as StringToDurationConverter to the standard set in DefaultConversionService, so by replacing it you lose those conversions. This may not be an issue for you if you don't use them, but it means that solution won't work for everyone.
I created the following #Configuration class which did the trick for me. It basically adds custom converters to the ConversionService instance used by Environment (which is then passed on to BeanFactory). This maintains as much backwards compatibility as possible while still adding your custom converter into the conversion services in use.
#Configuration
public class ConversionServiceConfiguration {
#Autowired
private ConfigurableEnvironment environment;
#PostConstruct
public void addCustomConverters() {
ConfigurableConversionService conversionService = environment.getConversionService();
conversionService.addConverter(new MyCustomConverter());
}
}
Obviously you can autowire a list of custom converters into this configuration class and loop over them to add them to the conversion service instead of the hard-coded way of doing it above, if you want the process to be more automatic.
To make sure this configuration class gets run before any beans are instantiated that might require the converter to have been added to the ConversionService, add it as a primary source in your spring application's run() call:
#SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(new Class<?>[] { MySpringBootApplication.class, ConversionServiceConfiguration.class }, args);
}
}
If you don't do this, it might work, or not, depending on the order in which your classes end up in the Spring Boot JAR, which determines the order in which they are scanned. (I found this out the hard way: it worked when compiling locally with an Oracle JDK, but not on our CI server which was using a Azul Zulu JDK.)
Note that for this to work in #WebMvcTests, I had to also combine this configuration class along with my Spring Boot application class into a #ContextConfiguration:
#WebMvcTest(controllers = MyController.class)
#ContextConfiguration(classes = { MySpringBootApplication.class, ConversionServiceConfiguration.class })
#TestPropertySource(properties = { /* ... properties to inject into beans, possibly using your custom converter ... */ })
class MyControllerTest {
// ...
}
I suggest to use #Autowired and the related dependency injection mechanism of spring to use a single ConversionService instance throughout your application. The ConversionService will be instantiated within the configuration.
All Converters to be available application wide receive an annotation (e.g. #AutoRegistered). On application start a #Component FormatterRegistrar (Type name itself is a bit misleading, yes it is "...Registrar" as it does the registering. And #Component as it is fully spring managed and requires dependency injection) will receive #AutoRegistered List of all annotated Converters.
See this thread for concrete implementation details. We use this mechanism within our project and it works out like a charm.
org.springframework.web.servlet.config.annotation.WebMvcConfigurer or any on its implementation is one stop place for any kind of customization in spring boot project. It prvoides various methods, for your Converter requirement.
Just create a new Converter by extending org.springframework.core.convert.converter.Converter<S, T>. Then register it with Spring by your class overriding method org.springframework.web.servlet.config.annotation.WebMvcConfigurer.addFormatters(FormatterRegistry)
Note there are Other types of Converter also which basically starts from ConditionalConverter.
Trying adding
#Converter(autoApply = true)
Its needs to be placed over the convertor class. This works for me in case of Convertor needed for Localdate for interacting to DB.
#Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
#Override
public Date convertToDatabaseColumn(LocalDate locDate) {
return (locDate == null ? null : Date.valueOf(locDate));
}
#Override
public LocalDate convertToEntityAttribute(Date sqlDate) {
return (sqlDate == null ? null : sqlDate.toLocalDate());
}
}
This is now applied automatically while interacting with DB.
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.