SpringMVC详解

发布于:2025-08-01 ⋅ 阅读:(22) ⋅ 点赞:(0)

SpringMVC 详解

1. SpringMVC 概述

SpringMVC 的基本概念及其在 Java Web 开发中的作用

Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。

在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:

  • Spring 家族原生产品,与IOC容器等基础设施无缝对接
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
    性能卓著,尤其适合现代大型、超大型互联网项目要求
  1. MVC(Model-View-Controller)设计模式简介
    MVC是一种经典的软件架构模式,将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。模型负责数据处理和业务逻辑,视图负责用户界面展示,控制器作为中间层协调模型与视图的交互。该模式最早由Trygve Reenskaug在1979年提出,广泛应用于Web开发、桌面应用和移动应用领域。

  2. SpringMVC 与传统 Servlet 的对比
    传统 Servlet代码写法

@WebServlet("/welcome")
public class WelcomeServlet extends HttpServlet {
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<form method='post'>");
        out.println("<input type='text' name='name' placeholder='Enter your name'>");
        out.println("<button type='submit'>Submit</button>");
        out.println("</form>");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        String name = request.getParameter("name");
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<p>Hello, " + name + "!</p>");
    }
}

Spring MVC 写法 调用业务即可

@Controller
@RequestMapping("/welcome")
public class WelcomeController {
    
    @GetMapping
    public String showForm() {
        return "welcomeForm";
    }

    @PostMapping
    public String handleSubmit(@RequestParam String name, Model model) {
        model.addAttribute("message", "Hello, " + name + "!");
        return "welcomeResult";
    }
}

2. SpringMVC 核心组件
  • DispatcherServlet:前端控制器的作用与配置
    它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
  • HandlerMapping:请求映射处理器
    它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
  • Controller:业务逻辑处理
    执行具体业务逻辑,处理请求参数并生成响应(数据或视图)。需关注与领域模型的交互设计。
  • ViewResolver:视图解析器
    视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]
  • ModelAndView:数据模型与视图绑定
    封装模型数据(键值对集合)和目标视图信息。模型数据通过ModelMap传递至视图层渲染,视图可为逻辑名称(如"home")或直接视图对象。在RESTful服务中通常由@ResponseBody替代。[协调员]
3. SpringMVC 请求处理流程
  • 请求从客户端到响应的完整流程
客户端请求 --> DispatcherServlet
               |
               v
           HandlerMapping
               |
               v
           HandlerAdapter
               |
               v
           Controller
               |
               v
           ModelAndView
               |
               v
           ViewResolver
               |
               v
           View (JSP, Thymeleaf, etc.)
               |
               v
           响应内容 --> 客户端

  • 涉及的核心组件及其交互方式
  1. DispatcherServlet:• 前端控制器,负责拦截所有请求,并协调其他组件完成请求处理。
  2. HandlerMapping:• 请求映射器,负责将请求映射到具体的处理器(Controller)。
  3. HandlerAdapter:• 处理器适配器,负责调用处理器(Controller)的方法。
  4. Controller:• 处理器,负责处理具体的业务逻辑,并返回 ModelAndView 对象。
  5. ModelAndView:• 包含视图名称和模型数据的对象,用于传递给视图解析器。
  6. ViewResolver:• 视图解析器,负责解析视图名称,找到具体的视图对象。
  7. View:• 视图对象,负责渲染最终的响应内容。
  • 生命周期与关键执行点
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Controller
@RequestMapping("/demo")
public class LifecycleDemoController {

    // 初始化阶段(Bean生命周期)
    @PostConstruct
    public void init() {
        System.out.println("Bean初始化完成");
    }

    // 请求映射阶段
    @GetMapping("/process")
    @ResponseBody
    public String handleRequest(
            @RequestParam(required = false, defaultValue = "default") String param,
            @ModelAttribute User user) {
        
        System.out.println("接收到参数: " + param);
        System.out.println("绑定模型属性: " + user);
        
        // 业务处理
        String result = executeBusinessLogic(param);
        
        System.out.println("业务逻辑执行完成");
        return result;
    }

    private String executeBusinessLogic(String input) {
        return "处理结果: " + input.toUpperCase();
    }

