Spring Boot not using configured Jackson ObjectMapper with #EnableWebMvc - spring-mvc

I wanted to use a configured version of Jackson ObjectMapper in my project (ignoring null values and snake_case, also using some custom modules).
In my large project I wasn't able to get Spring MVC to actually use this mapper.
The build.gradle:
buildscript {
ext {
springBootVersion = '1.5.6.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compile("org.springframework.boot:spring-boot-starter-jetty:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.8'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.8'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
My application.yml:
spring:
application:
name: Jackson test
jackson:
property-naming-strategy: SNAKE_CASE
default-property-inclusion: non_empty
debug: true
A container class:
public class MyLocationEntity {
public String nameAndSnake;
}
A config class:
#Configuration
#EnableWebMvc
public class AppConfig {
}
And a controller:
#RestController
#RequestMapping("/test")
public class TestController {
#Autowired
private ObjectMapper objectMapper;
#RequestMapping(value = "/test", produces = "application/json")
public MyLocationEntity test() throws JsonProcessingException {
MyLocationEntity location = new MyLocationEntity();
location.nameAndSnake = "hello world";
String expexted = objectMapper.writeValueAsString(location);
return location;
}
}
If I now look at the value of expected in the debugger it is {"name_and_snake":"hello world"}.
But if I let the controller run through the actual response is {"nameAndSnake":"hello world"}.
When I remove #EnableWebMvc it works. How can I use the configured mapper with MVC and not remove the rest of the autoconfiguration for Web MVC?

It's not evident just from the Javadocs, but #EnableWebMvc disables the Spring Boot default web MVC auto configuration provided by WebMvcAutoConfiguration, including the use of the Jackson ObjectMapper bean configured by the application.yml properties. Per the Spring Boot Reference Documentation:
9.4.7. Switch off the Default MVC Configuration
The easiest way to take complete control over MVC configuration is to provide your own #Configuration with the #EnableWebMvc annotation. Doing so leaves all MVC configuration in your hands.
Since #EnableWebMvc has the (likely surprising) behavior of disabling auto configuration, using this annotation may have unintended and undesirable side-effects. A different approach may be more appropriate.
That being said, it is possible that the behavior of #EnableWebMvc is still desired. To use application.yml properties in concert with the #EnableWebMvc annotation, the MVC configuration must be manually configured to mimic the relevant disabled Spring Boot auto-configuration. There are a few different possible approaches to this.
The first approach is to duplicate the Spring Boot configuration code from WebMvcAutoConfiguration.EnableWebMvcConfiguration.configureMessageConverters(). This will replace the message converters — including the MappingJackson2HttpMessageConverter containing the unconfigured ObjectMapper — with the ones that would have used with the default Spring Boot configuration:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectProvider<HttpMessageConverters> messageConvertersProvider;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
}
Alternatively, rather than using the default Spring Boot list of message converters, it is possible to swap in just the ObjectMapper or MappingJackson2HttpMessageConverter bean provided by Spring Boot (which have the application.yml properties applied):
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectMapper objectMapper;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.map(c -> (MappingJackson2HttpMessageConverter) c)
.forEach(c -> c.setObjectMapper(objectMapper));
}
}
or
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (int i = 0; i < converters.size(); i++) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
converters.set(i, mappingJackson2HttpMessageConverter);
}
}
}
}

Related

How do I register a HandlerInterceptor with constructor dependencies in Spring Boot

My use case is running custom code before a controller method by annotating methods.
HandlerInterceptor seems the way to go but it seems impossible to inject dependencies into it because it needs to be registered before the context is being created.
All examples I've found so far use empty constructors (see spring boot adding http request interceptors) or autowire properties in the configuration which fails because I declare dependent beans in the same configuration (Requested bean is currently in creation: Is there an unresolvable circular reference?).
Is there a better way that does not involve AOP?
Assume that your interceptor has constructor dependencies like that:
public class CustomInterceptor extends HandlerInterceptor {
private final DependentBean bean;
public CustomInterceptor(DependentBean bean) {
this.bean = bean;
}
}
Then you can register your handler like that:
#Configuration
public WebConfig extends WebMvcConfigurerAdapater {
#Bean
public DependentBean dependentBean() {
return new DependentBean();
}
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor(dependentBean());
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor());
}
}
#Configuration will ensure each Bean method call return the same bean instance
Building on the answer above from Mạnh, if using component scan for dependency injection of the dependency, then that can be Autowired in the WebConfig
#Configuration
public WebConfig extends WebMvcConfigurerAdapater {
#Autowired
DependentBean dependentBean;
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor(dependentBean);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor());
}
}
Also building on previous answers, and if you use Lombok, you can further simplify.
Have your interceptor implementation been a #Component
Add a private final DependentBean field to it.
Also add a #RequiredArgsConstructor annotation to it, to have Lombok generating a constructor with a single DependentBean parameter.
In your WebConfig, use the same technic to have a private final CustomInterceptor field been injected by Spring IOC.
This way the CustomInterceptor instance will be available & initialized the right way when addInterceptors will be called
Here are the corresponding code samples :
The CustomInterceptor :
#Component
#RequiredArgsConstructor
public class CustomInterceptor implements HandlerInterceptor {
private final DependentBean dependentBean;
#Override
public boolean preHandle( final HttpServletRequest request,
final HttpServletResponse response,
final Object handler ) throws Exception {
// your Interceptor Implementation goes here ...
}
}
The WebConfig :
#Configuration
#RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final CustomInterceptor customInterceptor;
#Override
public void addInterceptors( final InterceptorRegistry registry ) {
registry.addInterceptor( customInterceptor );
}
}

