Spring 与 Spring MVC 整合详解:父子容器机制与配置最佳实践
在企业级 Java Web 项目中,Spring(核心容器)与 Spring MVC(Web 框架)的整合是基础架构设计的关键环节。合理的整合能清晰划分业务逻辑层(Service、Dao) 与 Web 层(Controller、Interceptor) 的职责,避免配置混乱和 Bean 重复创建。从 “整合核心思想→配置步骤→关键问题解决→容器关系” 四个维度,彻底讲透 Spring 与 Spring MVC 的整合逻辑。
整合核心思想:父子容器机制
Spring 与 Spring MVC 整合的本质是创建两个独立但存在依赖关系的容器,即 “父子容器”,分别管理不同层级的 Bean,实现职责分离:
| 容器类型 | 加载者 | 配置文件 | 管理的 Bean 类型 | 核心职责 |
|---|---|---|---|---|
| 父容器 | ContextLoaderListener |
applicationContext.xml(Spring 核心配置) |
Service、Dao、数据源(DataSource)、事务管理器、第三方框架整合(如 MyBatis) | 处理业务逻辑、数据访问,提供全局共享的非 Web 组件 |
| 子容器 | DispatcherServlet |
springmvc.xml(Spring MVC 配置) |
Controller、HandlerInterceptor、ViewResolver、HandlerAdapter | 处理 Web 请求(路由、参数绑定、视图渲染) |
容器核心规则
- 加载顺序:父容器先初始化(
ContextLoaderListener在 Web 应用启动时执行),子容器后初始化(DispatcherServlet初始化时创建); - 访问权限:子容器可以访问父容器中的 Bean(如 Controller 注入 Service),但父容器不能访问子容器中的 Bean(如 Service 不能注入 Controller);
- Bean 优先级:若父子容器中存在同名 Bean,子容器的 Bean 会覆盖父容器的 Bean(但实际开发中应避免同名,确保职责分离)。
整合步骤:从配置到职责划分
整合分为 “web.xml 配置”“Spring 父容器配置”“Spring MVC 子容器配置” 三步,核心是明确两者的配置边界。
步骤 1:web.xml 配置 —— 指定容器加载规则
web.xml 是 Web 应用的入口配置文件,需同时配置父容器加载器(ContextLoaderListener)和子容器加载器(DispatcherServlet):
1 |
|
关键配置说明:
context-param:为ContextLoaderListener提供配置文件路径,若不配置,默认加载WEB-INF/applicationContext.xml;load-on-startup:值为非负整数,表示 Web 容器启动时初始化DispatcherServlet,值越小优先级越高;url-pattern:/:拦截所有请求(包括静态资源如 CSS/JS,需在 Spring MVC 中额外配置静态资源映射),不拦截.jsp(由 Tomcat 的 JspServlet 处理)。
步骤 2:Spring 父容器配置(applicationContext.xml)
父容器专注于非 Web 组件,如 Service、Dao、数据源、事务等,需排除 Web 层的 Controller(避免与子容器重复扫描):
1 |
|
步骤 3:Spring MVC 子容器配置(springmvc.xml)
子容器专注于Web 组件,如 Controller、Interceptor、ViewResolver 等,需仅扫描 Controller(避免与父容器重复扫描):
1 |
|
关键配置说明:
use-default-filters="false":禁用 Spring 默认的组件扫描规则(默认扫描所有带@Component、@Service、@Repository、@Controller的 Bean),确保仅扫描include-filter指定的@Controller;mvc:annotation-driven:自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter等核心组件,支持@RequestMapping、@ResponseBody等注解;mvc:resources:解决DispatcherServlet拦截静态资源(如/static/css/style.css)的问题,将指定路径的请求映射到静态资源目录。
整合关键问题解决
Spring 与 Spring MVC 整合时,最常见的问题是Bean 重复创建和容器访问权限错误,需针对性解决。
问题 1:Bean 重复创建(核心坑点)
原因:
若父子容器的 component-scan 扫描范围重叠(如都扫描 com.example 且未过滤),会导致同一 Bean(如 UserService)在父子容器中各创建一次,造成资源浪费和潜在的依赖冲突。
解决方案:
通过 context:include-filter 和 context:exclude-filter 严格划分扫描范围:
- 父容器:扫描所有包,但排除
@Controller; - 子容器:仅扫描
@Controller,禁用默认过滤器。
错误配置 vs 正确配置对比:
| 配置场景 | 错误配置(重复扫描) | 正确配置(职责分离) |
|---|---|---|
父容器(applicationContext.xml) |
<context:component-scan base-package="com.example"/>(扫描所有 Bean) |
<context:component-scan base-package="com.example"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> |
子容器(springmvc.xml) |
<context:component-scan base-package="com.example"/>(扫描所有 Bean) |
<context:component-scan base-package="com.example" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> |
问题 2:容器访问权限错误
现象 1:子容器访问父容器 Bean 失败
原因:父容器未正确加载(如 contextConfigLocation 路径错误),或子容器中的 Bean 依赖未在父容器中注册。
解决方案:
- 检查
web.xml中contextConfigLocation的路径是否正确(如classpath:applicationContext.xml是否存在); - 确保子容器依赖的 Bean(如
UserService)在父容器中已注册(如添加@Service注解并被父容器扫描)。
现象 2:父容器访问子容器 Bean 失败
原因:违反父子容器规则(父容器不能访问子容器的 Bean),如 UserService 试图注入 UserController。
解决方案:
- 调整依赖关系:确保仅子容器的 Bean 依赖父容器的 Bean(如
Controller依赖Service),反之则不允许; - 若需跨层访问,通过接口或事件机制实现,而非直接依赖。
问题 3:静态资源无法访问
原因:
DispatcherServlet 配置为 <url-pattern>/</url-pattern> 时,会拦截所有请求(包括静态资源),而 Spring MVC 默认不处理静态资源。
解决方案:
在 springmvc.xml 中配置静态资源映射:
1 | <!-- 映射 /static/** 路径到 /static/ 目录(如 /static/css/style.css → 实际路径 /static/css/style.css) --> |
父子容器关系深度解析
1. 容器初始化顺序
Web 应用启动时,容器初始化流程如下:
- Web 容器(如 Tomcat)启动,加载
web.xml; - 执行
ContextLoaderListener,初始化 Spring 父容器,加载applicationContext.xml; - 初始化
DispatcherServlet,加载springmvc.xml创建子容器; - 子容器初始化时,会将父容器设置为自己的 “父上下文”,建立父子关系。
2. Bean 依赖注入规则
子容器注入父容器 Bean:支持(如UserController注入UserService,UserService在父容器);
1
2
3
4
5
6
public class UserController {
// 正确:Service 在父容器,子容器可访问
private UserService userService;
}父容器注入子容器 Bean:不支持(如UserService注入UserController,会抛出NoSuchBeanDefinitionException);
1
2
3
4
5
6
public class UserService {
// 错误:Controller 在子容器,父容器不可访问
private UserController userController;
}
3. 获取容器上下文
Spring 启动时,ContextLoaderListener 会将父容器上下文存入 ServletContext(Web 应用上下文),可在任意 Web 组件(如 Controller、Filter)中获取:
示例:在 Controller 中获取父容器
1 | import org.springframework.web.context.WebApplicationContext; |
注意:
- 优先使用
WebApplicationContextUtils.getWebApplicationContext()工具类,避免硬编码属性名; - 实际开发中,应通过
@Autowired自动注入 Bean,而非手动获取容器上下文(仅在非 Spring 管理的组件中使用,如自定义 Filter)。
整合最佳实践总结
- 职责划分清晰:
- 父容器(Spring):管理 Service、Dao、数据源、事务等非 Web 组件;
- 子容器(Spring MVC):管理 Controller、Interceptor、ViewResolver 等Web 组件。
- 避免重复扫描:
- 父容器排除
@Controller,子容器仅扫描@Controller; - 禁用子容器的默认过滤器(
use-default-filters="false"),确保扫描范围精确。
- 父容器排除
- 静态资源处理:
- 配置
mvc:resources映射静态资源,避免DispatcherServlet拦截; - 前后端分离项目可省略视图解析器,专注 API 开发。
- 配置
- 依赖注入规范:
- 仅允许子容器的 Bean 依赖父容器的 Bean(Controller → Service → Dao);
- 禁止父容器依赖子容器的 Bean,避免容器访问权限错误
v1.3.10