0%

spring与springmvc整合

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 请求(路由、参数绑定、视图渲染)

容器核心规则

  1. 加载顺序:父容器先初始化(ContextLoaderListener 在 Web 应用启动时执行),子容器后初始化(DispatcherServlet 初始化时创建);
  2. 访问权限子容器可以访问父容器中的 Bean(如 Controller 注入 Service),但父容器不能访问子容器中的 Bean(如 Service 不能注入 Controller);
  3. Bean 优先级:若父子容器中存在同名 Bean,子容器的 Bean 会覆盖父容器的 Bean(但实际开发中应避免同名,确保职责分离)。

整合步骤:从配置到职责划分

整合分为 “web.xml 配置”“Spring 父容器配置”“Spring MVC 子容器配置” 三步,核心是明确两者的配置边界。

步骤 1:web.xml 配置 —— 指定容器加载规则

web.xml 是 Web 应用的入口配置文件,需同时配置父容器加载器ContextLoaderListener)和子容器加载器DispatcherServlet):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<!-- 1. 配置 Spring 父容器:由 ContextLoaderListener 加载 -->
<context-param>
<!-- 指定 Spring 核心配置文件路径(父容器配置) -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 注册监听器,启动时加载父容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 2. 配置 Spring MVC 子容器:由 DispatcherServlet 加载 -->
<servlet>
<servlet-name>springmvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 指定 Spring MVC 配置文件路径(子容器配置) -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 启动时加载(优先级 1,确保在 Web 容器启动时初始化) -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置 DispatcherServlet 拦截所有请求(除 JSP 外) -->
<servlet-mapping>
<servlet-name>springmvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
关键配置说明:
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 1. 组件扫描:扫描所有包,但排除 @Controller 注解的 Bean(留给子容器处理) -->
<context:component-scan base-package="com.example">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!-- 2. 配置数据源(如 Druid、HikariCP) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>

<!-- 3. 配置 MyBatis SqlSessionFactory(示例,非必需) -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>

<!-- 4. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 5. 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

