objectMapper not configured automatically - spring-boot

I'm having below spring boot application class
#SpringBootApplication
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(final ObjectMapper objectMapper) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
converter.setObjectMapper(objectMapper);
return converter;
}
}
in my intelliJ IDE, it's showing warning in objectMapper param as below
Could not autowire. no beans of 'ObjectMapper' type found.
Based on my understanding, objectMapper will autowire here automatically. and even I didn't got any error while running application. able to up the application.
Not sure, why intelliJ showing this warning.
Help me to find this and add/correct me if I missed anything

Related

Spring Ioc Beans management

I have a question about spring IoC management. I created Bean in:
#SpringBootApplication
public class Application {
public static void main(String[] args) {....}
#Bean
public XmlMapper xmlMapper() {
return new XmlMapper();
}
}
These beans work fine as expected. But Default ObjectMapper get overridden and
#RestController try to parse the request and expect that payload is XML.
Can anyone explain why this happens?
XmlMapper is a sub class of ObjectMapper so if you declare one bean of this type, Spring will use that one and inject it where needed.
If you want to still use basic ObjectMapper elsewhere. You can declare another bean ObjectMapper. You may have to indicate it as primary.
#SpringBootApplication
public class Application {
public static void main(String[] args) {....}
#Bean
public XmlMapper xmlMapper() {
return new XmlMapper();
}
#Bean
#Primary //Not sure if needed
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

What is the suggested way to modify / override / extend built-in Jackson 2 ObjectMapper in Spring Webflux?

Currently I have a minimalistic Spring / Netty, Reactor / Web Flux project with Jackson libraries
#Configuration
public class EmbeddedSpringServer extends DelegatingWebFluxConfiguration {
#Bean
MyController controller() {
return new AdminController();
}
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EmbeddedSpringServer.class);
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create("0.0.0.0", 8082).newHandler(adapter).subscribe();
applicationContext.registerShutdownHook();
}
}
build.gradle:
compile 'org.springframework:spring-context:5.0.2.RELEASE'
compile 'org.springframework:spring-web:5.0.2.RELEASE'
compile 'org.springframework:spring-webflux:5.0.2.RELEASE'
compile 'io.projectreactor.ipc:reactor-netty:0.7.2.RELEASE'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.3'
Controller class works fine (it returns a Mono<> with a DTO type).
Because Jackson is present in classpath Web Flux automatically creates an Object Mapper instance via DefaultServerCodecConfigurer however it's not clear how to override object mapper instance, because most Web Flux configuration classes are package private.
What I'd like to achieve is to create my own object mapper to add custom LocalDateTime serialization implemented in jackson-modules-java8
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
;
The problem is that's it's not clear how to modify Jackson2JsonEncoder created in package private org.springframework.http.codec.support.AbstractCodecConfigurer.AbstractDefaultCodecs.
You can disable that Jackson feature right from your application.properties file with:
spring.jackson.serialization.write-dates-as-timestamps=false
It turned out simpler than I thought initially as DelegatingWebFluxConfiguration already has a configureHttpMessageCodecs method overriding which is enough
#Configuration
public class EmbeddedSpringServer extends DelegatingWebFluxConfiguration {
#Bean
MyController controller() {
return new MyController();
}
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EmbeddedSpringServer.class);
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create("0.0.0.0", 8082).newHandler(adapter).subscribe();
applicationContext.registerShutdownHook();
}
#Bean
ObjectMapper objectMapper(){
return new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
}
#Override
protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper()));
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper()));
}
}
as for the Spring Boot I think it can be also achieved by returning a webFluxConfigurer bean
#Bean
WebFluxConfigurer webFluxConfigurer(ObjectMapper objectMapper) {
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper());
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper());
}
};
}
as they are picked by DelegatingWebFluxConfiguration created by #EnableWebFlux automatically.
N.B. default implementation of Jackson2ObjectMapperBuilder already registers these date modules automatically the problem with dates was not related, I ended with
#Override
protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(Jackson2ObjectMapperBuilder
.json()
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build()));
}
to achieve simple date serialization.

Spring Boot & Mongo: custom MappingMongoConverter doesn't work during DotReplacement