    // 异常处理阶段
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public String handleError(RuntimeException ex) {
        System.out.println("捕获到异常: " + ex.getMessage());
        return "错误处理结果";
    }

    // 销毁阶段(Bean生命周期)
    @PreDestroy
    public void cleanup() {
        System.out.println("Bean即将销毁");
    }
}

// 模型类
class User {
    private String name;
    private int age;
    
    // getters and setters
}

配置类

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
    // 可添加拦截器、视图解析器等配置
}

关键执行点说明
初始化阶段
@PostConstruct标记的方法会在依赖注入完成后执行,用于资源初始化操作。此时ServletContext和ApplicationContext已可用。

请求处理阶段

参数绑定:@RequestParam处理查询参数,@ModelAttribute自动绑定表单数据到对象

业务方法执行:实际处理请求的核心逻辑

返回值处理:@ResponseBody将返回值直接写入HTTP响应体

异常处理阶段
@ExceptionHandler捕获控制器内抛出的指定异常,实现集中错误处理

销毁阶段
@PreDestroy标记的方法在Bean销毁前执行,用于释放资源

4. SpringMVC 注解详解
  • @Controller / @RestController:控制器声明
    RestController就相当于Controller+RequestBody
@Controller
public class HelloController {

    //handlers

    /**
     * handler就是controller内部的具体方法
     * @RequestMapping("/springmvc/hello") 就是用来向handlerMapping中注册的方法注解!
     * @ResponseBody 代表向浏览器直接返回数据!
     */
    @RequestMapping("/springmvc/hello")
    @ResponseBody
    public String hello(){
        System.out.println("HelloController.hello");
        return "hello springmvc!!";
    }
}

  • @RequestMapping / @GetMapping / @PostMapping:请求映射
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class ExampleController {

    // 使用 @RequestMapping 处理多个HTTP方法
    @RequestMapping(value = "/example", method = {RequestMethod.GET, RequestMethod.POST})
    public String handleExample() {
        return "This endpoint accepts both GET and POST requests";
    }

    // 使用 @GetMapping 只处理GET请求
    @GetMapping("/getExample")
    public String handleGetExample() {
        return "This is a GET request example";
    }

    // 使用 @PostMapping 只处理POST请求
    @PostMapping("/postExample")
    public String handlePostExample(@RequestBody String requestBody) {
        return "Received POST request with body: " + requestBody;
    }
}

  • @RequestParam / @PathVariable:参数绑定
    @RequestParam:从URL查询字符串获取参数(如/path?param=value)
 /**
 * 前端请求: http://localhost:8080/param/data?name=xx&stuAge=18
 * 
 *  使用@RequestParam注解标记handler方法的形参
 *  指定形参对应的请求参数@RequestParam(请求参数名称)
 */
@GetMapping(value="/data")
@ResponseBody
public Object paramForm(@RequestParam("name") String name, 
                        @RequestParam("stuAge") int age){
    System.out.println("name = " + name + ", age = " + age);
    return name+age;
}

@PathVariable:从URI模板中提取变量(如/path/{value})

 /**
 * 动态路径设计: /user/{动态部分}/{动态部分}   动态部分使用{}包含即可! {}内部动态标识!
 * 形参列表取值: @PathVariable Long id  如果形参名 = {动态标识} 自动赋值!
 *              @PathVariable("动态标识") Long id  如果形参名 != {动态标识} 可以通过指定动态标识赋值!
 *
 * 访问测试:  /param/user/1/root  -> id = 1  uname = root
 */
@GetMapping("/user/{id}/{name}")
@ResponseBody
public String getUser(@PathVariable Long id, 
                      @PathVariable("name") String uname) {
    System.out.println("id = " + id + ", uname = " + uname);
    return "user_detail";
}
5. Spring MVC响应数据
  1. handler 方法分析
    理解handler方法的作用和组成
    handler主要负责三个关键任务:
    接收请求参数:通过多种方式获取前端传递的数据。
    支持类型:查询参数(param)、JSON数据、路径变量(pathVariable)、共享域(如session或request属性)等。

