Spring MVC

144 阅读10分钟

基本介绍

官方介绍

英文:

Spring Web MVC is the original web framework built on the Servlet API and has been included inthe Spring Framework from the very beginning. The formal name, “Spring Web MVC, ” comes from the name of its source module ( spring-webmvc), but it is more commonly known as “Spring MVC”

来自: docs.spring.io/spring-fram…

中文:

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

从上述定义我们可以得出两个关键信息:

  1. Spring MVC 是⼀个 Web 框架。

  2. Spring MVC 是基于 Servlet API 构建的。

MVC定义

​ MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。

image-20230509083159984
  • Model(模型)是应⽤程序中⽤于处理应⽤程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
  • View(视图)是应⽤程序中处理数据显示的部分。通常视图是依据模型数据创建的
  • Controller(控制器)是应⽤程序中处理⽤户交互的部分。通常控制器负责从视图读取数据,控制⽤户输⼊,并向模型发送数据。

MVC 和 Spring MVC 的关系

  • MVC 是⼀种思想,⽽ Spring MVC 是对 MVC 思想的具体实现的框架.
  • Spring MVC:Spring MVC是一个基于MVC设计模式和Serverlet API实现的Web项目, 同时Spring MVC又是Spring 框架中的一个WEB模块,它是随着Spring的诞生而存在一个框架。
  • Spring和Spring MVC诞生的历史是比较久远的,再他们之后才有了Spring Boot之后.
  • 现在绝⼤部分的 Java 项⽬都是基于 Spring(或 Spring Boot)的,⽽ Spring 的核⼼就是 Spring MVC。也就是说 Spring MVC 是 Spring 框架的核⼼模块,⽽ Spring Boot 是 Spring 的脚⼿架,因此我们也可以说,现在市⾯上绝⼤部分的 Java 项⽬约等于 Spring MVC 项⽬.

如何在创建项目时候使用Spring MVC框架

在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架,如下图所示:

image-20230509083723677

简单来说, Spring MVC是一切项目的基础, 以后创建的所有的Spring和Spring Boot项目基本都是基于Spring MVC的.

掌握技能

  1. 连接的功能: 将用户(浏览器)和Java程序连接起来, 也就是在浏览器输入URL地址之后, 能够在程序中匹配到响应的方法.
  2. 获取参数的功能: 用户访问的时候会带一些参数, 在程序中想办法获取到参数.
  3. 输出数据的功能: 执行了业务逻辑后, 要把程序执行的结果返回给用户.

连接的功能

方法一: 使用@RequestMapping("/xxx")

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@ResponseBody // 放在这里表示这个类中加了@RequestMapping的方法返回都是非静态页面
@RequestMapping("/user") // 一级目录(可有可无)
public class UserController {
    
	// @ResponseBody // 放在这里, 表示这个方法返回的非静态页面
    @RequestMapping("/sayhi") // 二级目录
    public String sayHi() {
        return "hello world!";
    }
}

image-20230509085825082

  • @Response注解表示的是返回一个非静态页面的数据. 如果没有加@Response注解,会去resource/static下面找静态页面, 然后将其返回.

image-20230508193121902

  • @RequestMapping

    • 既能修饰类(可选), 也能修饰方法
    • 默认情况下,既支持GET请求方式,也支持POST请求方式
  • @RequestMapping 参数扩展,使得只支持某个请求方式

image-20230508194754429

方法二: 使用@PostMapping("\XXX")和@GetMapping("\XXX")

@PostMapping注解只支持POST的请求方式, @GetMapping只支持GET的请求方式.

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@ResponseBody // 放在这里表示这个类中加了@RequestMapping的方法返回都是非静态页面
@RequestMapping("/user") // 一级目录 (可有可无)
public class UserController {

//    @ResponseBody // 放在这里, 表示这个方法返回的非静态页面
    @RequestMapping("/sayhi") // 二级目录
    public String sayHi() {
        return "hello world!";
    }

    // 设置为只支持POST请求方式
    // 方式1
    @RequestMapping(method=RequestMethod.POST, value = "sayhi2") // 设置为只支持POST请求方式
    public String sayHi2() {
        return "hello world!";
    }

