In classic Spring MVC, explain the split between the root WebApplicationContext and each DispatcherServlet's context. What goes where, and why?
answer
- ContextLoaderListener → root (services/repos/datasource)
- DispatcherServlet → child (controllers/handlers/view resolvers)
- Child parent = root; controllers inject services
- Multiple dispatchers share one root
- Scan overlap → duplicate service beans (AOP/tx break)
basics
~20 sThe root context (loaded by ContextLoaderListener) holds shared beans: services, repositories, datasource. Each DispatcherServlet creates a child context for web beans: controllers, handler mappings, view resolvers. Controllers can inject root services; root beans can't see web beans.
solid answer
~40 sClassic MVC uses a two-level hierarchy. `ContextLoaderListener` bootstraps a single **root `WebApplicationContext`** containing shared, servlet-agnostic infrastructure — `@Service`, `@Repository`, `DataSource`, transaction manager, security. Each `DispatcherServlet` then builds its **own child context** holding web-layer beans: `@Controller`s, `HandlerMapping`, `HandlerAdapter`, `ViewResolver`, `MessageConverter`s. Because the servlet context's parent is the root, controllers can inject root services, but root beans never see controllers. The point is **sharing + isolation**: multiple `DispatcherServlet`s (say one for the UI, one for a REST API) share one root of business beans while keeping independent web configs. If you never declare a root context, a single `DispatcherServlet` still works — its context just has no parent and holds everything. Spring Boot collapses this into one flat context by default.
code
java · 19 linespublic class MvcInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override protected Class<?>[] getRootConfigClasses() {
return new Class[]{ RootConfig.class }; // services, repos, DataSource, tx
}
@Override protected Class<?>[] getServletConfigClasses() {
return new Class[]{ WebConfig.class }; // @Controllers, ViewResolver, converters
}
@Override protected String[] getServletMappings() {
return new String[]{ "/" };
}
}
// RootConfig should scan services/repos and EXCLUDE controllers;
// WebConfig (@EnableWebMvc) should scan ONLY controllers -- otherwise
// the same @Service is created twice (once per context).go deeper
Name the two contexts and what each holds; controllers on top, services/repos shared underneath.
Explain ContextLoaderListener vs DispatcherServlet and that the servlet context's parent is the root, enabling controllers to inject services.
Discuss the sharing/isolation rationale, multiple DispatcherServlets over one root, and the duplicate-bean/AOP pitfall from overlapping scans.
Weigh whether the split is worth it today vs a flat Boot context; reason about layering enforcement, security/filter placement, and migration.
## The two contexts Classic (pre-Boot / XML or `WebApplicationInitializer`) Spring MVC deliberately creates a **parent-child pair**: ### 1. Root `WebApplicationContext` (the parent) - Created by `org.springframework.web.context.ContextLoaderListener`, configured via the `contextConfigLocation` context-param (or `getRootConfigClasses()` in Java config). - Holds **shared, web-agnostic** beans: `@Service`, `@Repository`, `DataSource`, `PlatformTransactionManager`, Spring Security, JPA/`EntityManagerFactory`, messaging. - Stored in the `ServletContext` under `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`. - There is **at most one** root per web app. ### 2. DispatcherServlet `WebApplicationContext` (the child) - Each `DispatcherServlet` builds its **own** context, configured via its `contextConfigLocation` init-param (or `getServletConfigClasses()`), defaulting to `/WEB-INF/<servlet-name>-servlet.xml`. - Holds **web-layer** beans: `@Controller` / `@RestController`, `HandlerMapping`, `HandlerAdapter`, `HandlerInterceptor`, `ViewResolver`, `HttpMessageConverter`, `MultipartResolver`, `@ControllerAdvice`. - Its **parent is set to the root context** (`DispatcherServlet` locates the root from the `ServletContext`). ## Why split them? - **Sharing:** one root of business beans is reused by every servlet — no duplication of services/datasources. - **Isolation:** each servlet can have its own web config (different view resolvers, interceptors, URL mappings). A web-app UI `DispatcherServlet` and an `/api` REST `DispatcherServlet` share services but stay independent. - **Directional wiring:** controllers (child) inject services (parent). Services (parent) must never depend on controllers — and structurally they *can't*, which enforces clean layering. ## Gotchas that actually bite - **Component-scan overlap:** if the root config `@ComponentScan`s the same packages as the servlet config, you get **duplicate beans** — one instance in each context. The usual fix: root scans services/repos and *excludes* `@Controller`; the servlet scans only controllers. A frequent symptom is transactions/AOP not applying to a service because a second, un-proxied copy was created in the servlet context. - **Security & filters** live at the servlet-filter level against the root context; getting the placement wrong breaks method security. - **A servlet bean can't be injected into a root bean** — if you find yourself needing that, your layering is inverted. ## Java-config equivalent `AbstractAnnotationConfigDispatcherServletInitializer` makes the split explicit: `getRootConfigClasses()` → root context, `getServletConfigClasses()` → DispatcherServlet child context, `getServletMappings()` → URL mapping. ## Spring Boot today Boot auto-configures a **single flat `ApplicationContext`** (the embedded servlet container registers one `DispatcherServlet` whose context *is* the application context, with no separate root). So the root/child split is mostly historical, but it still explains legacy apps, multi-`DispatcherServlet` setups, and Spring Cloud's bootstrap context.
- Why might @Transactional silently stop working after adding component scanning to the servlet config?If the servlet context re-scans the service packages, it creates a second, non-transaction-proxied instance of the service in the child context; controllers inject that copy instead of the AOP-proxied one in the root, so the advice never runs. Scope each context's scan to distinct stereotypes.
- Can two DispatcherServlets share the same business services?Yes — that's the main reason for the split. Both servlet contexts have the single root context as parent, so both inject the same shared service singletons while keeping independent web configurations.
saying these in an interview costs you the question
- Putting @Controllers in the root context (they belong in the servlet child).
- Letting root and servlet scans overlap, creating duplicate service beans.
- Claiming a root/service bean can inject a controller.
- Assuming Spring Boot uses this two-context split by default (it's a single flat context).