基本介绍
官方介绍
英文:
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”
中文:
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为“Spring MVC” 。
从上述定义我们可以得出两个关键信息:
-
Spring MVC 是⼀个 Web 框架。
-
Spring MVC 是基于 Servlet API 构建的。
MVC定义
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。
- 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 框架,如下图所示:
简单来说, Spring MVC是一切项目的基础, 以后创建的所有的Spring和Spring Boot项目基本都是基于Spring MVC的.
掌握技能
- 连接的功能: 将用户(浏览器)和Java程序连接起来, 也就是在浏览器输入URL地址之后, 能够在程序中匹配到响应的方法.
- 获取参数的功能: 用户访问的时候会带一些参数, 在程序中想办法获取到参数.
- 输出数据的功能: 执行了业务逻辑后, 要把程序执行的结果返回给用户.
连接的功能
方法一: 使用@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!";
}
}
- @Response注解表示的是返回一个非静态页面的数据. 如果没有加@Response注解,会去resource/static下面找静态页面, 然后将其返回.
-
@RequestMapping
- 既能修饰类(可选), 也能修饰方法
- 默认情况下,既支持GET请求方式,也支持POST请求方式
-
@RequestMapping 参数扩展,使得只支持某个请求方式
方法二: 使用@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;
}
获取多个参数
前端的参数的顺序和后端的参数顺序不一定要相同,后端是通过参数名识别参数的
// 获取多个参数
@RequestMapping("/login")
public String login(String username, String password) {
return "用户名: " + username + " | 密码: " + password;
}
获取对象
// 获取对象
@RequestMapping("/reg")
public String reg(UserInfo userInfo) {
return "用户信息: " + userInfo;
}
扩展:参数重命名
-
使用@RequestParam注解
// 参数重命名 @RequestMapping("/login") public String login(@RequestParam("name") String username, String password) { return "用户名: " + username + " | 密码: " + password; }
注意:
-
如果在参数中添加@RequestParam注解, 那么前段一定要传递此参数, 否则就会报错, 如下图所示:
-
想要解决上诉问题, 可以在@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; }
-
使用**@RequestBody**。可以接收JSON数据. 该注解表示接受的数据是JSON数据格式。如果是表单数据格式就会报错。
@RequestMapping("/reg") public String reg(@RequestBody UserInfo userInfo) { return "用户信息: " + userInfo; }
扩展:
- postman基本使用
从URL地址中获取参数(不是从URL地址中的参数部分获取参数)
@RequestMapping("/hero/{id}/{name}") // /{id}/{name}为参数
public String getHerInfo(@PathVariable Integer id, @PathVariable String name) {
return "ID: " + id + " | Name: " + name;
}
扩展:
- 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;
}
扩展:
-
不同运行平台的配置文件设置
- 新建不同平台的配置文件
-
在主配置文件中, 设置运行的配置文件
# application-dev.yml # 开发环境的配置文件 # 图片保存路径 img: path: D:/04_IDEA/# application-prod.yml # 生产环境的配置文件 #图片保存路径 img: path: /home/sgj/CPP102/photos# application.yml # 设置配置环境的运行平台 spring: profiles: active: prodpublic 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"); } -
使用注解@RequestHeader
@RequestMapping("/header2") public String getheader2(@RequestHeader("User-Agent")String userAgent) { // 获取特定的cookie return "User-Agent: " + userAgent; }
-
-
存储和获取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; } -
获取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文件夹下可以找到 } } -
返回非静态页面
-
使用@ResponseBody注解, 返回非静态页面
- 修饰类, 表示当前类中所有的方法都会返回一个非静态页面的数据.
@Controller @ResponseBody public class TestController { @RequestMapping("/sayhi") public String sayHi() { return "hello.html"; } }- 修饰方法, 表示当前方法返回的是非静态页面的数据.
@Controller public class TestController { @ResponseBody @RequestMapping("/sayhi") public String sayHi() { return "hello.html"; } } -
使用@RestController注解, 可以代替@Controller和@ResponseBody两个注解
@RestController public class TestController { @RequestMapping("/sayhi") public String sayHi() { return "hello.html"; } }
-
-
请求转发(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); } } -
请求重定向(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
-
练习: 实现计算器功能(前后端交互)
-
前端页面
`<!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> -
后端处理
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 -
输入需要计算的值
-
热部署
介绍
IDEA热部署(热加载): 在项目运行的时候, 修改源代码, IDEA可以实时编译源代码, 生成字节码, 自动帮开发者重启Spring Boot项目, 使得修改代码之后能够"实时"(有点延迟), 的看到新效果的目的.
实施步骤
-
在pom.xml中添加Spring Boot开发者框架支持(一般在创建项目的时候, 就会选择该框架)
-
开启IDEA的自动编译, 此步骤需要设置IDEA的两个settings, 一个是当前项目的settings, 另一个是新项目的settings, 具体操作如下图所示:
-
开启运行中的热部署. 不同的版本配置不同, 配置分为2021.2之前的版本, 和之后的版本配置
-
新版本的配置
-
老版本的配置
-
-
启动项目使用debug, 而非run运行