调用业务逻辑:在方法体内调用业务层服务。
例如:service.xx(),其中xx()是业务方法。

响应前端数据:返回结果给客户端。
支持方式:直接返回JSON数据、页面(但模版页面跳转不在此讲解)、转发(forward)、重定向(redirect)等。

@GetMapping
public Object handler(简化请求参数接收){
    调用业务方法
    返回的结果 (页面跳转,返回数据(json))
    return 简化响应前端数据;
}
页面跳转控制

导入maven依赖

<!-- jsp需要依赖! jstl-->
<dependency>
    <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
    <version>3.0.0</version>
</dependency>

创建JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Title</title>
  </head>
  <body>
        <!-- 可以获取共享域的数据,动态展示! jsp== 后台vue -->
        ${msg}
  </body>
</html>

配置jsp视图解析器

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描

//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }
}

handler返回视图

/**
 *  跳转到提交文件页面  /save/jump
 *  
 *  如果要返回jsp页面!
 *     1.方法返回值改成字符串类型
 *     2.返回逻辑视图名即可    
 *         <property name="prefix" value="/WEB-INF/views/"/>
 *            + 逻辑视图名 +
 *         <property name="suffix" value=".jsp"/>
 */
@GetMapping("jump")
public String jumpJsp(Model model){
    System.out.println("FileController.jumpJsp");
    model.addAttribute("msg","request data!!");
    return "home";
}
转发和重定向

在 Spring MVC 中,Handler 方法返回值来实现快速转发,可以使用 redirect 或者 forward 关键字来实现重定向。

@RequestMapping("/redirect-demo")
public String redirectDemo() {
    // 重定向到 /demo 路径 
    return "redirect:/demo";
}

@RequestMapping("/forward-demo")
public String forwardDemo() {
    // 转发到 /demo 路径
    return "forward:/demo";
}

返回静态资源处理

SpringMVC 配置配置类

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.nie.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }
    
    //开启静态资源处理 <mvc:default-servlet-handler/>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}
返回JSON数据

可以在方法上使用 @ResponseBody注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。在前后端分离的项目中使用!

@GetMapping("/accounts/{id}")
@ResponseBody
public Object handle() {
  // ...
  return obj;
}

测试方法:

@RequestMapping(value = "/user/detail", method = RequestMethod.POST)
@ResponseBody
public User getUser(@RequestBody User userParam) {
    System.out.println("userParam = " + userParam);
    User user = new User();
    user.setAge(18);
    user.setName("John");
    //返回的对象,会使用jackson的序列化工具,转成json返回给前端!
    return user;
}

在这里插入图片描述

也可以直接在类上使用@ResponseBody responseBody可以添加到类上,代表默认类中的所有方法都生效!

@ResponseBody  
@Controller
@RequestMapping("param")
public class ParamController {
@RestController

类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解。
RestController源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
 
  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   * @since 4.0.1
   */
  @AliasFor(annotation = Controller.class)
  String value() default "";
 
}
6. RESTful API 设计
  • RESTful 风格 API 的设计原则
  1. 每一个URI代表1种资源(URI 是名词);
  2. 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
  3. 资源的表现形式是XML或者JSON
  4. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
  • 使用 SpringMVC 实现 RESTful 接口
    准备实体类
package com.nie.pojo;

public class User {

    private Integer id;
    private String name;

    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

准备controller

@RequestMapping("user")
@RestController
public class UserController {