I need to achive dotReplacementKey
I'm using MongoDB with such config:
#Configuration
public class MongoTemplateConfig {
#Value("${adserver_mongo_connection_string}")
private String databaseConnectionString;
#Bean
public MongoDbFactory mongoDbFactory() throws UnknownHostException {
MongoClientURI uri = new MongoClientURI(databaseConnectionString.trim());
return new SimpleMongoDbFactory(uri);
}
#Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
return new MongoTemplate(mongoDbFactory());
}
#Bean
public MappingMongoConverter mongoConverter(MongoDbFactory mongoFactory, MongoMappingContext mongoMappingContext) throws Exception {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
mongoConverter.setMapKeyDotReplacement(".");
return mongoConverter;
}
}
I'm doing upsert as follows:
mongoTemplate.bulkOps(...).upsert(...)
but during runtime via debug I found out that different MappingMongoConverter is used rather then this that was configured as a #Bean
btw, if #Inject wherever MappingMongoConverter I get proper bean from config with keyDotReplacement="."
but looks like Spring Boot uses another one under the hood
P.S. I have seen this question without correct answer, but tried with mongoConverter.afterPropertiesSet() and obviously it doesn't work as well
It's a bit ridiculous but the point is that we should pass this custom mappingMongoConverter into MongoTemplate initialization :
#Bean
public MongoTemplate mongoTemplate(MappingMongoConverter mappingMongoConverter) throws UnknownHostException {
return new MongoTemplate(mongoDbFactory(), mappingMongoConverter);
}

Spring Hibernate Jackson Hibernate5Module

I have set up spring 4.3.1 with Hibernate 5.1.0 and Jackson 2.7.5
I had some lazy init Exceptions because the Jackson ObjectMapper tries to convert my Objects to late when I am out of the Transactional Service.
Therefore I have read the Hibernate5Module for Jackson.
After adding the Module I do not get lazy Exceptions BUT all #JsonView Annotations are ignored and my lazy collections are 'null'
public class SpringWebConfig extends WebMvcConfigurerAdapter {
...
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
Is there anything I am doing wrong?
The Hibernate5Module should initialize the lazy collections ...
By creating your own ObjectMapper, you're overriding the one Spring Boot would set up, which would include a bunch of useful modules, such as Jdk8 module.
What you should do instead, is just add the Hibernate5() module to the Application Context and Spring Boot will automatically add it to the ObjectMapper that it sets up. Like this in any #Configuration class:
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
Got it to work with the following
#EnableWebMvc
#Configuration
#ComponentScan({ "..." })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
#Autowired
SessionFactory sf;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Hibernate5Module module = new Hibernate5Module(sf);
module.disable(Feature.USE_TRANSIENT_ANNOTATION);
module.enable(Feature.FORCE_LAZY_LOADING);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modulesToInstall(module);
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
I manage to make it work with the below implementation
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Hibernate5Module module = new Hibernate5Module(); // or Hibernate4Module ... depends on hibernate version you are using
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
converters.add(new MappingJackson2HttpMessageConverter(mapper));
}
jackson-datatype-hibernate5 bring many solutions but there are some default configurations as well.
Please have a look on
Below is the configuration I did as per my project requirements.
#Bean
public Hibernate5Module hibernateModule() {
Hibernate5Module module = new Hibernate5Module();
module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
return module;
}

Integrating spring-boot with RESTEasy

