乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

Spring MVC 导引

一句话定位:Spring MVC 并非独立于 Spring 容器之外的新世界,而是构建在 IoC 容器之上的 Web 层扩展。理解这一点,是打通"容器思维"与"Web 开发"任督二脉的关键。本章承上启下,只回答一个问题:DispatcherServlet 与 ApplicationContext 到底是什么关系?


Spring MVC 与 IoC 容器的关系

Spring MVC 的全称是 Spring Web MVC,它是 Spring Framework 的 Web 模块(spring-webmvc)提供的基于 Servlet API 的 Web 框架。它的核心设计哲学是:所有 Web 层组件(Controller、Interceptor、ViewResolver 等)都是 Spring 容器中的 Bean。

这意味着:

  1. Controller 是 Bean:你写的 @Controller 或 @RestController 类,本质上和 @Service、@Repository 一样,由 Spring 容器实例化、组装依赖、管理生命周期
  2. DispatcherServlet 从容器获取 Bean:前端控制器 DispatcherServlet 本身被注册为 Spring 管理的 Bean,它通过容器查找 HandlerMapping、HandlerAdapter、Controller 等协作组件
  3. 依赖注入贯穿 Web 层:Controller 中注入 Service、Service 中注入 Repository,这条依赖链完全由容器的 DI 机制支撑

非 Web 容器 vs Web 容器的演进

关键理解:WebApplicationContext 是 ApplicationContext 的子接口,它额外提供了 ServletContext 的访问能力。Spring MVC 没有创造新的容器,而是复用并扩展了已有的 IoC 容器。


DispatcherServlet 如何从容器获取 Bean

DispatcherServlet 的容器层级

在典型的 Spring MVC 应用中,存在两个 IoC 容器层级:

层级容器类型职责包含的 Bean
父容器(Root Context)WebApplicationContext管理业务层和数据层 BeanService、Repository、DataSource、TransactionManager
子容器(Servlet Context)WebApplicationContext管理 Web 层 BeanController、ViewResolver、HandlerMapping、Interceptor

父子容器规则:

  • 子容器可以访问父容器中的 Bean(Controller 可以注入 Service)
  • 父容器不能访问子容器中的 Bean(Service 不能注入 Controller)
  • 这种隔离确保了 Web 层组件不会污染业务层,同时业务层可以被多个 DispatcherServlet(子容器)共享

请求处理时的容器交互

当一个 HTTP 请求到达时,DispatcherServlet 与容器的交互流程如下:

核心洞察:

  • DispatcherServlet 不是靠自己 new 出 Controller,而是像所有普通 Bean 一样,通过 applicationContext.getBean() 从容器的 Bean 定义注册表中查找
  • Controller 中的 @Autowired 依赖(如 Service)在容器初始化阶段就已经完成注入,请求到来时直接调用即可
  • 这意味着:Spring MVC 的请求处理本质上是容器内 Bean 之间的协作调用

Controller 也是 Bean

Controller 的生命周期

你写在 @Controller 或 @RestController 类中的每一个实例变量、每一个 @Autowired 字段,都遵循与普通 Bean 完全相同的规则:

与普通 Bean 的唯一区别:Controller 类上多了一层 Web 语义标记(@Controller 或 @RestController),使得 RequestMappingHandlerMapping 能够识别它并建立 URL 到方法的映射。但实例化、注入、生命周期管理完全由容器负责。

提及但不展开的 MVC 注解

以下注解是 Spring MVC 请求映射和处理的核心,但本章不展开讲解其用法,仅列出名称供后续章节学习:

  • @Controller:标记类为 Web 控制器(本质上是 @Component 的派生)
  • @RestController:@Controller + @ResponseBody 的组合
  • @RequestMapping 及其派生:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
  • @RequestParam、@PathVariable、@RequestBody、@ResponseBody
  • @ModelAttribute、@ExceptionHandler、@ControllerAdvice

这些注解的详细用法、参数绑定规则、RESTful 设计实践,将在后续专门的 Spring MVC 章节中系统讲解。本章只需建立认知:它们都是标注在容器 Bean 上的元数据,由 DispatcherServlet 在运行时读取并驱动请求分发。


完整示例

场景简述

飞翔科技公司的学生成绩管理系统已经完成了 Service 层和 Repository 层的开发。架构师白歌要求小崔接入 Web 层,暴露 HTTP 接口供前端黄俪调用。小崔需要理解:Controller 不是凭空创建的,而是从已有的 IoC 容器中"生长"出来的 Web 端点。

操作前:纯容器内的 Service 层

// 操作前:只有 Service 和 Repository,没有 Web 层
package com.feixiang.student.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StudentService {
    
    private final StudentRepository studentRepository;
    
    @Autowired
    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }
    
    public Student findById(Long id) {
        return studentRepository.findById(id);
    }
}

此时应用可以通过 AnnotationConfigApplicationContext 启动,但没有任何 HTTP 入口。前端黄俪无法调用。

接入 Web 层后的完整代码

小崔添加 Controller 类:

package com.feixiang.student.controller;

import com.feixiang.student.domain.Student;
import com.feixiang.student.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 学生信息控制器
 * 
 * 注意:这个 Controller 是 Spring 容器中的一个普通 Bean,
 * 它的 StudentService 依赖在容器启动时就已经注入完成。
 */