    // 方式2
    @PostMapping("sayhi3") // 设置为只支持POST请求方式
    public String sayHi3() {
        return "hello world!";
    }

    // 设置为只支持GET请求方式
    @GetMapping("sayhi4") // 设置为只支持POST请求方式
    public String sayHi4() {
        return "hello world!";
    }
}

获取用户请求参数

获取单个参数

package com.example.demo.model;

import lombok.Data;

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private  int  age;
}
   @RequestMapping("/getuserbyid") // 路由名字如果是大小写组合在不同系统下有所差异, 为了避免差异, 全部写成小写
    public UserInfo getUserById(Integer id) { // 参数必须得是包装类
        // 不查数据库, 伪代码, 返回用户对象
        UserInfo userInfo = new UserInfo();
        userInfo.setId(id == null ? 0 : id);
        userInfo.setUsername("张三");
        userInfo.setAge(18);
        return userInfo;
    }

image-20230508202109688

获取多个参数

前端的参数的顺序和后端的参数顺序不一定要相同,后端是通过参数名识别参数的

   // 获取多个参数
    @RequestMapping("/login")
    public String login(String username, String password) {
        return "用户名: " + username + " | 密码: " + password;
    }

image-20230509090829517

获取对象

    // 获取对象
    @RequestMapping("/reg")
    public String reg(UserInfo userInfo) {
        return "用户信息: " + userInfo;
    }

image-20230509091221638

扩展:参数重命名

  • 使用@RequestParam注解

    // 参数重命名
    @RequestMapping("/login")
    public String login(@RequestParam("name") String username, String password) {
        return "用户名: " + username + " | 密码: " + password;
    }
    

image-20230509091716616

注意:

  • 如果在参数中添加@RequestParam注解, 那么前段一定要传递此参数, 否则就会报错, 如下图所示:

    image-20230509092052136

  • 想要解决上诉问题, 可以在@RequestParam里面添加required=false

    @RequestMapping("/login")
    	public String login(@RequestParam(value = "name", required = false) String 	username, String password) {
    	return "用户名: " + username + " | 密码: " + password;
    }
    

接收JSON格式的数据

  • 不使用**@RequestBody**。从下面的结果也可以看到,不使用该注解,服务器端就无法实现JSON数据的接收。但是可以接收表单数据。

    @RequestMapping("/reg")
    public String reg(UserInfo userInfo) {
        return "用户信息: " + userInfo;
    }
    

image-20230509162651251

image-20230509163456168

  • 使用**@RequestBody**。可以接收JSON数据. 该注解表示接受的数据是JSON数据格式。如果是表单数据格式就会报错。

        @RequestMapping("/reg")
        public String reg(@RequestBody UserInfo userInfo) {
            return "用户信息: " + userInfo;
        }
    

    image-20230509163101235

    image-20230509163326197

扩展:

  • postman基本使用

image-20230509162551457

从URL地址中获取参数(不是从URL地址中的参数部分获取参数)

 @RequestMapping("/hero/{id}/{name}")  // /{id}/{name}为参数
    public String getHerInfo(@PathVariable  Integer id, @PathVariable String name) {
        return "ID: " + id + " | Name: " + name;
    }

image-20230509165219161

image-20230509163756994

扩展:

  • Spring Boot热加载:JVM只能识别target文件夹下的class包,当在运行项目的时候,修改了源代码,如果没有热加载,就要手动重新启动项目,在启动过程中会重新生成target下的class包,有了热加载就会在项目运行的时候,IDEA会检测到源代码已经发生变更,就会帮忙重启,就不用手动重启项目。

上传文件

// 从配置文件中读取图片的保存路径
@Value("${img.path}")
private String imgPath;

// 上传文件
@RequestMapping("upimg")
public boolean upload(Integer uid, @RequestPart("img") MultipartFile file) {
    boolean res = false;

    // 1. 目录 imgpath 
    // 2. 图片名称(图片名不能重复) uuid
    // 3. 获取原上传图片的格式
    String fileName = file.getOriginalFilename();
    fileName = fileName.substring(fileName.lastIndexOf("."));
    fileName = UUID.randomUUID().toString() + fileName;

    // 4. 保存图片到本地目录
    try {
        file.transferTo(new File(imgPath + fileName));
        res = true;
    } catch(IOException e) {
        log.error("上传图片失败: " + e.getMessage());
    }

    return res;
}