    /**
     * 模拟分页查询业务接口
     */
    @GetMapping
    public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
                            @RequestParam(name = "size",required = false,defaultValue = "10")int size){
        System.out.println("page = " + page + ", size = " + size);
        System.out.println("分页查询业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟用户保存业务接口
     */
    @PostMapping
    public Object saveUser(@RequestBody User user){
        System.out.println("user = " + user);
        System.out.println("用户保存业务!");
        return "{'status':'ok'}";
    }

    /**
     * 模拟用户详情业务接口
     */
    @PostMapping("/{id}")
    public Object detailUser(@PathVariable Integer id){
        System.out.println("id = " + id);
        System.out.println("用户详情业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟用户更新业务接口
     */
    @PutMapping
    public Object updateUser(@RequestBody User user){
        System.out.println("user = " + user);
        System.out.println("用户更新业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟条件分页查询业务接口
     */
    @GetMapping("search")
    public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
                            @RequestParam(name = "size",required = false,defaultValue = "10")int size,
                            @RequestParam(name = "keyword",required= false)String keyword){
        System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);
        System.out.println("条件分页查询业务!");
        return "{'status':'ok'}";
    }
}
  • HATEOAS 与超媒体支持
    导入maven依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

以下是一个基于 Spring HATEOAS 的 Java 代码示例,展示如何为 REST API 添加超媒体支持:

import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @GetMapping("/{id}")
    public EntityModel<Product> getProduct(@PathVariable Long id) {
        Product product = productService.findById(id); // 假设从服务层获取产品
        
        // 创建包含产品和超链接的 EntityModel
        EntityModel<Product> model = EntityModel.of(product);
        
        // 添加自引用链接
        model.add(WebMvcLinkBuilder.linkTo(
            WebMvcLinkBuilder.methodOn(ProductController.class)
                .getProduct(id)).withSelfRel());
        
        // 添加相关资源链接
        model.add(WebMvcLinkBuilder.linkTo(
            WebMvcLinkBuilder.methodOn(ProductController.class)
                .getAllProducts()).withRel("products"));
        
        return model;
    }

    @GetMapping
    public CollectionModel<EntityModel<Product>> getAllProducts() {
        List<EntityModel<Product>> products = productService.findAll()
            .stream()
            .map(product -> EntityModel.of(product,
                linkTo(methodOn(ProductController.class)
                    .getProduct(product.getId())).withSelfRel()))
            .collect(Collectors.toList());
        
        return CollectionModel.of(products,
            linkTo(methodOn(ProductController.class)
                .getAllProducts()).withSelfRel());
    }
}

7.全局异常处理机智
异常处理的两种 方式
  • 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
  • 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 @Throws@ExceptionHandler),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。
基于注解异常声明异常类型

异常处理控制类,统一定义异常处理handler方法!

@RestControllerAdvice
public class GlobalExceptionHandler {
  
}

声明异常处理hander方法

/**
 * 异常处理handler 
 * @ExceptionHandler(HttpMessageNotReadableException.class) 
 * 该注解标记异常处理Handler,并且指定发生异常调用该方法!
 * 
 * 
 * @param e 获取异常对象!
 * @return 返回handler处理结果!
 */
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){
    
    return null;
}

/**
 * 当发生空指针异常会触发此方法!
 * @param e
 * @return
 */
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){

    return null;
}

/**
 * 所有异常都会触发此方法!但是如果有具体的异常处理Handler! 
 * 具体异常处理Handler优先级更高!
 * 例如: 发生NullPointerException异常!
 *       会触发handlerNullException方法,不会触发handlerException方法!
 * @param e
 * @return
 */
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){

    return null;
}

配置文件扫描控制器类配置

 <!-- 扫描controller对应的包,将handler加入到ioc-->
 @ComponentScan(basePackages = {"com.nie.controller",
 "com.nie.exceptionhandler"})
8.拦截器

拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求

以下是一个基于Java的简单拦截器实现代码,使用Spring框架的HandlerInterceptor接口。该拦截器可用于在请求处理前后执行特定逻辑(如日志记录、权限校验等)。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class CustomInterceptor implements HandlerInterceptor {

    // 请求处理前执行
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        System.out.println("前置处理: " + request.getRequestURI());
        // 此处可进行权限校验,返回false会中断请求
        return true;
    }

    // 请求处理后、视图渲染前执行
    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response, 
                          Object handler,
                          ModelAndView modelAndView) throws Exception {
        System.out.println("后置处理: " + request.getRequestURI());
    }

    // 请求完成后执行(视图渲染完毕)
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) throws Exception {
        System.out.println("请求完成: " + request.getRequestURI());
    }
}

在Spring Boot项目中,需通过配置类注册拦截器并指定拦截路径:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomInterceptor())
                .addPathPatterns("/api/**")  // 拦截路径
                .excludePathPatterns("/api/public/**"); // 排除路径
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到