Spring MVC 其实是 Spring Web MVC,是基于 Servlet API,所以 Servlet 是 Spring MVC 的 “父亲”。因此,Servlet 那一套编程方法,在 Spring MVC 中,也是可以使用的。但是 Spring MVC 比 servlet 要简单很多。
Spring MVC :一开始就在 Spring 框架当中。只是属于 Spring 中的一个 web 模块。
一个 web 项目,只做三件事:
MVC 是 Model View Controller (模型视图控制器)的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。
四者之间的关系如下:
两者的关系,就像 IoC 和 DI 的关系。前者是思想,后者是实现。
Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。因为是 Web 框架,所以⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项 ⽬就可以感知到⽤户的请求。
现在绝大部分的 Java 项目都是基于 Spring(或 Spring Boot)的,而 Spring 的核心就是 Spring MVC。现在大部分的 Java 项目都是 Spring MVC 项目。
创建项目和我们之前创建 Spring Boot 项目一样,只是在添加依赖的时候,选择 Spring web 就好了:
这样就完成了 Spring MVC 项目的创建了。
使用 @RequestMapping(“/xxx”) 映射,可以加在 类上面,就是一级目录,然后方法上面再加的话,就是二级目录:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/sayhi")
public String sayHi() {
return "hello world";
}
}
访问方法如下:
user 是一级目录,sayhi 是二级目录。默认是 GET 请求:
使用 POST 请求来访问资源,通过 method 参数来设置:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping(method = RequestMethod.POST, value = "/sayhi")
public String sayHi() {
return "hello world";
}
}
访问结果如下:
就支持 POST 访问了。
使用 @PostMapping 来指定 POST 访问:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@PostMapping("/sayhi")
public String sayHi() {
return "hello world";
}
}
访问结果如下:
通过 GetMapping 来进行 GET 访问:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@GetMapping("/sayhi")
public String sayHi() {
return "hello world";
}
}
访问结果如下:
通过传入参数,然后返回对象:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@GetMapping("/getuserbyid")
public UserInfo getUserById(Integer id) {
UserInfo userInfo = new UserInfo();
userInfo.setId(id);
userInfo.setUsername("zhangsan");
userInfo.setAge(18);
return userInfo;
}
}
传入 id = 1,只要保证传入的参数和接收的参数一样就好了,运行结果如下:
使用 Integer 是可以接收 null 的。
通过传多个参数来获取:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login(String username, String password) {
return "用户名:" + username + " 密码:" + password;
}
}
运行结果如下:
也就是获取多个参数和获取一个参数的方法是一样的,只需要传递多个参数就好了。
如果有多个参数的时候,通过传对象可以让结果看的更简洁:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/reg")
public String login(UserInfo userInfo) {
return "用户信息:" + userInfo;
}
}
前端传这样的参数:
http://127.0.0.1:8080/user/reg?username=zhangsan&password=123&age=18
运行结果如下:
某些特殊情况下,前端传递的参数 key 和我们后端接收的 key 可能不一致比如前端传递了 name 之后,后端是用 username 来接收的,那么就会报错。通过 @RequestParam
来对存在念书重命名:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/reg")
public String login(@RequestParam("name") String username) {
return "用户名:" + username;
}
}
前端参数:
http://127.0.0.1:8080/user/reg?name=zhangsan
运行结果如下:
也就是当使用了这个注解之后,前端必须要传一个 name 参数。否则就报错。如果加了 required 之后,设置 required 的参数为 false,因为默认是 true,就不会报错了,会显示用户名是 null:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/reg")
public String login(@RequestParam(value = "name", required = false) String username) {
return "用户名:" + username;
}
}
运行结果如下:
使用 RequestBody 注解来完成对象的接收,后端代码如下:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/reg")
public String login(@RequestBody UserInfo userInfo) {
return "用户信息:" + userInfo;
}
}
然后通过 Postman 来模拟:
这里并不是从 URL 地址中的参数部分获取参数,而是从 /
来作为分隔符获取参数,通过 @PathVariable 注解来实现
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/info/{id}/{name}")
public String getURLInfo(@PathVariable Integer id, @PathVariable String name) {
return "ID:" + id + " name:" + name;
}
}
前端 URL:
http://127.0.0.1:8080/user/info/2/zhangsan
运行结果如下:
通过 @RequestPart 注解来实现对文件的上传,只需要对参数加上注解就好了。然后通过 Spring 的 MultipartFile 来接收文件就好了:
@Slf4j
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/upload")
public boolean upImg(Integer id, @RequestPart("image") MultipartFile file) {
boolean result = false;
//保存图片到本地目录
try {
file.transferTo(new File("D:/img.png"));
} catch (IOException e) {
log.error("上传图片失败: " + e.getMessage());
}
return result;
}
}
Postman 模拟如下:
D 盘的文件如下:
但是如果这样每次的名字都一样的话,就会导致文件覆盖。
直接把路径放在配置文件当中,这样的话,如果项目上线也很容易修改,直接修改为线上环境就好了,application 是必不可少的,不过可以通过 -
在 application 后面 加上名称。通过 application.yml
来配置 配置文件的运行环境:
# 设置配置文件的运行平台
spring:
profiles:
active: dev
开发环境配置:
生产环境如下:
文件路径:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
//从配置文件中读取图片的保存路径
@Value("${img.path}")
private String imgPath;
@RequestMapping("/sayHi")
public String sayHi() {
return "文件路径:" + imgPath;
}
}
访问结果如下:
这样就完成了对图片路径的访问。换成生产环境也一样可以。
让图片名不能重复,通过 UUID 来让图片永远不重复。UUID 是自动生成一个全球不同的一串数字。如果用时间戳的话,总会有两个人同时传数据,所以就会导致内容覆盖。
就是获取原图片的格式,不能向我们上面这样,改变图片的格式。从最后一个点,向后截取,就能获取到文件的后缀名了:
@Slf4j
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
//从配置文件中读取图片的保存路径
@Value("${img.path}")
private String imgPath;
@RequestMapping("/upload")
public boolean upImg(Integer id, @RequestPart("image") MultipartFile file) {
boolean result = false;
//保存图片到本地目录
String fileName = file.getOriginalFilename();
fileName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + fileName;
try {
file.transferTo(new File(imgPath + fileName));
result = true;
} catch (IOException e) {
log.error("上传图片失败: " + e.getMessage());
}
return result;
}
}
Postman 模拟如下:
保存路径内容如下:
web 程序里面都会有 Request 和 Response 参数,只需要在需要的时候,直接使用就好了:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/sayhi")
public String sayHi(HttpServletRequest request) {
return "hello world " + request.getParameter("name");
}
}
运行结果如下:
直接使用 @CookieValue 就好了,先设置好 Cookie:
然后输出:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/cookie")
public String getCookie(@CookieValue("lisi") String cookie) {
return "Cookie Value" + cookie;
}
}
运行结果如下:
如果要读取多个 cookie 的话,就设置多个 Cookie 参数。
直接通过 @RequestHeader 的注解来实现获取 Header,我们获取 Header 里面的 user-agent:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/header")
public String getCookie(@RequestHeader("User-Agent") String userAgent) {
return "user-Agent: " + userAgent;
}
}
运行结果如下:
听过 HttpSession 来设置 session:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/header")
public boolean setSession(HttpServletRequest request) {
boolean result = false;
//得到 Session
HttpSession session = request.getSession(true);//true 是创建会话
//使用 setAtt 设置值
session.setAttribute("userinfo", "userinfo");
return true;
}
}
运行结果如下,设置 Session 就是多了一个 JSESSIONID :
Spring Boot 通过 @SessionAttribute 注解就可以获取到 Session 了:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getsession")
public String getSession(@SessionAttribute(value = "userinfo",required = false) String userinfo) {
return "会话:" + userinfo;
}
}
访问结构如下:
加上 required 的时候,如果 session 当中没有 此属性 的时候,就不会报错了。
如下代码:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/sayhi")
public String sayHi() {
return "hello";
}
}
这样返回的其实是一个 静态页面 的结果:
把代码当中换成 hello.html
,然后写一个页面,就可以了:
@Controller
public class UserController {
@RequestMapping("/sayhi")
public String sayHi() {
return "hello.html";
}
}
运行结果如下:
@ResponseBody,就表示返回非静态页面的数据。
通过 @RestController 注解,就可以直接返回非静态页面的数据了:
@RestController
public class UserController {
@RequestMapping("/sayhi")
public String sayHi() {
return "hello.html";
}
}
运行结果如下:
通过 MVC 和 form 表单来实现。前端代码:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addtitle>
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>
后端接受的时候,路由也是 /calc
来接收,然后返回页面数据就好了:
@RestController
public class CalcController {
@RequestMapping("/calc")
public String calc(Integer num1, Integer num2) {
if (num1 == null || num2 == null) {
return "参数错误!
返回";
}
return "结果:"
+ (num1 + num2) + "返回";
}
}
后端返回的结果,这里写成了 h1 标签。运行结果如下:
点击之后效果如下:
通过 Ajax 来完成用户的登录,通过模拟登录来实现用户交互,前端代码如下:
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">
<script src="js/jquery-1.9.1.min.js">script>
<title>Documenttitle>
<script>
//ajax 提交
function mysub(){
//1.判空 也可以把 jQuery 符号换成 $
var username = jQuery("#username");
var password = jQuery("#password");
//使用 trim 方法来去掉空格
if(jQuery.trim(username.val())==""){
alert("请先输入用户名!");
//把光标重新放到元素上面
username.focus();
return;
}
if(jQuery.trim(password.val())==""){
alert("请先输入密码!");
password.focus(); //光标重制到此元素
return;
}
jQuery.ajax({
url:"/login",
type:"POST",
data:{"username":username.val(),
"password":password.val()},
success:function(result){
alert(JSON.stringify(result));
}
});
}
script>
head>
<body>
<div style="text-align: center;">
<h1>登录h1>
用户:<input id="username">
<br>
密码:<input id="password" type="password">
<br>
<input type="button" value=" 提交 " onclick="mysub()" style="margin-top: 20px;margin-left: 50px;">
div>
body>
html>
后端代码如下:
@RestController
public class UserController {
@RequestMapping("/login")
public HashMap<String, Object> login(String username, String password) {
HashMap<String, Object> result = new HashMap<>();
int state = 200;
int data = -1;//等于 1 的话,就表述登陆成功
String msg = "";
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
if (username.equals("admin") && password.equals("admin")) {
data = 1;
msg = "";
}
} else {
msg = "非法参数";
}
result.put("state", state);
result.put("data", data);
result.put("msg", msg);
return result;
}
}
运行结果如下:
故意填错密码:
填对密码的时候,就和我们约定的结果一样了:
通过 JSON 来传输数据的时候,后端就要通过 @RequeestBody 来接收了。然后前端也要指定为 JSON 格式发送。就是转化为 JSON 字符串就好了。前端代码如下:
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">
<script src="js/jquery-1.9.1.min.js">script>
<title>Documenttitle>
<script>
//ajax 提交
function mysub(){
//1.判空 也可以把 jQuery 符号换成 $
var username = jQuery("#username");
var password = jQuery("#password");
//使用 trim 方法来去掉空格
if(jQuery.trim(username.val())==""){
alert("请先输入用户名!");
//把光标重新放到元素上面
username.focus();
return;
}
if(jQuery.trim(password.val())==""){
alert("请先输入密码!");
password.focus(); //光标重制到此元素
return;
}
jQuery.ajax({
url:"login",
type:"POST",
contentType:"application/json",
data:JSON.stringify({"username":username.val(),
"password":password.val()}),
success:function(result){
alert(JSON.stringify(result));
}
});
}
script>
head>
<body>
<div style="text-align: center;">
<h1>登录h1>
用户:<input id="username">
<br>
密码:<input id="password" type="password">
<br>
<input type="button" value=" 提交 " onclick="mysub()" style="margin-top: 20px;margin-left: 50px;">
div>
body>
html>
后端代码如下:
@RestController
public class UserController {
@RequestMapping("/login")
public HashMap<String, Object> login(@RequestBody UserInfo userInfo) {
HashMap<String, Object> result = new HashMap<>();
int state = 200;
int data = -1;//等于 1 的话,就表述登陆成功
String msg = "";
if (StringUtils.hasLength(userInfo.getUsername()) && StringUtils.hasLength(userInfo.getPassword())) {
if (userInfo.getUsername().equals("admin") && userInfo.getPassword().equals("admin")) {
data = 1;
msg = "";
} else {
msg = "用户名或密码不正确";
}
} else {
msg = "用户名或密码不正确";
}
result.put("state", state);
result.put("data", data);
result.put("msg", msg);
return result;
}
}
运行结果如下:
请求转发是 forward,请求重定向是 redirect。
通过 forward 来实现请求转发:
@Controller
public class UserController {
@RequestMapping("/myforward")
public String myForward() {
return "forward:/hello.html";
}
}
forward 后面加的是请求转发路径。运行结果如下:
直接到了 hello.html 页面了。
请求重定向是 redirect :
@Controller
public class UserController {
@RequestMapping("/myredirect")
public String myRedirect() {
return "redirect:/hello.html";
}
}
运行结果如下:
这里就是直接访问 hello.html 页面了。重定向是发生在客户端的行为,不会经过服务器端。