How to define isolated web contexts in a modular Spring Boot application?

Given a Spring Boot application comprised of a bootstrap module, and two or more isolated business modules - each of which exposes REST API specific to a business domain, and each of which uses an independent, isolated document store for data persistence, how do I go about configuring such an application, such that:
The bootstrap module defines a parent context (non-web) which provides certain shared resources to underlying modules (global config, object mappers, etc.)
Each business module exposes their REST controllers on the same port but using a different context path. Ideally, I want to be able to define a base path for each module (e.g. /api/catalog, /api/orders) and separately define the sub-path within each controller.
Each business module defines its own repository configuration (e.g. different MongoDB settings for each module)
In order to isolate the contexts of the individual business modules (which allows me to manage independent repository configurations in each module) I have tried using the context hierarchies available in SpringApplicationBuilder to isolate the contexts of each of the individual business modules:
public class Application {
#Configuration
protected static class ParentContext {
}
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(ParentContext.class)
.child(products.config.ModuleConfiguration.class)
.web(true)
.sibling(orders.config.ModuleConfiguration.class)
.web(true)
.run(args);
}
}
however as each module contains a configuration class annotated with #EnableAutoConfiguration this causes Spring Boot to attempt to launch two independent embedded servlet containers, each trying to bind to the same port:
#Configuration
#EnableAutoConfiguration
public class WebApplicationConfiguration {
#Value("${api.basePath:/api}")
protected String apiBasePath;
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(),
apiBasePath + "/products/*");
registration.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registration;
}
}
The Spring Boot documentation on context hierarchies states that the parent context cannot be a web context, so I'm a bit lost as to how I can share an embedded servlet container between isolated child contexts.
I have created a minimal GitHub project to illustrate the point:
Create configuration class for business module child1's context
package com.child1;
#Configuration
#ComponentScan(basePackages = {"com.child1a", "com.child1b"})
public class Child1Configuration {
}
Create configuration class for business module child2's context
package com.child2;
#Configuration
#ComponentScan(basePackages = {"com.child2a", "com.child2b"})
public class Child2Configuration {
}
Create a configuration class for bootstrap module parent context. Specify the component scanning for beans to be shared by child contexts
package com.parent;
#Configuration
#ComponentScan(basePackages = {"com.parent1", "com.root"})
public class ParentConfiguration {
}
Create SpringBootApplication class with two dispatcher servlets beans, one for each business module. Create application context for each servlet and set the context created by the boot application as root. Basically spring will inject the context into the ApplicationContext parameter of the #Bean methods.
package com.parent;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean child1(ApplicationContext parentContext) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDetectAllHandlerMappings(false);
AnnotationConfigWebApplicationContext applicationContext = new
AnnotationConfigWebApplicationContext();
applicationContext.setParent(parentContext);
applicationContext.register(Child1Configuration.class);
applicationContext.refresh();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new
ServletRegistrationBean(dispatcherServlet, true, "/child1/*");
servletRegistrationBean.setName("child1");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
#Bean
public ServletRegistrationBean child2(ApplicationContext parentContext) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDetectAllHandlerMappings(false);
AnnotationConfigWebApplicationContext applicationContext = new
AnnotationConfigWebApplicationContext();
applicationContext.setParent(parentContext);
applicationContext.register(Child2Configuration.class);
applicationContext.refresh();
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new
ServletRegistrationBean(dispatcherServlet, true, "/child2/*");
servletRegistrationBean.setName("child2");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
}

Spring boot No WebApplicationContext found