image-20230509201537429

扩展:

  • 不同运行平台的配置文件设置

    • 新建不同平台的配置文件

    image-20230509195332948

  • 在主配置文件中, 设置运行的配置文件

    # application-dev.yml
    # 开发环境的配置文件
    
    # 图片保存路径
    img:
      path: D:/04_IDEA/
    
    # application-prod.yml
    # 生产环境的配置文件
    
    #图片保存路径
    img:
      path: /home/sgj/CPP102/photos
    
    # application.yml
    # 设置配置环境的运行平台
    spring:
      profiles:
        active:  prod
    
    public class UserController {
    
        // 从配置文件中读取图片的保存路径
        @Value("${img.path}")
        private String imgpath;  // 读取配置```文件
    }
    

获取cookie/session/header

  • 获取cookie

    • 使用Servlet获取Cookie
    @RequestMapping("/cookie")
    public void getCookie(HttpServletRequest request) {
        // 获取全部的cookie
        Cookie[] cookies = request.getCookies();
        for (Cookie item : cookies) {
            log.info("cookie key: " + item.getName() + " | Cookie Value: ", item.getValue());
        }
    }
    
    • 使用@CookieValue注解读取特定cookie

      @RequestMapping("/cookie2")
      public String getCookie2(@CookieValue("sgj")String cookie) {  // 如果想获取多个, 那么在参数列表里放多个注解
          // 获取特定的cookie
          return "Cookie Value: " + cookie;
      }
      
  • 获取header

    • 使用Serverlet

      @RequestMapping("/header1")
      public String getHeader1(HttpServletRequest request) {
          // 获取特定的cookie
          return "header: " + request.getHeader("User-Agent");
      }
      

      image-20230509210018418

    • 使用注解@RequestHeader

      @RequestMapping("/header2")
      public String getheader2(@RequestHeader("User-Agent")String userAgent) {
          // 获取特定的cookie
          return "User-Agent: " + userAgent;
      }
      

      image-20230509210314308

  • 存储和获取session

    • 存储session代码实现

      @RequestMapping("/setsess")
      public boolean setSession(HttpServletRequest request) {
          boolean result = false;
          // 1. 得到HttpSession对象
          HttpSession session = request.getSession(true); // true表示没有会话就创建一个新的会话, false表示有就用, 没有就不用
          // 2. 使用setAtt设置值
          session.setAttribute("userinfo", "userinfo");
          result = true;
      
          return result;
      }
      

      image-20230510093655202

    • 获取Session(获取session前,一定要先存储)

      // 获取session
      // 方式一
      @RequestMapping("/getsess1")
      public String getSession1(HttpServletRequest request) {
          String res = null;
          // 1. 得到HttpSession对象
          HttpSession session = request.getSession(false); // false: 如果存在该对话就该对话, 否则返回(不创建新的对话)
          // 2. getAtt得到Session信息                          // 只有存的时候才会设置成true
          if (session != null && session.getAttribute("userinfo") != null) {
              res = (String) session.getAttribute("userinfo");
          }
          return res;
      }
      
      // 方式2
      @RequestMapping("/getsess2") // 如果下面注解参数没有"required=false",那么当session中不存在此属性的时候, 就会报错
      public String getSession2(@SessionAttribute(value="userinfo", required=false)String userinfo) {
          return "会话: " + userinfo;
      }
      

