Spring MVC 程序开发 - 2
- [三 . 获取参数](#三 . 获取参数)
-
- [3.1 传递单个参数](#3.1 传递单个参数)
-
- [获取方法1 : 通过 Servlet 中的 request 方式获取](#获取方法1 : 通过 Servlet 中的 request 方式获取)
- [获取方法2 : Spring MVC(Spring Web) 主要获取参数的方式](#获取方法2 : Spring MVC(Spring Web) 主要获取参数的方式)
- [3.2 获取多个参数](#3.2 获取多个参数)
- [3.3 获取对象](#3.3 获取对象)
- [3.4 获取表单参数](#3.4 获取表单参数)
- [3.5 接收 JSON 对象](#3.5 接收 JSON 对象)
-
- [通过 ajax 传递 JSON 对象给后端(后续继续讲解)](#通过 ajax 传递 JSON 对象给后端(后续继续讲解))
- [3.6 上传文件](#3.6 上传文件)
- [3.7 获取 Cookie/Session/Header](#3.7 获取 Cookie/Session/Header)
-
- [获取 Cookie](#获取 Cookie)
-
- [传统获取 header / cookie](#传统获取 header / cookie)
- [简单地获取 Cookie : 使用注解 @CookieValue](#简单地获取 Cookie : 使用注解 @CookieValue)
- [Session 存取和获取](#Session 存取和获取)
-
- [Servlet 时代的方式](#Servlet 时代的方式)
- [获取 session 更简洁的方式 : @SessionAttribute](#获取 session 更简洁的方式 : @SessionAttribute)
- [获取 Header](#获取 Header)
-
- [Servlet 时代的方式](#Servlet 时代的方式)
- [获取 Header 更简单的方式 : @RequestHeader](#获取 Header 更简单的方式 : @RequestHeader)
- [3.8 重命名前端参数 : @RequestParam](#3.8 重命名前端参数 : @RequestParam)
- [3.9 获取 URL 中的参数 : @PathVariable](#3.9 获取 URL 中的参数 : @PathVariable)
- [四 . 返回数据](#四 . 返回数据)
-
- [4.1 返回值类型是 String](#4.1 返回值类型是 String)
- [4.2 返回值类型是 HashMap](#4.2 返回值类型是 HashMap)
- [4.3 返回值是 View](#4.3 返回值是 View)
- [4.4 请求转发 / 请求重定向](#4.4 请求转发 / 请求重定向)
这个专栏给大家介绍一下 Java 家族的核心产品 - SSM 框架
JavaEE 进阶专栏Java 语言能走到现在 , 仍然屹立不衰的原因 , 有一部分就是因为 SSM 框架的存在
接下来 , 博主会带大家了解一下 Spring、Spring Boot、Spring MVC、MyBatis 相关知识点
并且带领大家进行环境的配置 , 让大家真正用好框架、学懂框架
来上一篇文章复习一下吧
点击即可跳转到前置文章
三 . 获取参数
3.1 传递单个参数
获取方法1 : 通过 Servlet 中的 request 方式获取
我们之前在 Servlet 的时候 , 获取参数里面的信息使用的方法是 : request.getParameter("xxx") , 因为我们的 Spring MVC 是构建在 Servlet 上面的 , 所以我们 Servlet 的方法在 Spring MVC 里面仍然可以使用
java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
@GetMapping("/hi") //使用"/hi"可以访问到当前方法,该方法只支持 GET 请求
// 默认其实是把 HttpServletRequest request, HttpServletResponse response 隐藏起来了,需要用的时候再写出来
public Object sayHi(HttpServletRequest request, HttpServletResponse response) {
return "Hi," + request.getParameter("name");
}
}
接下来我们启动项目 , 在浏览器的地址栏输入 :
java
http://127.0.0.1:8080/hi?name=zhangsan
然后看一下页面能否显示出我们的名字
改成李四也是可以的
但是这种方式存在一些问题 :
这就存在一个问题 , 假如我们返回的数据是 int 类型 , 我们还能进行强转 , 但是假如我们这次没传数据呢 , 那进行强转的话不就是把 null 进行强转了吗 , 这可不太行
获取方法2 : Spring MVC(Spring Web) 主要获取参数的方式
java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取单个参数
* 代表我们从前端拿到 name 参数
* @param name
* @return
*/
@GetMapping("/get1")
public String getParam1(String name) {
return "value=" + name;
}
}
运行一下
那么这种情况 , 不传参数会不会有问题呢
没有问题 , 打印出来 null
那么我们还可以传递整型类型的参数
java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取单个参数
* 代表我们从前端拿到 name 参数
* @param name
* @return
*/
@GetMapping("/get1")
public String getParam1(int age) {
return "value=" + age;
}
}
那么我们不更改代码 , 接下来传递一个字符串呢
当然会报错了 , 这不是自己找不自在呢吗 , 我们目前参数列表里面写的是 int age , 那你传一个 String 类型的数据肯定会报错
参数里面使用 int 的话 , 我们不传参数的时候 , int 类型的参数就会报错
因为基本数据类型里面并不存在 null , 只有包装类才有 null , 所以我们这里使用包装类即可 , 那么 int 对应的包装类就是 Integer
java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取单个参数
* 代表我们从前端拿到 name 参数
* @return
*/
@GetMapping("/get1")
public String getParam1(Integer age) {
return "value=" + age;
}
}
这次就不会再报错了 , 因为 Integer 默认就是 null
最后要注意的就是输入框里面的参数要跟代码里面的参数的名称要相同
3.2 获取多个参数
方法还是跟之前的一样
java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取两个参数
* @param name
* @param age
* @return
*/
@GetMapping("/get2")
public String getParm2(String name,Integer age) {
return "name=" + name + ",age=" + age;
}
}
我们在浏览器试验一下
那么我们的参数顺序要是颠倒了 , 还能正常识别吗 ?
Spring MVC 中传递多个参数的时候 , 传递参数的顺序是不影响程序执行的 , 获取参数的结果和参数的名称有关 , 和顺序无关
目前我们的参数只有两个 , 但是假如我们的参数有很多 , 怎么解决呢 ?
我们当然可以写许多个参数 , 但是这样并不是利于维护的 , 那我们其实可以把这多个参数封装成一个对象 , 接收的时候接收一个对象即可
所以接下来 , 我们教大家怎么获取对象
3.3 获取对象
既然是获取对象 , 我们首先就要有一个对象
我们在 demo 包底下新建一个 model 包 , 然后新建一个类 ,叫做 Student
java
package com.example.demo.model;
import lombok.Data;
// 这个类就是一个普通的实体类,不需要加五大类注解
// 使用 @Data 注解就可以不用写 getter 和 setter 方法了
@Data
public class Student {
// 注意: 这里的属性名一定要和前端传递过来的名字相一致
private Integer id;// 如果我们要是害怕前端不传递过来参数的话导致报错的话,我们就可以把 int 提升成 Integer
private String name;
private Integer age;
private String sex;
// ...
}
接下来回到 WebController.java
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取对象
*/
@GetMapping("/get3")
public String getParam3(Student student) {
return student.toString();//虽然我们 Student 里面没写 toString,但是我们加了 @Data 注解,里面就包括了 toString
}
}
那么前端怎么传递参数呢 ?
该怎么传怎么传 , 传递的顺序也是无所谓的
注意事项 : 参数的传递是对大小写敏感的 , 时而好使时而不好使的 , 推荐大家这个名字全部小写 , 就别搞什么大驼峰啥的了
3.4 获取表单参数
我们知道 , 参数请求的参数一共有 :
- URL 传参 : 我们刚才的几个实例都是 URL 传参
- Ajax 传参 : 一般是页面局部跳转(比如动态页面部分内容刷新,整体不刷新)
- Form 表单传参 : 一般是页面全部跳转(跳转到新页面)使用 form 表单
获取参数少的情况
如果我们是 Form 表单传参 , 我们怎么实现
我们现在做一个小样例 : 实现用户的登陆 , 将用户名密码传过来
我们先实现一个简单的页面 : login.html
在 resources 目录底下的 static 里面新建一个 html 文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<!-- method="提交方法" action="提交地址" -->
<form method="get" action="login">
<div>
<h1> 登录 </h1>
用户名 : <input name="name"><br>
密码 : <input name="password"><br>
<input type="submit" value="提 交">
</div>
</form>
</body>
</html>
接下来 , 我们再来写 WebController , 这次我们使用 @RequestMapping , @RequestMapping Get 请求和 Post 请求都能接收
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取 form 表单中的参数
*/
// 这块要与前端的 action 的参数对应
@RequestMapping("/login")
public String login(String name,String password) {
return "<h1>用户名:" + name + "<br>密码:" + password + "</h1>";
}
}
获取参数多的情况
刚才这种情况是参数比较少的情况 , 那要是参数非常多呢 ?
比如我们的注册 : 用户名、密码、昵称、邮箱、手机号、验证码...
当然还是可以写许多个参数 , 但是这样代码的维护性很差 , 我们也可以通过对象来获取(跟刚才讲的一样)
我们依然是将传递过来的参数封装成一个对象
我们在 model 包底下新建一个类 , 叫做 User
我们只需要保证 form 表单传过来的参数和我们 User 类当中的 key 保持一致即可
java
package com.example.demo.model;
import lombok.Data;
// 这个类就是一个普通的实体类,不需要加五大类注解
// 使用 @Data 注解就可以不用写 setter 方法了
@Data
public class User {
// 注意: 这里的属性名一定要和前端传递过来的名字相一致
private Integer id;
private String name;
private String password;
private Integer age;
private String sex;
private String classname;
private String nickname;
private String img;
// ...
}
接下来写一下我们的前端页面 , 新建一个前端页面吧 , 叫做 reg.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<!-- method="提交方法" action="提交地址" -->
<form method="get" action="reg">
<div>
<h1> 注册 </h1>
用户名 : <input name="name"><br>
密码 : <input name="password"><br>
性别 : <input name="sex"><br>
<input type="submit" value="提 交">
</div>
</form>
</body>
</html>
(因为我对前端不是那么熟悉 , 所以简单写一下就好了)
那么我们后端怎样接收呢 ?
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@ResponseBody
public class WebController {
/**
* 获取 form 表单(多个参数)
* @param user
* @return
*/
@RequestMapping("/reg")
public String reg(User user) {
return user.toString();
}
}
来看一下效果
接下来 , 咱们再试验一下 ajax 传参的方式
我们在 resources.static 包底下新建一个 HTML 文件 , 叫做 login_ajax.html
我们还要使用 jQuery 第三方库 : https://www.aliyundrive.com/s/G8h1rn3sgX7
然后在 static 包下新建一个文件夹 , 叫做 js , 然后把 jQuery 引入进来(选中 js 文件夹 , ctrl + v)
然后我们在代码中引入 jQuery
然后编写页面代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/jquery.min.js"></script>
</head>
<body>
<div>
<h1> 登录 </h1>
用户 : <input id="name" name="name"><br>
密码 : <input id="password" name="password"><br>
<input type="button" onclick="onsub()" value=" 提 交 ">
</div>
<script>
function onsub() {
jQuery.ajax({
url:"login2",
type:"GET",
data:{"name":jQuery("#name").val(),"password":jQuery("#password").val()},
success:function(result) {
alert(result);
console.dir(result);
}
});
}
</script>
</body>
</html>
接下来回到 WebController , 再写一个 login2
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/login2")
public HashMap<String,Object> login2(String name, String password) {
HashMap<String,Object> result = new HashMap<>();
result.put("name",name);
result.put("password",password);
return result;//返回 JSON 格式的对象
}
}
运行查看效果 , 我们可以把 谷歌浏览器自带的抓包工具打开 (Fiddler 一样)
有可能是你的 jQuery 没有正常工作
但是咱们的代码目前是没有问题的 , 那就有可能是你的 IDEA 搞的鬼 ...
我们只需要把 target 目录删掉重新运行即可
然后这次就有了
回到浏览器我们重新看一眼
这回就有数据了
接下来输入数据 , 我们就抓到了包 , 他的类型是 application/json , 这就因为我们的 HashMap 也是 key-value 模型的 , 会转换成 JSON 数据格式
点击弹窗的确定之后 , 我们再来看一下控制台
这就说明前端传过来的数据 , 我们后端已经接收到了
3.5 接收 JSON 对象
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/login2")
public HashMap<String,Object> login2(String name, String password) {
HashMap<String,Object> result = new HashMap<>();
result.put("name",name);
result.put("password",password);
return result;//返回 JSON 格式的对象
}
}
我们可以通过 Postman 构造 JSON 对象
点击 Send , 然后去 Fiddler 抓包
那么有的同学说 : 我们传递的是 JSON 对象过去的啊 ,你用一个普通的参数能接收到吗 ?
那么我们再来写一个 login3 , 就做一件事 : 拿对象
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/login3")
public HashMap<String,Object> login3(User user) {
HashMap<String,Object> result = new HashMap<>();
result.put("name",user.getName());
result.put("password",user.getPassword());
return result;//返回 JSON 格式的对象
}
}
如果说 , 我通过这几句代码 , 通过一个 User 对象能够拿到前端传过来的对象的相关信息 , 并且把 name 值和 password 值进行 set 了 , 那么我们代码指定是没有问题的 , 那么我们返回的时候就不会是 null 了
那么我们再来试一下
我们刚刚讲过的通过 Ajax , 跟这个有很大区别 , 大家不要搞混
我们现在是通过传输 JSON 的这种方式 , 所以这两个是完全不一样的
最刚开始是在 url 里面搞事情 , 现在是在 body 里面搞事情 , 是不一样的 .
所以接收一个 JSON 对象 , 是跟以前都不一样的 , 我们需要加一个注解 : @RequestBody
这个注解的作用就是告诉这个对象 , 我接收的时候 , 不是从 url 里面去获取的 , 是从 body 里面获取的
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 得到用户传递的 JSON 对象
* @param user
* @return
*/
@RequestMapping("/login3")
public HashMap<String,Object> login3(@RequestBody User user) {
HashMap<String,Object> result = new HashMap<>();
result.put("name",user.getName());
result.put("password",user.getPassword());
return result;//返回 JSON 格式的对象
}
}
@RequestBody 注解只能搭配对象使用 ,不能搭配单个属性使用
最后 , 大家要清楚 :
圈出来的部分 , 其实他不是在 body 里面传值的
有的同学会说 , 我们之前都是 GET 啊 , GET 的情况下不就是通过 URL 去传的吗 ?
那改成 POST 就传到 Body 里面了啊
那我们就试一下 , 把这里改成 POST
然后重新运行
而且我们的请求当中也并未设置 content-type 为 JSON 格式 , 而是 application/x-www-form-urlencoded 的形式 (也就是 form 表单的形式) , 并不是 JSON 格式
那么我们通过 Postman 构造 JSON 格式再看一下呢
然后查看抓包结果
通过 ajax 传递 JSON 对象给后端(后续继续讲解)
我们需要设置两个位置 :
- 设置请求的格式 content-type 为 JSON
javascript
$.ajax({
url: '/your/api/endpoint',
type: 'POST',
data: JSON.stringify({ key: value }), // 将数据转换成 JSON 字符串
contentType: 'application/json', // 设置请求的格式为 JSON
dataType: 'json', // 指定服务器返回的数据类型为 JSON
success: function(data) {
// 请求成功后的处理逻辑
},
error: function(code, status, error) {
// 请求失败后的处理逻辑
}
});
- data 里面的写法就需要是一个 JSON 对象了(把圈出来的部分转换成一个 JSON 对象)
在 JS 中 , 转换 JSON 对象可以使用 JSON 对应的库
java
JSON.parse : 把 JSON 转化成 字符串
JSON.stringify : 把 JS 对象转化成 JSON
3.6 上传文件
使用注解 : @RequestPart("文件名") , 使用 MultpartFile 类型去接收
拿到文件之后还需要去存储 , 我们的 Spring MVC 也给大家提供了一种方法 : transferTo()
, 在括号内新 new 一个文件对象 , 名字就是要上传的文件名
口说无凭上实例
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 上传文件
*/
@RequestMapping("/reg2")
// 第一个参数:name
// 第二个参数:文件,获取文件需要加注解@RequestPart,注解需要设置参数
// 括号内起一个名字,保证这个名字和前端部分保持一致
public String reg2(String name, @RequestPart("myfile") MultipartFile file) throws IOException {
// 已经拿到头像了,接下来要保存头像
// 使用绝对路径,不能使用相对路径
file.transferTo(new File("D:\\study\\dogTest.jpg"));//目录+图片名,这里的文件名起什么名存到目录里的就是什么名
return "success";
}
}
那么后端代码已经实现 , 可是我们怎样去上传图片呢 ?
还是要使用我们的 Postman (良心工具)
然后填写 key
之后上传文件
再把第一个参数 name 给填上
之后点击 Send 发射 , 去 Fiddler 抓包看看
接下来我们去看看图片上没上传成功
3.7 获取 Cookie/Session/Header
获取 Cookie
传统获取 header / cookie
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 传统方法获取 header/cookie
*/
@RequestMapping("/cookie1")
@ResponseBody
public String cookie1(HttpServletRequest request,HttpServletResponse response) {
String name = request.getParameter("name");
// 获取所有 cookie 信息
Cookie[] cookies = request.getCookies();
return name;
}
}
简单地获取 Cookie : 使用注解 @CookieValue
@CookieValue("Cookie 名称" String 赋值给谁)
这个注解需要一个参数 , 这个参数跟 @RequestPart 的参数是一样的 , 需要拿到前端传递过来的 key 值
比如 : @CookieValue("haha" String xixi)
从前端传递过来的请求里面 , 去找一叫做 haha 的 cookie ,把 cookie 的 value 值交给当前程序的 xixi 变量 , 然后在程序里面我们就可以使用 xixi 变量了
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* Spring MVC 获取 cookie 的值
*/
@RequestMapping("/getck")
public String getCookie(@CookieValue("haha") String xixi) {
return "haha:" + xixi;// cookie名 + 要把haha赋值给谁
}
}
接下来我们使用开发者工具进行模拟 cookie 环境
添加 key value 数据
然后刷新页面
抓个包看看
那么为什么浏览器会默认将当前网站的所有 cookie 发送给后端呢 ?
原因就是 : http 协议无状态(我在访问你的时候 , 我不会记录你是谁)
那这样的话就会存在问题 : 如果你做的是教务管理系统 , 是需要进行登录的 , 然后你在进行发送每一次请求的时候 , 后端都不认识你啊 , 这就意味着我们每一次操作都需要进行登录 , 那这肯定是不行
所以我们有两种方案 :
- 每一次在 URL 上面动手脚 , 在 URL 上面加一个标识 , 每一次加了这个标识之后 , 后端在拿前端的参数时候 ,先拿这个标识 , 看看这个标识有没有问题 .
但是这样还是存在问题 :
- 标识加到了 URL 里面 , 还是有暴露的风险
- 验证的时候 , 通过 URL 去验证 , 但是 URL 很好伪造的 .
所以这种方法不太靠谱
- 把用户的身份标识存在客户端(浏览器) , 那我们浏览器唯一能操作的东西只有 cookie , 所以他就设计了一个机制 : 把身份标识加到了 cookie 里面
其实第二种方法也不是那么安全 , 在客户端的所有的东西都是不安全的 , 我们都可以通过特殊手段去伪造 , 但是使用 cookie 的这种伪造成本是比直接在 URL 里面添加参数要高的 , 虽然不能保证绝对的安全 , 但是能够保证相对的安全
而且使用 cookie 这种形式 , 我们存储的是秘钥 , 存的并不是一个固定的值 , 而且这个秘钥 , 同一个用户登录多次是不一样的 , 即使你拿到秘钥了 , 但是过一阵子重新登陆之后秘钥就过期了 ,你破解了也没有用
那么我们就选择第二种方式 : 把身份标识存储到 cookie 里面 , 把身份标识存储到 cookie 里面也有问题啊 , 即使我的身份信息已经存到 cookie 里面了 , 那浏览器也不知道你什么时候需要这个身份信息 , 所以浏览器就只好每次都返回 ,管你用不用呢 , 有 cookie 我就给你
那么接下来 , 假如说我想打印两组 cookie 呢 ?
我们只需要在代码里面函数参数部分再写一份注解
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* Spring MVC 获取 cookie 的值
*/
@RequestMapping("/getck")
public String getCookie(@CookieValue("haha") String xixi,
@CookieValue("aima") String aiba) {
return "haha:" + xixi + " aima:" + aiba;
}
}
刷新一下
这个 cookie 是没有进行持久化的 , 我们把浏览器关掉重新打开访问 ,就会报错
因为 cookie 相对来说不太安全 , 我们又出现了 session , 我们不能把所有的安全机制放在外人手里 , 所以放到服务器端会安全一点 , 而 session 完全是在服务器端的 , 现在我的程序就是服务器端 , 正常情况下不能让客户端去记录所有的登录信息 , 那我们要把控制权攥在自己手里 , 就使用 session . 因为 session 是放在服务器端的 , 但是又有一个问题 : 服务器端的东西要在服务器端生效 , 但是用户操作是在客户端操作啊 , 那为了解决这个问题 , session 里面就会有个东西 , 叫做 sessionId , 这就相当于我有一个会话表 , 会话表里面有两部分数据 : 一个是 sessionId , 一个是存储的对应信息 , 这个 sessionId 是随机生成的 , 同一个用户每次登陆生成的 sessionId 是不同的 , 这样就可以保证安全性以及时效性
Session 存取和获取
Servlet 时代的方式
获取 session
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/setsess")
@ResponseBody
public String setsess(HttpServletRequest request) {
// 获取 HttpSession 对象
// 参数为true:如果没有session对象就创建一个session
HttpSession session = request.getSession(true);
if (session == null) {
session.setAttribute("username","java");
}
return "success";
}
}
读取 session
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/sess")
@ResponseBody
public String sess(HttpServletRequest request) {
// 如果session不存在,不会自动创建
HttpSession session = request.getSession(false);
String username = "none";
if(session != null && session.getAttribute("username") != null) {
username = (String) session.getAttribute("username");
}
return "username:" + username;
}
}
获取 session 更简洁的方式 : @SessionAttribute
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 设置 session
* @param request
* @return
*/
@RequestMapping("/setsess")
public String setsess(HttpServletRequest request) {
// 获取 HttpSession 对象
// 参数为true:如果没有session对象就创建一个session
HttpSession session = request.getSession(true);
if (session != null) {
session.setAttribute("username","java");
}
return "success";
}
@RequestMapping("/getsess")
public String getsess(@SessionAttribute("username") String username) {
return "username:" + username;
}
}
运行一下
会报错的 , 这是因为我们的 session 还没添加进去
所以我们需要先运行 setsess , 再运行 getsess
我们可以在 sessionAttribute 里面加上 required = false , 此时再来看效果
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/getsess")
public String getsess(@SessionAttribute(value = "username",required = false) String username) {
return "username:" + username;
}
}
我们直接来访问他的 getsess
这回就不报错了 , 这是因为 required = false , 直白一点就是 : session 可以为空 , 可以不为空 , 不为空的时候返回 null
之前说 , session 是依靠 cookie 的 , 那就带大家来个花活
那我们故意把这个 sessionId 篡改了之后呢 ?
获取 Header
Servlet 时代的方式
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/getheader")
public String getheader(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return userAgent;
}
}
获取 Header 更简单的方式 : @RequestHeader
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 读取请求头
* @param userAgent
* @return
*/
@RequestMapping("/gethead")
public String getHead(@RequestHeader("User-Agent") String userAgent) { //把请求头里面的User-Agent信息放到userAgent里面
return "UserAgent:" + userAgent;
}
}
那么这里面的 User-Agent 是什么 ?
运行就会把我们的版本信息打印出来
3.8 重命名前端参数 : @RequestParam
比如说前端给我们的参数 ,叫做 username , 那么我们后端想要使用 name , 那前后端名称不统一的话 , 程序就无法运行 , 所以 Spring MVC 给我们提供了一个注解 : @RequestParam
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 前端参数重命名
* 以后端程序员的视角,咱们参数应该设置成 time
* 但是以前端程序员的视角,人家觉得设置成t之后,放在URL里面不占位置,传输速度快
* 这就产生分歧了
* @return
*/
@RequestMapping("/gettime")
// 后端程序员想要去用 time
public String getTime(String time) {
return "time:" + time;
}
}
我们的参数写的是 time , 但是前端设置的返回的 URL 里面的参数是 t , 所以我们进行测试的话就会发生错误
因为我们后端设置的参数叫做 time , 但是 URL 里面传过来的是 t , 所以服务器端就接收不到数据
那怎么办呢 ?
我们就可以使用 @RequestParam
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 前端参数重命名
* 以后端程序员的视角,咱们参数应该设置成 time
* 但是以前端程序员的视角,人家觉得设置成t之后,放在URL里面不占位置传输速度快
* 这就产生分歧了
* @return
*/
@RequestMapping("/gettime")
// 后端程序员想要去用 time
public String getTime(@RequestParam("t") String time) { //接收 URL 里面的 t , 用 time 替换
return "time:" + time;
}
}
重命名之后 , 再次运行
那么前端程序员想要改成 time , 然后告诉后端程序员我改完了
然后后端程序员去运行
好家伙 , 你竟敢耍我 ...
其实这就不是前端程序员的锅了 , 我们设置好参数重命名之后就只能使用设置好的参数名了 , 比如不能用 time 了 , 只能用 t 了
那么我们就可以设置一下 required 属性为 false , 这样即使没传过来 t 参数的话 , 也不会报错了 , 只是显示为 null
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 前端参数重命名
* @return
*/
@RequestMapping("/gettime")
// 在括号里面设置 required = false 就代表没有传过来 t 这个参数 , 我也不报错 , 显示 null 即可
public String getTime(@RequestParam(value = "t",required = false) String time) { //接收 URL 里面的 t , 用 time 替换
return "time:" + time;
}
}
但是我们使用 @RequestParam 之后只能识别 t 参数 , 并将 t 参数的值 , 设置给 time 变量
此时即使使用 time 参数 , 也为时已晚了 , 也是读取不到前端的参数的 , 他只认参数名为 t 的参数
3.9 获取 URL 中的参数 : @PathVariable
有一种奇奇怪怪的 URL , 他不是用 ? 引出的 , 它是以目录形式存在的
那么这种情况有没有呢 ?
当然也是存在的 , 给大家举个栗子 . 我们去搜索徐佳莹
有的词条就是在前面的 , 有的词条就是在后面的
那搜狗和知乎公司实力旗鼓相当 , 他们俩为啥一个在前一个在后呢 , 其实就有可能是 URL 的问题
那这样的话 , B 公司就有可能会被搜索引擎推送到前面 , 因为他是在 URL 里面添加参数的 , 肯定要比在参数里面添加参数比重更高 .
但是在 URL 里面获取参数更加困难 , 不过也不是没有办法
我们需要注意两个位置 :
- 在 @RequestMapping 里面写路由的时候 , 要加一个花括号 , 表示当前花括号里面的内容不是一个静态的字符 , 是一个变量
java
@RequestMapping("login4/{name}/{password}")
// 这就代表 name 并不是一个目录 , 而是一个变量
- 要在参数里面使用一个特殊的注解 : @PathVariable
java
@RequestMapping("/login4/{name}/{password}")
// 在每个参数前面加上 @PathVariable 就代表从 URL 的路由中拿变量赋值给对应的变量
// 比如 : @PathVariable String name 就代表从路由中拿到 name 变量给参数中的 name 变量
public String login4(@PathVariable String name, @PathVariable String password) {
System.out.println("name:" + name + " password:" + password);
return "name:" + name + " password:" + password;
}
我们还可以进行指定名称
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 从 URL 里面获取参数
* @param username
* @param password
* @return
*/
@RequestMapping("/login4/{name}/{password}")
// 在路由里面拿 name 变量的路由,赋值给 username
public String login4(@PathVariable("name") String username, @PathVariable String password) {
System.out.println("name:" + username + " password:" + password);
return "name:" + username + " password:" + password;
}
}
运行一下
前端传递参数直接采用路径层级来传递的
四 . 返回数据
其实我们之前已经用过很多次了 , 就是最后 return 回去就好了
不过要清楚一件事 :
4.1 返回值类型是 String
如果我们代码中返回的是 String 类型 , 那么 Spring MVC 会自动帮我们去转换成 text/html 格式
拿刚才的例子举例
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
/**
* 从 URL 里面获取参数
* @param username
* @param password
* @return
*/
@RequestMapping("/login4/{name}/{password}")
public String login4(@PathVariable("name") String username, @PathVariable String password) {
return "username:" + username + " password:" + password;
}
}
这个方法返回的数据类型是 String
那么我们运行之后 , 看一下抓包结果
4.2 返回值类型是 HashMap
如果返回值类型是 HashMap 的话 , 此时 Spring MVC 就会帮我们设置一个新的类型
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
@ResponseBody
public class WebController {
@RequestMapping("/login2")
public HashMap<String, Object> login2(String name, String password) {
HashMap<String, Object> result = new HashMap<>();
result.put("name", name);
result.put("password", password);
return result;//返回 JSON 格式的对象
}
}
4.3 返回值是 View
想要实现返回值是 View(页面) 的话 , 我们只需要删除掉 @ResponseBody 注解即可
java
package com.example.demo.controller;
import com.example.demo.model.Student;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@Controller
public class WebController {
@RequestMapping("/hi2")
public Object sayHi() {
return "/index.html";
}
}
4.4 请求转发 / 请求重定向
forward(请求转发) VS redirect(请求重定向)
请求转发就是 , 比如我们使用的学校官网 , 有一天换网站了 , 之前那个网站不用了 , 但是大部分人记住的还是之前那个网站 , 而且老师和同学们记住一个新的网站也很麻烦 , 那就实现这样的一件事情 - 输入老网站的地址就转发到新的页面
而请求重定向 , 指的就是后端会告诉浏览器 , 你请求的目标网址不是我这里 , 你去其他人那里吧 .
或者再给大家换个例子 :
双11 剁手节 , 躺了一年 , 也挺辛苦的 , 给自己换个手机吧 . 那必然支持国货华为啊 , 兴高采烈地去买了个华为的折叠屏手机 , 但是好景不长 , 他坏掉了 . 我去咨询客服 , 客服说我可以去全国的华为售后中心去进行维修 , 这就叫做请求转发 . 而请求重定向就是你听说苹果的售后贴心无比 , 你拿着华为手机去找人家维修 , 人家说 : "先生 , 请您去华为售后中心寻求帮助 , 我们这里是苹果售后服务中心."
请求转发就是对内的 , 请求重定向就是对外的 .
forward 和 redirect 具体区别如下
- 请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。
请求重定向是将资源重新定位 , 整个请求还是浏览器重新去请求的 , 请求转发是服务器帮你去转发的 . 他要完成的事是更多的
- 请求重定向地址发生变化,请求转发地址不发生变化。
请求重定向本质上是你访问一个地方 , 他告诉你这个地方不对你重新去访问另外一个地方 , 所以地址相对于之前就会发生变化 . 请求转发是服务器帮我们去完成的 , 我们并不知道背后的原理 , 所以他的地址是不发生变化的
- 请求重定向与直接访问新地址效果一致,不存在原来的外部资源能够访问;请求转发的话服务器端转发
有可能造成原外部资源不能访问。
请求重定向能够保证我们访问新的地址的效果是一模一样的 , 能够保证所有的功能都是正常的 .
而使用请求转发就保证不了 , 请求转发可能会造成某些功能实现不了 , 有些文件资源访问不了 , 原因是因为请求转发的时候是服务器帮助我们去转发的 , 转发完了之后 , 他的地址就停留到了当前你访问的地址 , 而这些外部的资源 , 就是以当前地址来作为相对地址去访问的 , 那地址改变了 , 之前的资源就都不见了 . 那就会请求不到资源 , 这样的话 JS 加载不上 , 从而导致 ajax 发不出去 , 就导致我们的功能不能正常使用 .
使用方法 : (其实后端已经不实现跳转的功能了 , 这应该是前端的职责)
java
// 请求重定向
@RequestMapping("/index")
public String index() {
return "redirect:/index.html";
}
// 请求转发
@RequestMapping("/index2")
public String index2() {
return "forward:/index.html";
}
在 controller 包底下新建一个类 , 叫做 TestController
java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
// 请求转发
// 返回页面的时候,返回值要为 Object
// 实现效果:当我们访问 hello 的时候,让他跳转到登录页面
@RequestMapping("/hello")
public Object hello() {
return "forward:/login.html";
}
// 请求重定向
@RequestMapping("/hello2")
public Object hello2() {
return "redirect:/reg.html";
}
}
对应的 login.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<!-- method="提交方法" action="提交地址" -->
<form method="get" action="login">
<div>
<h1> 登录 </h1>
用户名 : <input name="name"><br>
密码 : <input name="password"><br>
<input type="submit" value="提 交">
</div>
</form>
</body>
</html>
对应的 reg.html
java
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<!-- method="提交方法" action="提交地址" -->
<form method="get" action="reg">
<div>
<h1> 注册 </h1>
用户名 : <input name="name"><br>
密码 : <input name="password"><br>
性别 : <input name="sex"><br>
<input type="submit" value="提 交">
</div>
</form>
</body>
</html>
运行一下 :
请求转发的情况 : 页面发生变化了 , 但是地址未发生变化
请求重定向的情况 : 页面发生变化了 , 地址也发生变化了