I am trying to prototype a Spring Boot application. I'm coming from a Guice JAX-RS application, so I prefer the standard JAX-RS annotations to Spring MVC. I've gotten Jetty up and serving:
#Configuration
#Import({ResteasyBootstrap.class, SpringBeanProcessorServletAware.class, HttpServletDispatcher.class})
public class EmbeddedJetty {
#Bean
#Singleton
public EmbeddedServletContainerFactory servletContainer() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setPort(9000);
factory.setSessionTimeout(10, TimeUnit.MINUTES);
return factory;
}
}
However, I just can't figure out how to get RESTEasy hooked up correctly. With the above SpringBeanProcessorServletAware it bails, seemingly the ServletContext is not injected through ServletContextAware before it ends up being used:
java.lang.NullPointerException: null
at org.jboss.resteasy.plugins.spring.SpringBeanProcessorServletAware.getRegistry(SpringBeanProcessorServletAware.java:30)
at org.jboss.resteasy.plugins.spring.SpringBeanProcessor.postProcessBeanFactory(SpringBeanProcessor.java:247)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:284)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:680)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:522)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
I also tried using the SpringContextLoaderListener, but that seems to conflict with the spring-boot AnnotationConfigEmbeddedWebApplicationContext class.
I'm using spring-boot 1.3.3 and spring-framework 4.3.0.rc1
The other answer won't have your resources as spring beans, this autoconfiguration will integrate them properly:
The Configuration class:
#Configuration
#ConditionalOnWebApplication
public class RestEasyAutoConfigurer {
private Environment environment;
#Bean(name = "resteasyDispatcher")
public ServletRegistrationBean resteasyServletRegistration() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HttpServletDispatcher(), getPrefix()
+ "/*");
registrationBean.setInitParameters(ImmutableMap.of("resteasy.servlet.mapping.prefix", "/rs/")); // set prefix here
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
#Bean(destroyMethod = "cleanup")
public static RestEasySpringInitializer restEasySpringInitializer() {
return new RestEasySpringInitializer();
}
#Bean
// use Spring Boot configured Jackson
public CustomResteasyJackson2Provider jackson2Provider(ObjectMapper mapper) {
return new CustomResteasyJackson2Provider(mapper);
}
public static class RestEasySpringInitializer
implements
ServletContextInitializer,
ApplicationContextAware,
BeanFactoryPostProcessor {
private ResteasyDeployment deployment;
private ConfigurableApplicationContext applicationContext;
private ConfigurableListableBeanFactory beanFactory;
public void cleanup() {
deployment.stop();
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
ListenerBootstrap config = new ListenerBootstrap(servletContext);
deployment = config.createDeployment();
deployment.start();
servletContext.setAttribute(ResteasyProviderFactory.class.getName(), deployment.getProviderFactory());
servletContext.setAttribute(Dispatcher.class.getName(), deployment.getDispatcher());
servletContext.setAttribute(Registry.class.getName(), deployment.getRegistry());
SpringBeanProcessor processor = new SpringBeanProcessor(deployment.getDispatcher(),
deployment.getRegistry(), deployment.getProviderFactory());
processor.postProcessBeanFactory(beanFactory);
applicationContext.addApplicationListener(processor);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
}
}
And the Jackson provider:
#Provider
#Consumes({"application/*+json", "text/json"})
#Produces({"application/*+json", "text/json"})
public class CustomResteasyJackson2Provider extends ResteasyJackson2Provider {
private ObjectMapper mapper;
public CustomResteasyJackson2Provider(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
return Optional.ofNullable(_mapperConfig.getConfiguredMapper()).orElse(mapper);
}
}
NOTE: this is a working configuration for Spring Boot 1.3.3 / RESTEasy 3.0.16
You can use RESTEasy Spring Boot starter. Here is how you do it:
Adding POM dependency
Add the Maven dependency below to your Spring Boot application pom file.
<dependency>
<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>2.1.1-RELEASE</version>
<scope>runtime</scope>
</dependency>
Registering JAX-RS application classes
Just define your JAX-RS application class (a subclass of Application) as a Spring bean, and it will be automatically registered. See the example below. See section JAX-RS application registration methods in How to use RESTEasy Spring Boot Starter for further information.
package com.test;
import org.springframework.stereotype.Component;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#Component
#ApplicationPath("/sample-app/")
public class JaxrsApplication extends Application {
}
Registering JAX-RS resources and providers
Just define them as Spring beans, and they will be automatically registered. Notice that JAX-RS resources can be singleton or request scoped, while JAX-RS providers must be singletons.
Further information at the project GitHub page.
Here is fully working example.
First, a sample JAX-RS endpoint:
#Path("/api")
public class SampleResource {
#GET
#Path("/sample")
#Produces(MediaType.APPLICATION_JSON)
public String getSample() {
return "Some JSON";
}
}
Next, a JAX-RS configuration class that loads all endpoints.
import javax.ws.rs.core.Application;
public class RestEasyConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(SampleRest.class);
return classes;
}
}
Finally, in your Spring configuration, initialize RESTEast filter and inform the framework about its existence.
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.jboss.resteasy.plugins.server.servlet.FilterDispatcher;
...
#Bean
public FilterRegistrationBean filterRegistrationBean() {
Map<String, String> initParams = new HashMap<>();
initParams.put("javax.ws.rs.Application", RestEasyConfig.class.getCanonicalName());
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new FilterDispatcher());
registrationBean.setInitParameters(initParams);
return registrationBean;
}
Your endpoint should be up and running. If you are missing the FilterDispatcher class on your class path, add the resteasy-jaxrs library to your build descriptor.

Resources