当前,使用Spring Boot的spring-boot-starter-web可以快速构建Web应用,其内嵌了Spring MVC。本文旨在深入理解Spring MVC的基础原理和工作流程

一、特点

  1. 轻量级:基于核心Spring IOC容器,非侵入式设计。
  2. 高效:基于请求-响应模型的MVC框架。
  3. 与Spring容器无缝集成:可在Spring IOC容器中注册Bean,方便依赖注入和管理。
  4. 约定优于配置:提供了很多默认配置,简化开发。
  5. 功能丰富:原生支持RESTful风格、数据验证、格式化、拦截器等。

二、原理

核心是前端控制器原理,所有请求通过一个统一的入口DispatcherServlet进行分发

  1. 发起的请求被前置的控制器拦截到请求
  2. 根据请求参数生成代理请求并找到对应的实际控制器
  3. 控制器处理请求
  4. 创建数据模型
  5. 访问数据库并将模型响应给中心控制器
  6. 控制器使用模型和视图渲染视图结果并返回给中心控制器,最后返回给用户

三、执行流程

在这里插入图片描述

  1. 接收请求DispathcerServlet表示前置控制器,是整个SpringMvc的控制中心,用户发出请求,DispathcerServlet接受请求并拦截请求

    比如有个URL地址:http://localhost8080/SpringMvc/hello。

    这个可以拆成三部分:

    服务器域名:http://localhost8080

    SpringMvc部署在服务器上的web站点

    hello表示控制器

    整合起来就是:请求位于服务器localhost:8080上的SpringMvc站点的hello控制器

  2. 查找处理器DispatcherServlet查询 HandlerMapping(处理器映射器),根据URL找到对应的Handler(处理器,通常就是我们的Controller)及其拦截器,形成HandlerExecutionChain(处理器执行链)。

  3. 执行处理器DispatcherServlet通过 HandlerAdapter(处理器适配器)来适配并执行找到的HandlerHandlerAdapter会调用我们Controller中的具体方法,处理业务逻辑(访问Service、数据库等)。

  4. 处理返回结果:Controller方法执行完毕后,会返回一个结果(可能是ModelAndView,也可能是一个数据对象)。HandlerAdapter将此结果返回给DispatcherServlet

  5. 解析视图:如果返回的结果包含视图信息,DispatcherServlet会调用ViewResolver(视图解析器)将逻辑视图名解析为具体的View对象(如JSP、Thymeleaf模板)。

  6. 渲染响应DispatcherServlet将模型数据传递给View对象,由View对象进行渲染(生成HTML等),最终将渲染结果通过HttpServletResponse返回给用户。

​ 按自己的话来说:

  1. 首先作为前端控制器DispatcherServlet接收到用户请求
  2. 然后查找对应的Handler处理映射器,返回处理程序执行链给前端控制器。
  3. 紧接着控制器要着手去按照执行链去请求适配器HandlerAdapter(也就是Controller)执行,经过Service和数据库返回具体信息如ModelAndView给前端控制器。
  4. 得到了ModelAndView后控制器要请求视图解析知道要给用户呈现什么页面,返回View对象,最后响应客户请求

四、使用注解开发SpringMvc

​ 现代Spring MVC开发几乎完全基于注解,极大简化了配置。

1. 创建Controller

Controller提供访问应用程序的行为,通常通过接口定义注解实现

接口实现(比较老)当前开发不再使用,只做了解

//实现接口,就是一个控制器了
public class ControllerTest1 implements Controller{
 public ModelAndView handleRequest(HttpServletRequest req,HttpServletResponse res)throws Exception{
     ModelAndView mv=new ModelAndView();
     mv.addObject("msg","Test1Controller");
     mv.setViewName("test");
     return mv;
 }
}

然后在Spring配置文件中注册请求Bean;name对应请求路径,class对应请求的类

<bean name="/t1" class="com.example.controller.ControllerTest1"

当前开发主要使用注解方式@Controller

使用@Controller注解将一个类声明为控制器,Spring IOC容器会自动扫描并注册它。

@Controller // 让Spring IOC容器初始化时自动扫描到
@RequestMapping("/user") // 类级别的RequestMapping,可省略
public class UserController {

    @RequestMapping("/hello") // 映射URL路径
    public String hello(Model model) {
        model.addAttribute("msg", "Hello, Spring MVC!");
        return "hello"; // 返回逻辑视图名,由视图解析器处理
    }
}

2. 配置组件扫描和视图解析器

在Spring配置文件中(如springmvc-servlet.xml),需要开启注解扫描并配置视图解析器。

<!-- 自动扫描包,让指定包下的注解生效 -->
<context:component-scan base-package="com.example.controller"/>

<!-- 视图解析器:将逻辑视图名解析为具体页面路径 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/> <!-- 前缀 -->
    <property name="suffix" value=".jsp"/> <!-- 后缀 -->
</bean>

对于上述Controller,访问 /user/hello,视图解析器会将其指向 /WEB-INF/jsp/hello.jsp文件。

3. @RestController注解

它是@Controller@ResponseBody的组合注解。它的作用是直接将方法的返回值写入HTTP响应体,通常用于返回JSON/XML数据,而不是跳转到一个视图页面。这是开发RESTful API的基石。

@RestController // 等价于 @Controller + @ResponseBody
public class ApiController {
    @GetMapping("/data")
    public User getData() {
        return new User("张三", 25); // 方法返回的对象将被转换为JSON
    }
}

五、RestFul风格

概念