步骤 3:Spring MVC 子容器配置(springmvc.xml

子容器专注于Web 组件,如 Controller、Interceptor、ViewResolver 等,需仅扫描 Controller(避免与父容器重复扫描):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 1. 组件扫描:仅扫描 @Controller 注解的 Bean(禁用默认过滤器,避免扫描其他组件) -->
<context:component-scan base-package="com.example" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!-- 若有 @RestController,需额外添加(或直接扫描 @Controller,因 @RestController 继承 @Controller) -->
<!-- <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/> -->
</context:component-scan>

<!-- 2. 启用 Spring MVC 注解驱动(注册 HandlerAdapter、HandlerMapping 等核心组件) -->
<mvc:annotation-driven/>

<!-- 3. 配置静态资源映射(解决 DispatcherServlet 拦截静态资源的问题) -->
<mvc:resources mapping="/static/**" location="/static/"/>

<!-- 4. 配置视图解析器(传统 JSP 项目,前后端分离可省略) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/> <!-- 视图前缀 -->
<property name="suffix" value=".jsp"/> <!-- 视图后缀 -->
</bean>

<!-- 5. 配置拦截器(示例) -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<bean class="com.example.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
关键配置说明:
  • use-default-filters="false":禁用 Spring 默认的组件扫描规则(默认扫描所有带 @Component@Service@Repository@Controller 的 Bean),确保仅扫描 include-filter 指定的 @Controller
  • mvc:annotation-driven:自动注册 RequestMappingHandlerMappingRequestMappingHandlerAdapter 等核心组件,支持 @RequestMapping@ResponseBody 等注解;
  • mvc:resources:解决 DispatcherServlet 拦截静态资源(如 /static/css/style.css)的问题,将指定路径的请求映射到静态资源目录。

整合关键问题解决

Spring 与 Spring MVC 整合时,最常见的问题是Bean 重复创建容器访问权限错误,需针对性解决。

问题 1:Bean 重复创建(核心坑点)

原因:

若父子容器的 component-scan 扫描范围重叠(如都扫描 com.example 且未过滤),会导致同一 Bean(如 UserService)在父子容器中各创建一次,造成资源浪费和潜在的依赖冲突。

解决方案:

通过 context:include-filtercontext: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 依赖未在父容器中注册。
解决方案

  1. 检查 web.xmlcontextConfigLocation 的路径是否正确(如 classpath:applicationContext.xml 是否存在);
  2. 确保子容器依赖的 Bean(如 UserService)在父容器中已注册(如添加 @Service 注解并被父容器扫描)。
现象 2:父容器访问子容器 Bean 失败

原因:违反父子容器规则(父容器不能访问子容器的 Bean),如 UserService 试图注入 UserController
解决方案

  • 调整依赖关系:确保仅子容器的 Bean 依赖父容器的 Bean(如 Controller 依赖 Service),反之则不允许;
  • 若需跨层访问,通过接口或事件机制实现,而非直接依赖。

问题 3:静态资源无法访问

原因:

DispatcherServlet 配置为 <url-pattern>/</url-pattern> 时,会拦截所有请求(包括静态资源),而 Spring MVC 默认不处理静态资源。

解决方案:

springmvc.xml 中配置静态资源映射:

1
2
3
4
5
<!-- 映射 /static/** 路径到 /static/ 目录(如 /static/css/style.css → 实际路径 /static/css/style.css) -->
<mvc:resources mapping="/static/**" location="/static/"/>

<!-- 若静态资源在 classpath 下(如 Maven 的 resources/static 目录),使用 classpath: 前缀 -->
<!-- <mvc:resources mapping="/static/**" location="classpath:/static/"/> -->

父子容器关系深度解析

1. 容器初始化顺序

Web 应用启动时,容器初始化流程如下:

  1. Web 容器(如 Tomcat)启动,加载 web.xml
  2. 执行 ContextLoaderListener,初始化 Spring 父容器,加载 applicationContext.xml
  3. 初始化 DispatcherServlet,加载 springmvc.xml 创建子容器;
  4. 子容器初始化时,会将父容器设置为自己的 “父上下文”,建立父子关系。

2. Bean 依赖注入规则

  • 子容器注入父容器 Bean:支持(如UserController注入UserService,UserService在父容器);

    1
    2
    3
    4
    5
    6
    @Controller
    public class UserController {
    // 正确:Service 在父容器,子容器可访问
    @Autowired
    private UserService userService;
    }
  • 父容器注入子容器 Bean:不支持(如UserService注入UserController,会抛出NoSuchBeanDefinitionException);

    1
    2
    3
    4
    5
    6
    @Service
    public class UserService {
    // 错误:Controller 在子容器,父容器不可访问
    @Autowired
    private UserController userController;
    }

3. 获取容器上下文

Spring 启动时,ContextLoaderListener 会将父容器上下文存入 ServletContext(Web 应用上下文),可在任意 Web 组件(如 Controller、Filter)中获取:

示例:在 Controller 中获取父容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 方式1:通过 ServletContext 获取父容器(推荐)
WebApplicationContext parentContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
// 从父容器中获取 Service Bean
UserService userService = parentContext.getBean(UserService.class);

// 方式2:直接从 ServletContext 中获取(不推荐,耦合度高)
// WebApplicationContext parentContext = (WebApplicationContext) request.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

ModelAndView mav = new ModelAndView("test");
mav.addObject("data", userService.getUserList());
return mav;
}
}
注意:
  • 优先使用 WebApplicationContextUtils.getWebApplicationContext() 工具类,避免硬编码属性名;
  • 实际开发中,应通过 @Autowired 自动注入 Bean,而非手动获取容器上下文(仅在非 Spring 管理的组件中使用,如自定义 Filter)。

整合最佳实践总结

  1. 职责划分清晰
    • 父容器(Spring):管理 Service、Dao、数据源、事务等非 Web 组件
    • 子容器(Spring MVC):管理 Controller、Interceptor、ViewResolver 等Web 组件
  2. 避免重复扫描
    • 父容器排除 @Controller,子容器仅扫描 @Controller
    • 禁用子容器的默认过滤器(use-default-filters="false"),确保扫描范围精确。
  3. 静态资源处理
    • 配置 mvc:resources 映射静态资源,避免 DispatcherServlet 拦截;
    • 前后端分离项目可省略视图解析器,专注 API 开发。
  4. 依赖注入规范
    • 仅允许子容器的 Bean 依赖父容器的 Bean(Controller → Service → Dao);
    • 禁止父容器依赖子容器的 Bean,避免容器访问权限错误

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10