I have a simple spring boot app and I'm trying to get it up and running. The config consists of an app context ( applicationContext.xml) XML with a bunch of beans in it. I have a Spring application class:
#SpringBootApplication
#Configuration
#ImportResource("classpath:applicationContext.xml")
public class WebCheckApplication {
private static final Logger logger = Logger.getLogger(WebCheckApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(WebCheckApplication.class, args);
if (logger.isDebugEnabled()) {
logger.debug("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
logger.debug(beanName);
}
}
}
}
And I have a #WebListener class that grabs a few beans from the WebContext from within the ServletContext:
#WebListener
public class SystemPropertiesContextInitializer extends SysPropsAlertsFetcher implements ServletContextListener {
private static final Logger logger = Logger.getLogger(SystemPropertiesContextInitializer.class);
#Override
public void contextDestroyed(ServletContextEvent sce) {
//remove the SystemProperties and alert types map object from context
sce.getServletContext().removeAttribute(BaseAuthenticatedController.SYSPROPS_KEY);
sce.getServletContext().removeAttribute(BaseAuthenticatedController.ALERT_TYPES_MAP_KEY);
}
#Override
public void contextInitialized(ServletContextEvent sce) {
SysPropsDataAccess = (SystemPropertiesDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("SystemPropertiesDataAccess");
AlertsDataAccess = (AlertDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("AlertsDataAccess");
fetchObjects(sce.getServletContext());
}
}
When I attempt to start the app, I get the following error:
SEVERE: Exception sending context initialized event to listener instance of class web.SystemPropertiesContextInitializer
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83)
at .web.SystemPropertiesContextInitializer.contextInitialized(SystemPropertiesContextInitializer.java:31)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4994)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5492)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
and it occurs at this line:
SysPropsDataAccess = (SystemPropertiesDataAccess) WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getBean("SystemPropertiesDataAccess");
It looks like Spring isn't creating a WebApplicationContext.
Greater than or equal 1.3.0.RC1 use #ServletComponentScan
#ServletComponentScan // <-- This scans for EJB #WebFilter, #WebListener and #WebServlet
#SpringBootApplication
#ImportResource("classpath:applicationContext.xml")
public class WebCheckApplication {
Less than or equal 1.2.x use #Component to scan for listener
#Component // <-- This allows the component to be found by #ComponentScan inside of #SpringBootApplication
#WebListener
public class MojoSystemPropertiesContextInitializer extends MojoSysPropsAlertsFetcher implements ServletContextListener {
War Deploy extend SpringBootServletInitializer
public class WebCheckApplication extends SpringBootServletInitializer {
In 1.3.0.RC1 #ServletComponentScan was added so simply annotating your main application config should allow these to be picked up. Otherwise adding #Component to your ServletContextListener should work
This link is a discussion on how they currently handle #WebFilter how they decided to handle #WebFilter and they also discuss SpringBootServletInitializer and how this would pick process each item twice if both were to be used. Also links to the commits that implement the new feature.
https://github.com/spring-projects/spring-boot/issues/2290
If you intend to deploy your application as a war file you may also have your main configuration extend SpringBootServletInitializer
http://docs.spring.io/spring-boot/docs/current/reference/html/howto-traditional-deployment.html
All that was needed was to make the Application class extend SpringBootServletInitializer

Enabling transaction in Spring MVC

My application is working well, but I´m not sure why I needed to configure the way I did.
The issue is that transactions must be enabled in both contexts, root and dispatcher.
Why is this necessary?
The app is configured like this:
public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses () {
return new Class<?>[] {RootConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses () {
return new Class<?>[] {WebMvcConfig.class};
}
}
Then
#Configuration
#Import(value = {
DataSourceConfig.class,
JpaConfig.class,
SecurityConfig.class
})
#ComponentScan(
basePackages = {"com.**.service", "com.**.mail", "com.core.config"},
excludeFilters = {
#Filter(Configuration.class),
#Filter(Controller.class),
#Filter(RestController.class)
})
#EnableAsync
#EnableScheduling
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class RootConfig {
// Beans Config...
}
Finally
#Configuration
#ComponentScan(basePackages = {"com.web"})
#EnableWebMvc
#EnableTransactionManagement
public class WebMvcConfig extends WebMvcConfigurerAdapter {
// Beans Config ...
}
Note:
Transactions are just managed in the Service Layer.
That #EnableTransactionManagement is present in both config
classes.
If I remove the one in WebMvcConfig, transactions will not be injected in the service layer when accesed from a Controller class. Even though the service layer is scanned by the root context.
If I remove the one in RootConfig, transactions will not be injected in the service layer when accesed for example by Spring Security.
Is there any way to enable transactions globally?

Reconfigure Spring Data Rest to Index at Page 1

I thought I had this figured out but the setting does not seem to change the index. setOneIndexedParameters(true)
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setOneIndexedParameters(true);
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
.... other config stuff
Expected result is that instead of the base URL for spring data rest being http://localhost:8080/api/text?page=0&size=20 it would change to http://localhost:8080/api/text?page=1&size=20 as the initial page.
Did I do this correctly or is this a bug?
The answer was here Spring Data Rest - Configure pagination
I moved the configuration to extending RepositoryRestMvcConfiguration
#Configuration
class CustomRestMvcConfiguration extends RepositoryRestMvcConfiguration {
#Override
#Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
HateoasPageableHandlerMethodArgumentResolver resolver = super.pageableResolver();
resolver.setOneIndexedParameters(true);
return resolver;
}
}

Resources