返回数据给前端

  • 返回静态页面: 默认返回的是静态页面

    @Controller
    public class TestController {
    
        @RequestMapping("/sayhi")
        public String sayHi() {
            return "hello.html";  // 返回的是名叫hello.html的静态页面, 在static文件夹下可以找到
        }
    }
    

    image-20230510100220342

    image-20230510100947719

  • 返回非静态页面

    • 使用@ResponseBody注解, 返回非静态页面

      • 修饰类, 表示当前类中所有的方法都会返回一个非静态页面的数据.
      @Controller
      @ResponseBody
      public class TestController {
          @RequestMapping("/sayhi")
          public String sayHi() {
              return "hello.html";
          }
      }
      

      image-20230510101200107

      • 修饰方法, 表示当前方法返回的是非静态页面的数据.
      @Controller
      public class TestController {
          @ResponseBody
          @RequestMapping("/sayhi")
          public String sayHi() {
              return "hello.html";
          }
      }
      

      image-20230510101344811

    • 使用@RestController注解, 可以代替@Controller和@ResponseBody两个注解

      @RestController
      public class TestController {
      
          @RequestMapping("/sayhi")
          public String sayHi() {
              return "hello.html";
          }
      }
      

      image-20230510101701419

  • 请求转发(forward)

    @Controller // 不能使用@RestController注解, 不然返回就是非静态页面
    public class TestController {
        // 请求转发实现方式1
        @RequestMapping("/fw1")
        public String myForward1() {
            return "forward:/hello.html";
        }
    
        @RequestMapping("/fw11")
        public String myForward11() {
            return "/hello.html";  // 也可以把“forword:”给去掉, 也就是返回静态页面, 就是请求转发的过程
        }
    
        // 请求转发实现方式2
        @RequestMapping("/fw2")
        public void myForward2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.getRequestDispatcher("/hello.html").forward(request, response);
        }
    }
    

    image-20230510172544023

  • 请求重定向(redirect) (不推荐)

    // 请求重定向实现方式一
    @RequestMapping("/rd1")
    public String myRedirect1() {
        return "redirect:/hello.html";
    }
    
    // 请求重定向实现方式二
    @RequestMapping("rd2")
    public void myRedirect2(HttpServletResponse response) throws IOException {
        response.sendRedirect("/hello.html");
    }
    
    • 请求转发和请求重定向的区别
      • 请求转发是服务端帮用户实现的; 请求重定向的请求发生在客户端(浏览器端), 服务器端不会替客户端请求的.
      • 请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。
      • 请求重定向地址发⽣变化,请求转发地址不发⽣变化。
      • 请求重定向与直接访问新地址效果一致,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问, 例如资源在static夹的一级目录, 但是请求转发的路由是二级目录, 那么就会出错, 这是因为会去static文件夹下的二级目录中找该资源, 一级目录的资源就会丢失.
  • 扩展:

    • @RestController相当于组合注解等于@Controller + @ResponseBody

      image-20230510101937175

练习: 实现计算器功能(前后端交互)

  • 前端页面

    `<!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>计算器示例</title>
    </head>
    <body>
        <form action="calc">
            <h1>计算器</h1>
           数字1:<input name="num1" type="text"><br>
           数字2:<input name="num2" type="text"><br>
            <input type="submit" value=" 点击相加 ">
        </form>
    </body>
    </html>
    

    image-20230510151416321

  • 后端处理

    package com.example.demo.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class CalcController {
        @RequestMapping("/calc")
        public String calc(Integer num1, Integer num2) {
            if (num1 == null || num2 == null)
                return "<h1>参数错误</h1>" + "</h1><a href='javascript:history.go(-1);'>返回</a>";
            return "<h1>结果: " + (num1 + num2) + "</h1><a href='javascript:history.go(-1);'>返回</a>";
        }
    }
    
  • 测试

    • 输入http://127.0.0.1:8080/calc.html

      image-20230510151604504
    • 输入需要计算的值

      image-20230510151714463

热部署

介绍

IDEA热部署(热加载): 在项目运行的时候, 修改源代码, IDEA可以实时编译源代码, 生成字节码, 自动帮开发者重启Spring Boot项目, 使得修改代码之后能够"实时"(有点延迟), 的看到新效果的目的.

实施步骤

  1. 在pom.xml中添加Spring Boot开发者框架支持(一般在创建项目的时候, 就会选择该框架)

    image-20230510152324398

  2. 开启IDEA的自动编译, 此步骤需要设置IDEA的两个settings, 一个是当前项目的settings, 另一个是新项目的settings, 具体操作如下图所示:

    image-20230510152556308image-20230510152735385

    image-20230510152745117
  3. 开启运行中的热部署. 不同的版本配置不同, 配置分为2021.2之前的版本, 和之后的版本配置

    • 新版本的配置

      image-20230510153157194
    • 老版本的配置

      image-20230510153245028

      image-20230510153314558
  4. 启动项目使用debug, 而非run运行

    image-20230510153801689