​ 资源定位以及资源操作的风格,让软件更简洁、有层次、更易于实现缓存等机制

平时我们URL地址是localhost:8080/method?add,RestFul风格的URL地址就是localhost:8080/method/add

功能

​ 互联网中的所有事务可抽象成为资源

操作 传统 RestFul URL
查询用户 GET /user/queryUser?id=1 GET /user/1
新增用户 POST /user/saveUser POST /user
更新用户 POST /user/updateUser PUT /user/1
删除用户 GET /user/deleteUser?id=1 DELETE /user/1
  • 通过使用POSTDELETEPUTGET,使用不同的方法对资源进行对应操作(添加、删除、修改、请求)

传统方式操作资源时方法单一,使用POST和GET,比如:

http://127.0.0.1/item/queryItem.action?id=1 查询GET

http://127.0.0.1/item/saveItem.action 新增,POST

​ 但是使用RestFul操作资源时,虽然请求地址相同,但是可以通过不同的请求方式来实现不同的效果。这一定程度上提高了安全性


在Spring MVC中实现

​ 在SpringMVC中我们可以使用@PathVariable注解,让方法参数的值对应绑定到一个URL模板变量上。

@RestController
public class RestFulController {

    // @GetMapping 是 @RequestMapping(method = RequestMethod.GET) 的快捷方式
    @GetMapping("/commit/{p1}/{p2}")
    public String index(@PathVariable int p1, @PathVariable int p2, Model model) {
        int result = p1 + p2;
        model.addAttribute("msg", "结果:" + result);
        return "test";
    }

    // 更典型的RESTful API示例
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user); // 返回200状态码和用户数据
    }
}

六、结果跳转方式(了解即可)

在SpringMVC出现以前,有几种方法可以实现结果跳转:

  1. 通过设置ModelAndView对象,根据View名称和视图解析器跳到指定的页面。

  2. ServletAPI中我们不需要视图解析器,通过

    1. HttpServletResponse进行输出
    2. 通过HttpServletResponse实现重定向sendRedirect()
    3. 通过HttpServletResponse实现转发getRequestDispatcher()
  3. 到SpringMVC,我们无需视图解析器即可实现该功能,只需在原基础上注释掉解析器,在控制层方法上添加return “Redirect:/index.jsp”

可以点击回顾

七、接收请求参数及数据回显

1. 接收请求参数

@RequestParam:获取URL查询参数或表单数据。

@GetMapping("/search")
public String search(@RequestParam("keyword") String keyword, 
                     @RequestParam(value = "page", defaultValue = "1") Integer page) {
    // 处理 /search?keyword=Java&page=2 的请求
    return "result";
}

@PathVariable:获取URL路径参数(如上文RestFul示例)。

@RequestBody:获取请求体中的JSON/XML数据,并转换为Java对象(常用于POST/PUT请求)。

@PostMapping("/user")
public User createUser(@RequestBody User user) { // 将请求体的JSON自动绑定到User对象
    return userService.save(user);
}

自动绑定到对象:表单提交时,参数名与对象属性名匹配即可自动绑定。

@PostMapping("/user")
public String saveUser(User user) { // 表单字段name="userName"会自动set到user.setUserName()
    // ...
    return "redirect:/user/list";
}

2. 数据回显

数据回显主要指将数据从Controller传递到View(如JSP)进行展示。

使用 Model对象:最常用方式。

@GetMapping("/user/edit/{id}")
public String editUser(@PathVariable Long id, Model model) {
    User user = userService.findById(id);
    model.addAttribute("user", user); // 将user对象放入model,在视图中通过 ${user} 获取
    return "user/edit";
}

使用 ModelAndView对象:可以同时设置视图名称和模型数据。

@ModelAttribute注解:可将方法返回值或方法参数自动添加到Model中。


八、乱码问题

​ 有时在提交表单查看页面时会发现乱码问题,一般是从后端启动开始就出现问题,我们最先想到的就是使用HttpServlet中的setEncodingCharset,但是试验后是无效的,这时我们会想到过滤器可以解决。在过滤器doFilter中调用setEncodingCharset来解决

在web.xml中配置Spring MVC提供的字符编码过滤器,是解决POST请求乱码的标准做法。

<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <!-- 强制请求和响应都使用该编码 -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> <!-- 过滤所有请求 -->
</filter-mapping>

九、拦截器

​ 是AOP思想在Web层面的应用,用于对处理器进行预处理和后处理。SpringMVC的处理器类似Servlet开发中的过滤器Filter,开发者可以自己定义一些拦截器来实现特定的功能

过滤器和拦截器的区别

方面 过滤器 拦截器
归属 Servlet规范,任何Java Web工程都能用 Spring MVC框架自身的组件
拦截范围 更广,能拦截所有资源(JSP, JS, CSS等) 只拦截访问控制器方法 (Controller) 的请求
依赖 依赖Servlet容器(如Tomcat) 依赖Spring MVC框架

自定义拦截器

自定义的需要实现拦截器的3个方法:preHandler、postHandler和afterCompletion

public class MyInterceptor implements HandlerInterceptor {
    
    // 在Controller方法执行前调用。return true则放行,false则中断。
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("预处理...");
        return true; // 放行
    }
    
    // 在Controller方法执行后,视图渲染前调用。
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("后处理...");
    }
    
    // 在整个请求完成(视图渲染完毕)后调用,常用于资源清理。
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("请求完成...");
    }
}

最后:正不断学习,如有错误请大佬指出🙏

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