@RestController
@RequestMapping("/api/students")
public class StudentController {
    
    private final StudentService studentService;
    
    @Autowired
    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }
    
    @GetMapping("/{id}")
    public Student getStudent(@PathVariable Long id) {
        return studentService.findById(id);
    }
}

启动类(Spring Boot 自动配置内嵌 Tomcat 和 DispatcherServlet):

package com.feixiang.student;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudentManagementApplication.class, args);
    }
}

操作后运行结果及分析

启动应用,控制台输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.7.18)

2024-05-20 11:30:15.123  INFO 12345 --- [main] o.s.b.w.e.t.TomcatWebServer          : 
    Tomcat initialized with port(s): 8080 (http)
2024-05-20 11:30:15.456  INFO 12345 --- [main] o.s.b.w.s.ServletContextInitializerBeans : 
    Mapping servlet: 'dispatcherServlet' to [/]
2024-05-20 11:30:15.789  INFO 12345 --- [main] c.f.s.StudentManagementApplication     : 
    Started StudentManagementApplication in 2.456 seconds

使用 curl 测试接口:

curl http://localhost:8080/api/students/2024001

响应:

{
    "id": 2024001,
    "name": "小崔",
    "major": "计算机科学与技术",
    "score": 92.5
}

分析:

  1. 容器启动:StudentManagementApplication 启动时,@SpringBootApplication 触发组件扫描,StudentController、StudentService、StudentRepository 全部被注册为 Bean。
  2. 依赖注入:StudentController 的构造器参数 StudentService 在容器初始化阶段被注入。
  3. DispatcherServlet 注册:Spring Boot 自动配置将 DispatcherServlet 注册到 Tomcat,映射路径为 /。
  4. 请求处理:curl 请求到达时,Tomcat 交给 DispatcherServlet,后者从 Servlet WebApplicationContext 中查找 HandlerMapping,映射到 StudentController.getStudent() 方法,调用并返回 JSON。

整个过程中,Controller 没有离开过容器。它和普通 Service 的区别仅在于:它的方法被 URL 映射元数据标注,可以被 DispatcherServlet 通过反射调用。


易错场景与面试考点

易错场景:试图在 Controller 中手动 new Service

小崔最初不理解 Controller 是容器 Bean,写出了这样的代码:

// 错误示范:手动创建 Service,脱离容器管理
@RestController
@RequestMapping("/api/students")
public class StudentController {
    
    private final StudentService studentService = new StudentService(new StudentRepository());
    // ← 错误!手动 new 出来的对象不在 Spring 容器中
    
    @GetMapping("/{id}")
    public Student getStudent(@PathVariable Long id) {
        return studentService.findById(id);
    }
}

后果:

  • StudentRepository 没有 DataSource 注入,因为手动 new 的对象不经过容器
  • StudentService 上的 @Transactional 失效,因为 AOP 代理由容器创建
  • 如果 StudentRepository 依赖 JdbcTemplate,则 NullPointerException

正确做法:所有依赖必须通过容器注入,Controller 中绝不应该出现 new Service()。

面试考点

Q:DispatcherServlet 和 ApplicationContext 是什么关系?

DispatcherServlet 是 Spring MVC 的前端控制器,它本身被注册为 Spring 容器中的一个 Bean。在运行时,它通过持有的 WebApplicationContext 引用,从容器中查找 HandlerMapping、HandlerAdapter、Controller 等协作组件。可以说,DispatcherServlet 是容器的使用者,而非容器的替代者。

Q:Spring MVC 中为什么需要父子容器?

父容器(Root Context)管理业务层和数据层 Bean,子容器(Servlet Context)管理 Web 层 Bean。父子容器实现了关注点隔离:Web 层组件可以访问业务层,但业务层不感知 Web 层。同时,多个 DispatcherServlet 可以共享同一个父容器中的 Service 和 Repository,避免重复创建。

Q:Controller 是单例还是多例?

默认是 singleton(单例),与所有其他 Spring Bean 一样。这意味着 Controller 的实例变量会被所有请求共享,因此 Controller 中不应持有请求级别的可变状态。如果需要请求级状态,应使用方法参数或 ThreadLocal。

Q:Spring Boot 中为什么没有显式配置 DispatcherServlet?

Spring Boot 的 DispatcherServletAutoConfiguration 自动检测类路径中的 spring-webmvc,自动创建 DispatcherServlet Bean 并注册到内嵌 Tomcat。开发者只需写 Controller,无需关心 Servlet 注册细节。这是自动配置哲学的典型体现。


教程学习路径总结

至此,本教程的 Spring Core 核心内容已全部覆盖。让我们回顾从容器到 Web 的完整学习路径:

学习建议:

  1. 不要跳过容器基础:Spring MVC 的所有魔法都建立在 IoC 容器之上。如果不懂 Bean 生命周期和依赖注入,遇到 @Transactional 失效、AOP 不生效等问题时将无从下手。
  2. 重视条件注解:@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean 是阅读 Spring Boot 源码和排查自动配置问题的必备工具。
  3. 从容器视角看 Web:当你写 @RestController 时,记住你只是在定义一个带有 URL 映射元数据的普通 Bean。请求处理的本质是容器内 Bean 的协作调用。

愿你朝华相顾,愿你前程似锦。 容器之道,一通百通。