一.基本概念及知识准备:
1)基本概念
SpringMVC是Spring框架后续开发的web模块.主要负责前后端数据交换.其基于Servlet进行开发的框架,目的简化前后端的调用.
请求类型一共8种,但是常用的有4种(get/post/put/delete)
@RequestBody 接收前端自定义对象的参数
2)JSON知识回顾
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式
可参考Json官网:http://www.json.org.cn/ JSON数据格式:
①JSON格式-对象格式: {"id": "100","name": "tomcat", "age": "18"}
②JSON格式-数组格式: [100,"张三",true]
JSON可以嵌套,值(value) 可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。
3)Axios知识回顾
Axios封装了Ajax,
功能和作用: Ajax主要实现前后端交互.提高用户页面与服务器之间交互效率.promise是Axios封装服务器返回值的对象
特点: 局部刷新,异步访问
缺点:不同的服务器之间发送ajax请求时会有"跨域"问题,通过注解搞定. @CrossOrigin
注意: axios为了接收后端服务器数据,利用promise对象封装后端服务器数据参数
Ajax异步的原理:
1. 由Ajax引擎直接访问后端服务器。
2. 在回调函数没有执行之前,用户可以执行自己的任务。 异步回调地狱优化-async-await
<script> axios.defaults.baseURL = "http://localhost:8080" async function saveUser(){ let user = {id: 100,name:"春节已过!!!"} let promise = await axios.post("/axios/saveUser",user) console.log(promise.data) } /* 2.调用函数 */ saveUser() </script>
4)跨域问题
同源策略:
浏览器URL中的地址/Ajax请求的URL地址必须满足 协议/域名/端口号都相同时.表示满足同源策略.如果满足同源策略,则称之为 “同域访问” 反之称之为 “跨域访问” 跨域访问浏览器一般都会报错(浏览器默认端口号是http80,https443,localhost!=127.0.0.1)跨域解决方案:
1.jsonp 方式跨域 淘汰了.
2.CORS 跨域资源共享:CORS要求在服务器端标识哪个网址可以跨域
@CrossOrigin(value="http://www.baidu.com")表示只准许该网址http://www.baidu.com访问
5)Tomcat/jetty对请求的处理
二.SpringMVC底层实现原理
SpringMVC调用流程:
1. 用户发起请求之后,第一步访问就是前端控制器.
2. 前端控制器只负责请求的转发/响应. 没有办法直接处理业务.
3.当SpringMVC容器启动时,处理器映射器首先会加载所有的@RequestMapping注解.将请求路径与方法进行绑定.保存到Map中. Map</url,method方法>, 当前端控制器发送请求被处理器映射器接收.①如果URL地址匹配,则告知应该执行哪个方法.②如果url地址不匹配.,则提示用户4044.前端控制器得知将要执行的方法是谁,但是前端控制只能转发,没有办法执行业务.
所以请求处理器适配器执行业务逻辑.5.处理器适配器针对配置文件(xml方式/注解方式/其它方式)的格式,挑选合适的处理器去执行业务逻辑. 业务执行成功之后返回ModelAndView对象 Model:业务数据 View:页面
历史版本: 需要返回页面名称,及将数据填充到页面中
6. 前端控制器接收到返回的ModelAndView之后,交给视图解析器去解析数据. 视图解析器通过自身的配置获取页面的名称 (/web/user.html).
7. 最终通过经过视图渲染,将数据填充到页面中.最终用户看到的页面就包含了业务数据.前后端分离方式:
关键用法: @ResponseBody
三.传统方式发送请求
1)传统get/delete方式放松请求
url格式如: http://localhost:8080/findUser?name=tomcat&age=18&sex=男
?hobby=篮球,排球,乒乓球
①前端发送 axios请求
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axios测试</title>
<script src="../js/axios.js"></script>
</head>
<body>
<h1>Axios测试案例-1</h1>
<script>
/* 简化方式1: 抽取后端服务器地址 */
axios.defaults.baseURL = "http://localhost:8080"
/*发送无参类型的请求*/
let url = "/hello"
axios.get(url).then(function(promise){
console.log(promise.data)
})
/*发送含1个参数 类型的请求*/
let url1 = "/axios/getUserById?id=100"
axios.get(url1)
.then(function(promise){
console.log(promise.data)
})
/*发送含多个参数 类型的请求*/
let user2 = {name:"tomcat",age:100}
let url2 = "/axios/getUserByNA"
axios.get(url2,{params: user2})
.then( promise => {
console.log(promise)
})
/* 简化方式3: async await简化调用 重点
问题描述: 如果ajax如果嵌套的层级较多,则引发"回调地狱"问题
解决问题: 能否将axios中的then进行简化.
语法:
1. 使用async关键字标识函数
2. 通过await标识ajax请求
3. 必须同时出现
*/
async function getHello(){ //定义函数
//let {data: result,status: code} = await axios.get("/web/hello")
let {data: result} = await axios.get("/web/hello")
alert(result)
}
//调用函数
getHello()
/* 箭头函数写法
1.去除function关键字
2.参数与函数体之间使用 => 连接
3.如果只有一个参数,则参数括号可以省略
4.箭头函数使用一般用于回调函数
5.如果使用function关键字 则使用this时会产生歧义
*/
let user2 = {id: 200,name:"箭头函数!!!"}
axios.post("/axios/saveUser",user2)
.then(function(promise){
console.log(promise.data)
})
axios.post("/axios/saveUser",user2)
.then( promise => {
console.log(promise.data)
})
</script>
</body>
</html>
②后端接收数据并返回处理好的数据
@RestController
@CrossOrigin //主要解决跨域问题
@RequestMapping("/axios")
public class AxiosController {
//@RequestParam("id")该注解主要考虑向前兼容,JDK1.5倩是需要加的
@RequestMapping("/getUserById")
public User getUserById(@RequestParam("id") Integer id){
//根据ID查询数据库
User user = new User();
user.setId(id);
user.setName("好好学习");
user.setAge(1000);
user.setSex("男");
return user;
}
@RequestMapping("/findJSON")
public User findJSON(User user){
//在业务层扩展数据
user.setId(101);
user.setSex("男");
return user;
}
@RequestMapping("/getUserByNA")
public List<User> getUserByNA(User user){
List<User> list = new ArrayList<>();
list.add(user);//简化赋值操作 直接返回
list.add(user);
return list;
}
服务端:
当方法return的类型为自定义对象或集合时,
springMVC框架会自动将集合或对象转成son格式的字符串然后将字符串再转成二进制数据通过网络传输给客户端
客户端:
服务器返回的是二进制数据,会先将二进制数据转成son格式的字符串
然后axios框架会将JSON格式的字符串再转成数组里面装对象
2)传统post/put方式放松请求
①前端发送 axios请求
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axios测试</title>
<script src="../js/axios.js"></script>
</head>
<body>
<h1>Axios测试案例-2</h1>
<script>
/*语法:
1.参数 axios.post(URL地址,数据对象)
2.注意事项: 与axios.get(url,{params:对象})请求写法不一样.*/
let url1 = "http://localhost:8080/axios/saveUser"
let user1 = {id:100, name:"猫", age:2, sex: "母"}
axios.post(url1,user1)
.then(function(promise){
console.log(promise.data)
})
</script>
</body>
</html>
②后端接收数据并返回处理好的数据
@RestController
@CrossOrigin //主要解决跨域问题
@RequestMapping("/axios")
public class AxiosController {
@RequestMapping("/getUserById")
public User getUserById(Integer id){
int a = 100;
//根据ID查询数据库
User user = new User();
user.setId(id);
user.setName("好好学习");
user.setAge(1000);
user.setSex("男");
return user;
}
@RequestMapping("/getUserByNA")
public List<User> getUserByNA(User user){
List<User> list = new ArrayList<>();
list.add(user);//简化赋值操作 直接返回
list.add(user);
return list;
}
@RequestMapping("/findUserByNS/{name}/{sex}") //调用set方法为属性赋值
public List<User> findUserByNS(User user){
List<User> list = new ArrayList<>();
list.add(user);
list.add(user);
return list;
}
//@RequestMapping(value="/saveUser",method = RequestMethod.POST)
@PostMapping("/saveUser")
public String saveUser(@RequestBody User user){
System.out.println(user);
return "新增用户成功!!!";
}
}
四.restFul风格发送请求
restFul风格方式提交数据一般用于更新操作
url格式如: http://localhost:8080/findUser/tomcat/18/男
要求: restFul的风格数据的位置一旦确定,不能修改.
注意: restFul风格请求支持常用的4种请求类型(get/post/put/delete),
但是为了数据安全问题一般不使用post请求
1)前端发送 axios请求
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axios测试</title>
<script src="../js/axios.js"></script>
</head>
<body>
<h1>Axios测试案例-1</h1>
<script>
/**动态获取参数
* 模版字符串写法:ES6引入的新功能
* 语法:
* 1. 使用反引号 ``
* 2. 作用: 1.可以保证字符串的格式
* 2.可以动态获取变量的值
*/
let user3 = {name: "tomcat", sex: "男"}
let url3 = `http://localhost:8080/axios/findUserByNS/${user3.name}/${user3.sex}`
axios.get(url3)
.then(function(promise){
console.log(promise.data)
})
</script>
</body>
</html>
2)后端接收数据并返回处理好的数据
@RequestMapping("/findUser/{name}/{age}/{sex}")
public String findUser(@PathVariable String name,
@PathVariable int age,
@PathVariable String sex){
return name+":"+age+":"+sex;
}
/**注意:如果{ }的属性与对象的属性名称一致则可以使用对象接收 */
@RequestMapping("/findUserByNS/{name}/{sex}") //调用set方法为属性赋值
public List<User> findUserByNS(User user){
List<User> list = new ArrayList<>();
list.add(user);
list.add(user);
return list;
}
使用注解说明
@Controller
将类交给SpringMVC管理,SpringMVC交给Spring容器管理JSON串与对象之间的相互转化
1.@ResponseBody
①如果返回的是对象,将返回对象转化为JSON串到前端
②如果返回String类型,则@ResponseBody将字符串本身返回给前端.
2.@RequestBody JSON串转化为User
要求: JSON串转化 要求json串中的属性与对象中的属性一致,并且赋值时调用对象的set方法@RestController public class WeiboController { @Autowired(required = false) WeiboMapper mapper; @RequestMapping("/insert") public void insert(@RequestBody Weibo weibo){ //由于客户端提交的参数不是FormData对象了 是一个自定义的对象 // 需要通过@RequestBody注解修饰接收数据的参数 System.out.println("weibo = " + weibo); mapper.insert(weibo); } }
@RestController
等同于@Controller + @ResponseBody@RequestMapping
负责用户的请求路径与后台服务器之间的映射关系
可以支持任意类型的请求. 但是这样的写法不安全
对应请求使用如下注解:新增: post请求类型 @PostMapping("")
删除: delete请求类型 @DeleteMapping("")
修改: put请求类型 @PutMapping("")
查询: get请求类型 @GetMapping("")@PathVariable
接收RestFul风格数据时,利用@PathVariable注解,动态获取路径中的数据,要求名称必须匹配
五.请求常见异常
- 405 异常 ajax的请求类型与后端接收的请求类型不匹配.
- 400异常 参数类型不匹配
- 404异常 请求路径找不到
- 403异常 后端未对请求进行处理
会话管理
客户端和服务器之间进行数据交互,遵循的是HTTP协议,此协议属于无状协议(一次请求对应一次响应)响应完连接断开,服务器无法跟踪客户端的请求,通过会话管理中的Cookie技术,可以在客户端向服务器发出请求时服务器给客户端添加一个标识(把数据保存到这个标识里面) , 之后客户端每次发出请求时都会带上这个标识,这样服务器通过此标识就能识别出该客户端的信息, 但是这种把数据保存在客户端的方式存在数据被篡改的风险,Session的作用就是能够解决这种问题, 因为Session的数据是保存在服务器端,不存在被篡改的问题.
Cookie: 数据保存在客户端,类似打孔式的会员卡
- 只能保存字符串类型的数据
- 保存时长: 默认保存在浏览器内存中, 浏览器关闭时清除, 也可以设置任意的保存时长,设置后数据会保存到客户端磁盘中,时间到了后清除.
- 应用场景: 长时间保存的数据使用Cookie,比如记住用户名和密码
Session: 数据保存在服务器端, 类似银行卡
- 可以保存任意对象类型的数据
- 保存时长: 只能保存半小时左右
- 应用场景: 对数据安全性要求比较高并且是短时间保存的,比如登录状态
通过Cookie实现记住用户名和密码功能
@RestController
public class UserController {
@Autowired(required = false)
UserMapper mapper;
@RequestMapping("/login")
public int login(@RequestBody User user, HttpSession session, HttpServletResponse response){
System.out.println("user = " + user);
User u = mapper.selectByUsername(user.getUsername());
if (u!=null){
if (u.getPassword().equals(user.getPassword())){
//登录成功后把从数据库里面查询到的用户对象保存到Session会话对象中
session.setAttribute("user",u);
//判断是否需要记住用户名和密码
if (user.isRem()){
//创建Cookie记住用户名和密码
Cookie c1 = new Cookie("username",user.getUsername());
Cookie c2 = new Cookie("password",user.getPassword());
//设置Cookie保存数据的时长
c1.setMaxAge(60*60*24*30);
//把cookie下发给客户端
response.addCookie(c1);
response.addCookie(c2);
}
return 1;
}
return 3;
}
return 2;
}
@RequestMapping("/currentUser")
public User currentUser(HttpSession session){
//获取登录成功时保存的用户对象
return (User) session.getAttribute("user");
}
@RequestMapping("/logout")
public void logout(HttpSession session){
//删除登录成功时保存的用户对象
session.removeAttribute("user");
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录页面</h1>
<form action="">
<input type="text" name="username" v-model="user.username" placeholder="用户名"><br>
<input type="password" name="password" v-model="user.password" placeholder="密码"><br>
<input type="checkbox" name="rem" v-model="user.rem">记住用户名和密码<br>
<input type="button" value="登录" @click="login()">
</form>
<script src="js/vue.min.js"></script>
<script src="js/axios.min.js"></script>
<script>
let v = new Vue({
el:"form",
data:{
user:{
username:"",
password:"",
rem:""
}
},
methods:{
login(){
axios.post("/login",v.user).then(function (response) {
if (response.data==1){
alert("登录成功!");
location.href="/";
}else if(response.data==2){
alert("用户名不存在!");
}else{
alert("密码错误!");
}
})
}
}
})
//获取cookie里面的数据
let cookieStr = document.cookie;
//'username=tom; password=123456'
let cookieArr = cookieStr.split(";");
for (let cookie of cookieArr) {
let arr = cookie.split("=")
let name = arr[0].trim(); // trim()去掉空格
let value = arr[1];
if (name=="username"){
v.user.username = value;
}else if (name=="password"){
v.user.password = value;
}
}
</script>
</body>
</html>
文件上传实现:
@RestController
public class UploadController {
@RequestMapping("/upload")
public String upload(MultipartFile picFile) throws IOException {
//得到原始文件名
String fileName = picFile.getOriginalFilename();
//得到后缀 .jpg
String suffix = fileName.substring(fileName.lastIndexOf("."));
//得到唯一文件名 UUID.randomUUID()获取唯一标识符
fileName = UUID.randomUUID()+suffix;
System.out.println("文件名:"+fileName);
//保存图片的文件夹
String dirPath = "D:/file";
File dirFile = new File(dirPath);
//判断如果文件夹不存在 则创建文件夹
if (!dirFile.exists()){
dirFile.mkdirs();//创建文件夹
}
//准备一个完整的文件路径
String filePath = dirPath+"/"+fileName;
//把上传的文件保存到上面指定的路径 异常抛出
picFile.transferTo(new File(filePath));
//把图片在服务器中的路径返回给客户端
return "/"+fileName;
}
@RequestMapping("/remove")
public void remove(String name){
//文件夹路径
String dirPath = "D:/file";
//得到文件的完整路径
String file = dirPath+name;
System.out.println("删除的文件路径:"+file);
//删除服务器中的文件
new File(file).delete();
}
}
#配置静态资源文件夹 默认的是static文件夹
spring.web.resources.static-locations=file:D:/file,classpath:static
#设置单个上传文件的大小
spring.servlet.multipart.max-file-size=10MB
#设置批量上传文件的总大小
spring.servlet.multipart.max-request-size=100MB
cookie实现记住用户名和密码及session保存登录状态