上一篇博客的补充:
一般出现这种问题,我们就要检查版本了
我们需要查看这几个地方是否版本是对的
注意: jdk版本运行取决于什么?
1.通过cmd运行,jdk版本就是你设置的环境变量
2.通过Idea运行,取决于该项目设置的JDK版本
创建项目的方式:
1> 我们上个博客用idea进行创建
2> 通过官网进行创建
3> 通过阿里云进行创建
4> 不基于任何页面,插件进行创建: 创建空的maven项目,自己写依赖,启动类.
1. 认识Spring MVC
Spring MVC的概念:
因为这一块的概念有些难懂,我会多写一些例子
MVC是⼀种架构设计模式,也⼀种思想,而SpringMVC是对MVC思想的具体实现.除此之外,Spring MVC还是⼀个Web框架.总结来说Spring MVC是⼀个实现了MVC模式的Web框架.
简而言之: Spring MVC就是把开发网站常用的jar包封装起来,它是一种web框架
什么是框架:
库->模块->框架(根据封装程度从小到大排序),就相当于把很多jar包放在一起封装起来.
什么是Web:web就是网站
什么是MVC:它是一种软件设计模式由: 模型,试图,控制器组成(后续会详细讲)
什么是Servlet : 它是一种规范 ,**其他人根据这个规范来用不同方式来进行实现.比如Tomcat 是一个实现了 Java Servlet 规范的 Web 容器(Web Container),**上一节我们晓得了Tomcat是一种内置在Spring Boot里面的服务器.再比如,单例模式是一种规范,我们可以用饿汉模式和懒汉模式来进行实现.
再打个比方: servlet是自行车,Spring是电动车,Spring是基于servlet的一种实现.
MVC设计模式:
MVC是Model View Controller的缩写,它是软件⼯程中的⼀种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分.
View(视图): 指我们看的见的部分,也就是在应用程序种专门用来和浏览器进行交互,展示数据的资源.
Model(模型): 这个就是来处理业务逻辑,是应用程序的主要部分.
Controller(控制器): 相当于一个分发器,它用来决定视图发过来的请求用哪个模型来进行处理,以及处理完之后再跳回哪个视图.也就是用来连接视图和模型.
举个例子:去饭店吃饭
客户进店之后,服务员来接待客⼾点餐,客户点完餐之后,把客⼾菜单交给前厅,前厅根据客户菜单给后厨下达命令.后厨负责做饭,做完之后,再根据菜单告诉服务员,这是X号餐桌客人的饭.
在这个过程中
服务员就是View(视图),负责接待客户,帮助客户点餐,以及给顾客端饭
前厅就是Controller(控制器),根据客户的点餐情况,来选择给哪个后厨下达命令.
后厨就是Model(模型),根据前厅的要求来完成客户的⽤餐需求
Spring SpringBoot Spring MVC之间的关系
我们通过一个例子来理解:
Spring 相当于火车,Spring MVC类似于售卖火车票(卖票肯定是基于火车这个条件的),一开始我们卖票就是在火车站线下买票.
Spring Boot类似于12306,它不仅具备线上卖票(比线下更高效)的功能,还封装了买保险,订酒店,订外卖...功能模块.
总而言之,一个项目有很多模块,Spring MVC是其中的一个模块,Spring Boot把包括Spring MVC的很多模块都封装起来了.简而言之: Spring Boot 是封装了更多功能,这个功能包含了Spring MVC的功能.也就是Spring Boot更加的高级
也就是说Spring Boot只是实现SpringMVC的其中⼀种方式而已 .
SpringBoot可以添加很多依赖,借助这些依赖实现不同的功能.SpringBoot通过添加Spring Web MVC框架,来实现web功能.
Spring MVC 比起平常的MVC它的浏览器和服务器的交互发生了一些变化.之前我们的视图要把请求发给控制器,而此时我们的浏览器就直接把请求发给控制器,视图此时更像是起到通道的作用
2. 使用Spring MVC 用来进行浏览器和服务器的交互
概念碎碎念
既然是Web框架,那么当用户在浏览器中输⼊了url之后,我们的Spring MVC项目就可以感知到用户的请求,并给予响应. 我们学习SpringMVC,重点也就是学习如何通过浏览器和用户程序进⾏交互.(学会了下面三个流程基本上掌握了Spring MVC)
交互流程:
1> 建立连接: 把浏览器(用户)和java程序连接起来,也就是输入一个url能够访问并且调用到我们写的后端程序.
2> 请求: 浏览器发出的请求可能会带一些参数,在后端程序中要想办法获得参数,因此主要还是要实现获取参数的功能.
3>响应: 执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应.
比如用户去银行存款
建立连接:去柜台
请求:带着银行卡,向柜台服务员发出存款请求.
响应:银行返回⼀张存折.
2.1 建立连接
在SpringMVC中使用 @RequestMapping来实现URL路由映射,也就是浏览器连接程序的作用.
路由映射: 当用户访问⼀个URL时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射.(差不多就是一个通过地址找快递的过程)
我们直接看代码
这里的另一个注解@RestController这个就是一个标记,让Spring去看被这个标记修饰的类里面的方法.
1> @RequestMapping修饰方法
我们执行启动类,然后再浏览器输入url
执行结果:
1> @RequestMapping修饰类
这里有几个命名建议:
1> 路径前面我们要加/
2> 类路径和方法路径建议一起使用.方法路径的命名我们可以根据我们的类名前面的单词来命名.
2.2 请求
请求方式:
1> post
2> get
2.2.1 使用前端form的方式来发起请求
这俩个是常用的,我们可以再html,form表单里面的action进行使用和设置
我们也可以打开Fiddler进行抓包,来看我们的请求方式
2.2.2 我们使用Postman来构造我们的请求
毕竟我们是后端开发,前端的内容我们可能不熟,因此我们可以使用Postman来进行构造
Postman,可以让我们来发送请求,不必写html里面的form,我们直接进行设置就可以把我们的请求方式,请求的参数,url给构造好
刚刚是不带参数的,我们来看看带参数的怎么构造.(主意每次修改完代码之后一定要重新启动启动类进行刷新)
1> 带一个参数(方法里面写)
String 类型
为空的时候的状况
为数字的情况:
这个说明我们传到我们后端的东西其实是一个字符串,然后再进行类型强制转化.我们可以看看下面的Integer类型,就很明显.
Integer类型
不带参数的情况
为非数字的参数:
这个就很明显了,我们的age传进来的是一个非数字的字符串,我们强转字符串之后发现参数和接受类型是不匹配的.
基本数据类型
不带参数的情况
传入的是非数字的参数
这个报错理由和上面的一样
2> 带多个参数
直接在方法里面多加入几个参数
创建对象,直接在类里面增加参数(类里面写)
但是每次如果我们要增加参数,我们修改的代码就很多,我们方法如果增加了一个参数,我们调用这个方法的其他方法的实参就要多一个,此时为了解决这个问题,我们就传入一个对象,以后要增加参数,直接在类里面增加就行.
我们先写一个类,里面提供它的get和set还有toString方法
然后设置对象的属性值:
不设置对象的属性值:
此时我们发现一个奇妙的事情,我们的属性值明明是基本数据类型,啥也没赋值并没有像刚刚的把基本数据类型作为方法的参数不赋值报错4XX.因为我们的对象构造出来默认就会给属性初始值了.在类里面创建对象,外面的int默认为0,Integer默认为null,在方法里面参数类型我们建议写Integer.
后端参数重命名:
某些特殊的情况下,前端传递的参数key和我们后端接收的key可以不⼀致,比如前端传递了⼀个time给后端,⽽后端是使⽤createtime字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使⽤来 @RequestParam 重命名前后端的参数值.(前后端形参的名称不一致)
参数重命名,参数必传的情况
此时name是前端参数,userName是后端参数
参数重命名,设置参数不必传的情况
原理:
3> 数组传参
方法一: 一次性写进去
方法二: 拆开写进去
4> 集合传参
刚刚的数组是不必要用什么标签的,因为默认是传的数组,然而我们要传集合的话需要使用:
@RequestMapping标签来标记这个传的是集合.相当于把接收到的字符数组重新赋值给list
5> 传递json数据
json语法介绍: json是一种数据格式,主要负责在不同语言的数据传递和交换
语法:
传输json对象(有在线编辑器,可以把格式进行压缩或者把压缩还原成有格式状态)
此时我们使用的是**@RequestBody** 这个注解表示我们接受的是请求里面的正文,不使用就接收不到.jason里面的请求正文不是key-value的形式,json传的是请求对象,因此Student接受不到参数,如果我们要拿到那个值,我们就要使用**@RequestBody.**
JSON字符串和Java对象互转
此时我们使用的是ObjectMapper来处理(底层是反射)
1> JSON字符串->JAVA对象
使用的是ObjectMapper里面的**readValue()**方法
2> JAVA对象->JSON字符串
使用的是ObjectMapper的writeValueAsString() 方法
6> 获取URL中参数@PathVariable
注意@RequestMapping里面的设置我们的参数的格式/article/{articleId}/{name},这个对应的是和@PathVariable我们前端参数一致.(不能只传一个参数,参数的顺序不一样也不行)
7> 上传⽂件@RequestPart
重命名前端参数:
代码:
package org.xiaobai.springmvc_tets.MVCFistclass;
import jakarta.websocket.server.PathParam;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
@RequestMapping("/request")//Request,类路径取类名前面的东西进行命名
@RestController
public class RequestController {
@RequestMapping("/hello")//1.建立连接
public String say(){
return "hello, Spring mvc";
}
@RequestMapping("/r1")
public String r1(String name) {
//后端接受到参数
return "接收到参数,name: " + name;
}
@RequestMapping("/r2")
public String r2(Integer age) {//传过来的都是String类型,然后我们进行强转
//后端接受到参数
return "接收到参数,age: " + age;
}
@RequestMapping("/r3")
public String r3(int age) {//传过来的都是String类型,然后我们进行强转
//后端接受到参数
return "接收到参数,age: " + age;
}
@RequestMapping("/r4")
public String r4(String name,Integer age) {//传过来的都是String类型,然后我们进行强转
//后端接受到参数
return "name: "+name+" age: " + age;
}
//TODO 在这里接口的定义为双方的协议,具体为一个方法
// @RequestMapping("/r5")
// public String r5(String name,Integer age,String nickName) {//传过来的都是String类型,然后我们进行强转
// //后端接受到参数
// return "name: "+name+" age: " + age;
// }
//参数越来越多,我们传过去接口要进行修改
//此时我们就直接传个对象,后续增加接口,直接在类里面添加即可,不必修改接口
@RequestMapping("/r5")
public String r5(Student student){
return "接受参数,student: " +student;
}
//name是商品名字还是人名呢?此时就可以对参数进行重命名
@RequestMapping("/r6")
public String r6(@RequestParam("name") String userName){//前面是前端参数,后面是后端参数
return "接受参数,name: " + userName;
}
//实现只想重命名不想必传参数
@RequestMapping("/r7")
public String r7(@RequestParam(value = "name",required = false) String userName){//前面是前端参数,后面是后端参数
return "接受参数,name: " + userName;
}
//TODO MVC第二节课
//TODO 传数组
//方法1,直接在key里面写多个value
@RequestMapping("/r8")
public String r8(String[] array){//前面是前端参数,后面是后端参数
return "接受参数,array: " + Arrays.toString(array);
}
//方法2 相同的key,一个key一个value的来写
//TODO 接受一个集合
// @RequestMapping("/r9")
// public String r9(@RequestParam() List<String> list){//相当于把接收到的字符数组重新赋值给list
// return "接受参数,list: " + list;
// }
@RequestMapping("/r9")//参数不必传
public String r9(@RequestParam(value = "list",required = false) List<String> list){//相当于把接收到的字符数组重新赋值给list
return "接受参数,list: " + list;
}
//TODO 使用json(一种数据格式)来进行传参
// @RequestMapping("/r10")//参数不必传
// public String r10(Student student){//相当于把接收到的字符数组重新赋值给list
// return "接受参数,student: " +student;
// }
@RequestMapping("/r10")
public String r10(@RequestBody Student student){
return "接受参数,student: " +student;
}
//TODO 获取url中的参数
@RequestMapping("/article/{articleId}")//参数不必传
public String r11(@PathVariable("articleId") Integer articleId1){//相当于把接收到的字符数组重新赋值给list
return "接收到参数,articleId: " +articleId1;
}
//获取多个参数
@RequestMapping("/article/{articleId}/{name}")//参数不必传
public String r12(@PathVariable("articleId") Integer articleId1,@PathVariable("name")String name1){//相当于把接收到的字符数组重新赋值给list
return "接收到参数,articleId: " +articleId1 +" name: " +name1;
}
//上传文件
//part一般就代表文件
// @RequestMapping("r13")//参数不必传
// public String r13(MultipartFile file){
// String orginalFilename = file.getOriginalFilename();
// return "接收到文件,文件名称: " + orginalFilename;
// }
//文件重命名
@RequestMapping("r13")//参数不必传
public String r13(@RequestPart("file") MultipartFile imgfile){
String orginalFilename = imgfile.getOriginalFilename();
return "接收到文件,文件名称: " + orginalFilename;
}
}
8> 获取Cookie/Session(很重要)
引入Cookie和Session
诞生Cookie和Session的原因:默认状态下,http协议的客户端和服务器之间的通信,每一次豆是一个状态,没有区别.因此http协议是一个无状态协议,也就是同样的请求参数,对于服务器而言是一样的(没有记忆力,比如我们之前写的很多代码今天和明天跑出来的是一样的结果)但是我们实际开发种,我们希望在登录网站后第二次访问服务器的时候,服务器已经知道这个请求之前已经登录过了.第一次见,把你的信息存储下来,第二次见,直接从存储的地方取出来.但是一个服务器,无状态的才是好的,别人法多少个请求都不会影响结果(这个是后话了)
因此像网站这种有记忆力的,可以在前端存储用户信息,也可以在后端存储用户信息.
存储用户信息:
- 客户端存: 我们的浏览器会给每个网站开辟一个固定的大小(存储内容的地方)让他去存储这些信息.这个地方就是cookie(前端)
- 服务端存: 在服务器存储的地方就是session(后端)
Cookie和Session的介绍
会话:Seesion,它是服务端存储的一个信息
比如淘宝的在线客服,这个就是一个会话(短暂时间产生的对话),它就是客户和服务器之间的不中断的请求响应.
在计算机领域,会话是⼀个客⼾与服务器之间的不中断的请求响应. 对客⼾的每个请求,服务器能够识别出请求来⾃于同⼀个客⼾.当⼀个未知的客⼾向Web应⽤程序发送第⼀个请求时就开始了⼀个会话.当客⼾明确结束会话或服务器在⼀个时限内没有接受到客⼾的任何请求时,会话就结束了.
⽐如我们打客服电话
每次打客服电话,是⼀个会话.挂断电话,会话就结束了下次再打客服电话,⼜是⼀个新的会话.
如果我们⻓时间不说话,没有新的请求,会话也会结束.
交互过程简要概括:
-
客户端 输入用户名和密码
-
服务器,进行校验,校验成功返回信息:token/令牌(学生证)
-
客户端访问时,带上token
-
服务器校验token是否合法,合法就返回响应信息
token是身份的证明,sessionID是身份证明的一种实现方式,因此,session是token,但是token不是session.
服务器同⼀时刻收到的请求是很多的.服务器需要清楚的区分每个请求是从属于哪个⽤⼾,也就是属于哪个会话,就需要在服务器这边记录每个会话以及与⽤⼾的信息的对应关系.
Session是服务器为了保存⽤⼾信息⽽创建的⼀个特殊的对象.因此Session本质就是一个哈希表,存储了⼀些键值对结构.Key就是SessionID,Value就是⽤⼾信息(⽤⼾信息可以根据需求灵活设计).cookie里面存储了sessionID,服务器从cookie中获取SessionID进行查找
注意: 客户端保存用户信息的方式不止cookie一种
服务器和客户端之间的交互过程详细概括:
-
当用户端第一次登录的时候,服务器会在Session里面增加一个记录,然后我们把sessionId通过HTTP响应的Set-Cookie字段返回给客户端,也就是返回一个token(里面有sessionId),然后客户端里面的cookie就通过set-cookie设置了它的值,里面包含了sessionID.(cookie里面存储了sessionID,服务器从cookie中获取SessionID进行查找).
-
当后面客户端再次登录向服务器发送请求的时候带上cookie,客户端在收到请求后,通过cookie里面的SessionID拿到对应session的信息(id,用户名..)如果找到了就返回客户信息,如果没找到就在Session里面创建新的记录,把SessionId返回给客户端
Session和Cookie的区别
Cookie是客户端保存用户信息的⼀种机制.Session是服务器端保存用户信息的⼀种机制.
Cookie和Session之间主要是通过SessionId关联起来的,SessionId是Cookie和Session之间的桥梁Cookie和Session经常会在⼀起配合使⽤.但是不是必须配合.完全可以⽤Cookie来保存⼀些数据在客⼾端.这些数据不⼀定是⽤户⾝份信息,也不⼀定是SessionId. Session中的sessionId也不需要⾮得通过Cookie/Set-Cookie传递,⽐如通过URL传递.
注意: Session默认是保存在内存中的.如果重启服务器则Session数据就会丢失.
获取Cookie
方法一:使用Servlet内置对象
先去设置一下cookie(cookie的信息是可以直接在浏览器进行伪造的,因此服务器是需要进行辨别的)信息,启动程序,按f12,设置cookie信息.
浏览器点击刷新,出现了结果:
或者在postman里面设置
代码解析:
方法二:
升级的点:
运行结果:
获取Session
方法一:使用Servlet内置对象
先设置session:
获取session:
方法二:
升级内容:
运行结果:
方法三:
升级内容:
运行结果:
9> 获取Header
方法一:使用Servlet内置对象
注意:抓啥,我们设置啥
方法二:
升级:
运行结果:
2.3 响应
2.3.1 返回视图(@Controller)
我们想返回的是html这个页面,而不是返回字符串,因此这个不是我们想要的结果
但是此时我们换一个注解把@RestController换为@Controller试试.此时出现了我们想要的页面了.
接下来我来@RestController和@Controller这俩个注解的区别,早期我们的MVC,我们的后端会返回视图,现在我们开发更加流行前后端分离的模式,我们java不再处理前端的内容,因此MVC也发生了一些改变,Controller:接收参数,返回响应.我们后端就只是根据参数,然后返回我们的数据,不会再返回一个页面了(这个数据怎么展示在页面是前端的问题)前后端进行分离了.@RestController(返回的是数据) = @Controller(返回视图) + @ResponseBody(返回的是数据,牛逼一点).因此@RestController是个复合注解
注意:@RestController,@Controller都只能修饰类,不能修饰方法,@ResponseBody方法和类都能修饰,如果作用在类上,表示该类的所有方法,返回的都是数据,如果作用在方法上,表示该方法返回的是数据.
我们来看看@RestController的源码:
2.3.2 返回数据(@ResponseBody)
2.3.3 返回HTML代码片段
2.3.4 返回JSON对象
2.3.5 设置状态码
SpringMVC会根据我们⽅法的返回结果⾃动设置响应状态码,程序员也可以⼿动指定状态码通过SpringMVC的内置对象HttpServletResponse提供的⽅法来进⾏设置.注意:我们设置的状态码不影响页面的展示
补充概念:REST
接口设计的架构风格REST:资源表现层状态转移,网络上所有的资源,资源的表现形式有图片,world...形式.浏览器访问资源,资源的状态发生变化(增删改查).
基于它产生了RESTful API(这个就是接口:程序写好了,程序员告诉用户怎么去用).比如我们同样使用这个url,http://127.0.0.1:8080/blog,但是我们的请求分别是get和post,get就是获取谋篇博客的资源.post就是提交博客.也就是一个url对应的操作是不同的,但是企业中一般会把get和post这个功能放进代码里面,一般会使用http://127.0.0.1:8080/getBlog
2.3.6设置Header(了解即可)
Http响应报头也会向客户端传递⼀些附加信息,比如服务程序的名称,请求的资源已移动到新地址等,如:Content-Type,Local等.我们通过@RequestMapping来进行实现.先来看看它的源码
我们通过设置produces属性的值,设置响应的报头Content-Type
代码:后端
package org.xiaobai.springmvc_tets.MVCFistclass;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RequestMapping("/res")
//@RestController
@Controller//换了一个注解,返回的是视图
public class ResponseController {
//TODO 返回静态页面
@RequestMapping("/index")
public String index() {
return "/index.html";//这个返回的只是一个字符串,我们要的是一个数据,我们把上面的注解进行修改
}
@ResponseBody//返回的是数据
@RequestMapping("/indexData")
public String indexD() {
return "返回数据";//这个返回的只是一个字符串,我们要的是一个数据,我们把上面的注解进行修改
}
//TODO 返回HTML代码片段
@ResponseBody
@RequestMapping("/indexData2")
public String indexD2() {
//我们返回的数据里面有HTML代码的时候,它就会进行解析
return "<h1>我是中国人</h1>";//这个返回的只是一个字符串,我们要的是一个数据,我们把上面的注解进行修改
}
//TODO 返回对象
@ResponseBody
@RequestMapping("/getMap")
public HashMap<String,String> getMap(){
HashMap<String,String> map = new HashMap<>();
map.put("k1","v1");
map.put("k2","v2");
map.put("k3","v3");
return map;
}
//TODO 设置状态码
@ResponseBody
@RequestMapping("/getMap2")
public String setStatus(HttpServletResponse response){
response.setStatus(418);
return "设置状态码";
}
//TODO 设置Header
//method()
//produces()
@ResponseBody
@RequestMapping(value = "/setContentType",produces = "application/json")//设置返回值类型
public String setContentType(){
return "{\"OK\":1}";
}
}
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
hello,Spring MVC,我是Index页面
</body>
</html>