Having some difficulties to separate spring-actuator-ApplicationContext from my own WebApplicationContext. Maybe someone can help me.
So, I have a spring-boot 2.7.17 webMVC app on port 8080, which is currently autoconfigured (so uses WebMvcAutoConfiguration.class) I'm adding actuator on port 8081 (which means separated DispatcherServlet and a child WebAppContext), trying to make it a client for spring-boot-admin.
But from this point pop-up an issue: on our app we are doing some custom configs within our WebMvcConfigurer, currently this WebMvcConfigurer belongs to the root WebAppContext, therefore those custom configs applied to actuator, changing the default output. As result spring-boot-admin is not able to work with such actuator-responses.
So my idea was to create additional separated WebAppContext, into which i'll place the WebMvcConfigurer. It will be not visible for root context, therefore actuator won't be affected. Let me create a silly schema to show the idea:
+---------------------------+ +---------------------------+
| Root WebAppContext | | Root AppContext |
| | +---------------------------+
| contains WebMvcConfigurer | / \
+---------------------------+ PLAN --> / \
| / \
+---------------------------+ +---------------------------+ +---------------------------+
| Child-actuator | | Child WebAppContext | | Child-actuator |
| WebAppContext | | contains WebMvcConfigurer | | WebAppContext |
| consumes WebMvcConfigurer | +---------------------------+ +---------------------------+
| and breaks spr-boot-admin | PROFIT
+---------------------------+
The problem is, that i'm not sure how to make it with Spring-boot-autoconfiguration. What i actually did try is the following:
@Bean
public DispatcherServlet dispatcherServlet(WebApplicationContext webApplicationContext) {
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.setParent(webApplicationContext);
webContext.register(WebMvcConfig.class);
return new DispatcherServlet(webContext);
}
@EnableWebMvc
public static class WebMvcConfig implements WebMvcConfigurer {
...
While this solves part of my problems, it creates another, bigger ones: without @EnableWebMvc WebMvcConfig is not invoked and applied, BUT WITH @EnableWebMvc the whole WebMvcAutoConfiguration is not invoked therefore i'm having troubles in other places. So as i understand the problem of my try is that WebAppContext is created on Root context, and not in the child, so as below:
+---------------------------+
| Root WebAppContext | - WebAppContext is initialised here, while it should be simple AppContext here, while webMvc initialisation should be moved to child context
+---------------------------+
/ \
/ \
/ \
+---------------------------+ +---------------------------+
| Child WebAppContext | | Child-actuator |
| contains WebMvcConfigurer | | WebAppContext |
+---------------------------+ +---------------------------+
with @EnableWebMvc - WebMvcConfigurer applied, but WebMvcAutoConfiguration is switched off -> troubles
without @EnableWebMvc - WebMvcConfigurer not applied.
So does anyone knows how i can implement my planned schema? Is it at all possible?
Or maybe someone knows how to trigger WebMvcConfigurer without @EnableWebMvc ?
Finally i was able to solve this issue. At the end i initialize WebApplicationContext in the root, but WebMvc in the child context, as below:
//1. Excluding WebMvcAutoConfiguration from root context
@SpringBootApplication(exclude = { ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class })
public class MultiContextApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(MultiContextApplication.class, args);
}
//2. Creating child context for app
@Bean
public DispatcherServlet dispatcherServlet(WebApplicationContext webApplicationContext) {
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.setParent(webApplicationContext);
webContext.setId(webApplicationContext.getId() + ":myApp");
webContext.register(DispatcherConfig.class);
return new DispatcherServlet(webContext);
}
//3. Inside child context we import WebMvcAutoConfiguration, which was excluded from root context on step 1
//Btw, be aware, that this class is not annotated with @Configuration or any @Component, this done by purpose.
//Actuator shouldn't detect this config.
@Import({ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class})
public static class DispatcherConfig implements WebMvcConfigurer {
//4. This is a crucial part of config, child context won't see @Controllers from parent context without it.
@Bean
public WebMvcRegistrations webMvcRegistrations(){
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
RequestMappingHandlerMapping mappingHandlerMapping = new RequestMappingHandlerMapping();
mappingHandlerMapping.setDetectHandlerMethodsInAncestorContexts(true);
return mappingHandlerMapping;
}
};
}
... below go your WebMvcConfigurer configurations.
Hope, this will be helpful for someone.