【JavaEE】-- spring web MVC入门
1. 什么是Spring Web MVC
SpringWebMVC是基于ServletAPI构建的原始Web框架,从一开始就包含在Spring框架中。它的正式名称“SpringWebMVC”来自其源模块的名称(Spring-webmvc),但它通常被称为"SpringMVC".
什么是Servlet呢?
Servlet是一种实现动态页面的技术.准确来讲Servlet是一套JavaWeb开发的规范,或者说是一套JavaWeb开发的技术标准.只有规范并不能做任何事情,必须要有人去实现它.所谓实现Servlet规范,就是真正编写代码去实现Servlet规范提到的各种功能,包括类、方法、属性等Servlet规范是开放的,除了Sun公司,其它公司也可以实现Servlet规范,目前常见的实现了Servlet规范的产品包括Tomcat、Weblogic、Jetty、Jboss、WebSphere等,它们都被称为"Servlet容器".Servlet容器用来管理程序员编写的Servlet类.
总之,Spring web MVC是一个web框架,简称为Spring MVC .
1.1 MVC定义
MVC是ModelViewController的缩写,它是软件工程中的一种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分.
- View(视图)指在应用程序中专门用来与浏览器进行交互,展示数据的资源.
- Model(模型)是应用程序的主体部分,用来处理程序中数据逻辑的部分.
- Controller(控制器)可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型.
1.2 什么是Spring MVC
MVC是一种架构设计模式,也是一种思想,而SpringMVC是对MVC思想的具体实现.除此之外,SpringMVC还是一个Web框架.
总结来说,SpringMVC是一个实现了MVC模式的Web框架.
所以,SpringMVC主要关注有两个点:
1.MVC
2.Web框架
Spring Boot只是实现Spring MVC 的其中一种方式而已。Spring Boot 可以添加很多依赖,借助这些依赖实现不同的功能,Spring Boot 通过添加Spring Web MVC 框架,来实现web功能。
不过Spring 在实现MVC时,也结合自身项目的特点,做了一些改变。
2. 学习Spring MVC
既然是Web框架,那么当用户在浏览器中输入了url之后,我们的SpringMVC项目就可以感知到用户的请求,并给予响应.
学习SpringMVC,重点也就是学习如何通过浏览器和用户程序进行交互.主要分以下三个方面:
1. 建立连接: 将用户(浏览器)和 Java程序连接起来,也就是访问一个地址能够调用到我们的Spring程序。
2. 请求: 用户请求的时候会带一些参数,在程序中要想办法获取到参数,所以请求这块主要是获取参数的功能。
3. 响应: 执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应。
2.1 项目准备
SpringMVC项目创建和SpringBoot创建项目相同,在创建的时候选择SpringWeb就相当于创建了SpringMVC的项目.
创建项目时,勾选上SpringWeb模块即可,如下图所示:
2.2 建立连接
在SpringMVC中使用@RequestMapping来实现URL路由映射,也就是浏览器连接程序的作用我们先来看看代码怎么写:
创建一个HelloController类, 实现用户通过浏览器和程序的交互,具体实现代码如下:
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello, spring boot!";
}
}
方法名和路径名称无需一致。
接下来访问:http://127.0.0.1:8080/hello,就可以看到程序返回的数据了。
2.2.1 @RequestMapping 注解介绍
@RequestMapping是SpringWebMVC应用程序中最常被用到的注解之一,它是用来注册接口的路由映射的.
表示服务收到请求时,路径为/hello的请求就会调用hello个方法的代码。
路由映射: 当用户访问一个URL时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射.
既然@RequestMapping已经可以达到我们的目的了,我们为什么还要加@RestController呢?
我们把@RestController去掉,再来访问一次:
可以看到,程序报了404,找不到该页面.
这就是@RestController起到的作用.
一个项目中,会有很多类,每个类可能有很多的方法,Spring程序怎么知道要执行哪个方法呢?
Spring会对所有的类进行扫描,如果类加了注解@RestController,Spring才会去看这个类里面的方法有没有加@RequestMapping这个注解.
2.2.2 @RequestMapping 使用
@RequestMapping既可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类路径+方法路径.
@RequestMapping标识一个类:设置映射请求的请求路径的初始信息
@RequestMapping标识一个方法:设置映射请求请求路径的具体信息
@RequestMapping("/hello1")
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello, spring boot!";
}
}
访问地址:http://127.0.0.1:8080/hello1/hello
注意:
@RequestMapping的URL路径最前面加不加/(斜杠)都可以,Spring程序启动时,会进行判断,如果前面没有加/,Spring会拼接上一个/。但是建议加上!
@RequestMapping("hello1")
@RestController
public class HelloController {
@RequestMapping("hello")
public String hello(){
return "hello, spring boot!";
}
}
不加也是可以正常响应的。
2.2.3 @RequestMapping 是GET还是POST请求?
GET请求:
浏览器发送的请求类型都是get,通过以上案例,可以看出来@RequestMapping支持get请求.
POST请求:
我们通过form表单来构造请求:创建test.html
html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/hello1/hello" method="post">
<input type="submit" value="提交">
</form>
</body>
</html>
前端代码放在static目录下,访问方式为:http://127.0.0.1:8080/test.html
如果有多层目录,访问链接从static目录开始写
上图的访问链接为:http://127.0.0.1:8080/html/test.html
从运行结果可以看出:@RequestMapping既支持Get请求,又支持Post请求.同理,也支持其他的请求方式.
指定GET/POST方法类型
@RestController
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String hello(){
return "hello, spring boot!";
}
}
2.3 请求
访问不同的路径,就是发送不同的请求.在发送请求时,可能会带一些参数,所以学习Spring的请求,主要是学习如何传递参数到后端以及后端如何接收。
2.3.1 传递单个参数
接收单个参数,在SpringMVC中直接用方法中的参数就可以,比如以下代码:
@RestController
public class HelloController {
@RequestMapping("/r3")
public String r3(String name){
return "接收到参数name:" + name;
}
}

注意事项:
使用基本类型来接收参数时,参数必须传(除boolean类型),否则会报500错误
类型不匹配时,会报400错误
@RestController
public class HelloController {
@RequestMapping("/r4")
public String r4(int age){
return "接收到参数age:" + age;
}
}
-
正常传递参数

通过Fiddler观察请求和响应,HTTP响应状态码为200,Content-Type 为text/html
-
不传递age参数

通过Fiddler观察请求和响应,HTTP响应状态码为500
观察项目控制台的错误日志:
-
传递参数类型不匹配
通过Fiddler观察请求和响应, HTTP响应状态码为400

对于包装类型,如果不传对应参数,Spring接收到的数据则为null。
所以企业开发中,对于参数可能为空的数据,建议使用包装类型
2.3.2 传递多个参数
@RestController
public class HelloController {
@RequestMapping("/r5")
public String r5(String name, Integer age){
return "接收到参数name:" + name + "接收到参数age:" + age;
}
}

当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置是不影响后端获取参数的结果
2.3.3 传递对象
public class Person {
private String name;
private Integer age;
private String password;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@RestController
public class HelloController {
@RequestMapping("/r6")
public String r6(Person person){
return "接收到参数person:" + person;
}
}

Spring会根据参数名称自动绑定到对象的各个属性上,如果某个属性未传递,则赋值为null(基本类型则赋值为默认初识值,比如int类型的属性,会被赋值为0).
2.3.4 后端参数重命名(后端参数映射)
某些特殊的情况下,前端传递的参数key和我们后端接收的key可以不一致,比如前端传递了一个time给后端,而后端是使用createtime字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使用@RequestParam来重命名前后端的参数值。
具体示例如下,后端实现代码:
@RestController
public class HelloController {
@RequestMapping("/r7")
public String r7(@RequestParam("q") String keyword){
return "搜索词为:" + keyword;
}
}

可以看到,Spring可以正确的把浏览器传递的参数q绑定到了后端参数keyword参数上。
此时,如果浏览器使用keyword进行参数传递呢?
错误日志信息:
控制台打印日志显示:请求参数keyword不存在。
结论:
- 使用 @RequestParam进行参数重命名时,请求参数只能和@RequestParam声明的名称一致,才能进行参数绑定和赋值。
- 使用@RequestParam 进行参数重命名时,参数就变成了必传参数.
非必传参数设置
如果我们的实际业务前端的参数是一个非必传的参数,针对上述问题,如何解决呢?
先来了解下参数必传的原因,我们查看@RequestParam注解的实现细节就可以发现端倪,注解实现如下:
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
可以看到required的默认值为true,表示含义就是:该注解修饰的参数默认为必传。
既然如此,我们可以通过设置@RequestParam中的required=false来避免不传递时报错,具体实现如下:
@RestController
public class HelloController {
@RequestMapping("/r7")
public String r7(@RequestParam(value = "q", required = false) String keyword){
return "搜索词为:" + keyword;
}
}
可以看到,添加required=false之后,time前面也加了key,变成了value=“time”.
注解属性赋值时,没有指明key的话,默认为value属性.
如果需要有多个属性进行赋值时,需要写上key
2.3.5 传递数组
SpringMVC可以自动绑定数组参数的赋值
后端实现代码:
@RestController
public class HelloController {
@RequestMapping("/r8")
public String r8(int[] arr){
return Arrays.toString(arr);
}
}

2.3.6 传递集合
集合参数:和数组类似,同一个请求参数名有为多个,且需要使用@RequestParam绑定参数关系
默认情况下,请求中参数名相同的多个值,是封装到数组.如果要封装到集合,要使用@RequestParam 绑定参数关系.
请求方式和数组类似:
@RestController
public class HelloController {
@RequestMapping("/r9")
public String r9(@RequestParam List<Integer> list){
return "size" + list.size() + ", list:" + list;
}
}

2.3.7 传递JSON数据
JSON概念
JSON:JavaScript Object Notation【JavaScript对象表示法】
JSON是一种轻量级的数据交互格式.它基于ECMAScript(欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。–百度百科
简单来说:JSON就是一种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串。主要负责在不同的语言中数据传递和交换。
JSON与Javascript的关系
没有关系,只是语法相似,js开发者能更快的上手而已,但是他的语法本身比较简单,所以也很好学。
JSON语法
JSON是一个字符串,其格式非常类似于JavaScript对象字面量的格式。
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast "]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": ["Million tonne punch", "Damage resistance", "Superhuman reflexes "]
},
{
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": ["Immortality", "Heat Immunity", "Inferno",
"Teleportation", "Interdimensional travel"
]
}
]
}
也可以压缩表示:
{"squadName":"Super hero squad","homeTown":"Metro
City","formed":2016,"secretBase":"Super tower","active":true,"members":
[{"name":"Molecule Man","age":29,"secretIdentity":"Dan Jukes","powers":
["Radiation resistance","Turning tiny","Radiation blast"]},{"name":"Madame
Uppercut","age":39,"secretIdentity":"Jane Wilson","powers":["Million tonne
punch","Damage resistance","Superhuman reflexes"]},{"name":"Eternal
Flame","age":1000000,"secretIdentity":"Unknown","powers":["Immortality","Heat
Immunity","Inferno","Teleportation","Interdimensional travel"]}]}
和上面描述的数据一样,只不过上面的进行了格式化,更易读。
JSON的语法
- 数据在
键值对(Key/Value)中- 数据由逗号
,分隔- 对象用
{}表示- 数组用
[]表示- 值可以为对象,也可以为数组,数组中可以包含多个对象
JSON的两种结构
- 对象:大括号
{}保存的对象是一个无序的键值对 集合.一个对象以左括号{开始,右括号}结束。每个"键"后跟一个冒号:,键值对使用逗号,分隔. - 数组:中括号
[]保存的数组是值(value)的有序集合.一个数组以左中括号[开始,右中括号]结束,值之间使用逗号,分隔。
JSON字符串和JAVA对象互转
JSON本质上是一个字符串,通过文本来存储和描述数据。
SpringMVC框架也集成了JSON的转换工具,我们可以直接使用,来完成JSON字符串和Java对象的互转。
本质上是jackson-databind提供的功能,SpringMVc框架中已经把该工具包引l入了进来,咱们直接使用即可,如果脱离SpringMVc使用,需要引入相关依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.5</version>
</dependency>
JSON的转换工具包有很多,jackson-databind只是其中的一种
public class JSONTests {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Person person = new Person();
person.setName("zhangsan");
person.setAge(18);
person.setPassword("123456");
//对象转JSON
String s = objectMapper.writeValueAsString(person);
System.out.println(s);
//JSON转对象
String json = "{\"name\":\"zhangsan\",\"password\":\"123456\",\"age\":15}";
Person person1 = objectMapper.readValue(json, Person.class);
System.out.println(person1);
}
}
使用ObjectMapper对象提供的两个方法,可以完成对象和JSON字符串的互转
writeValueAsString:把对象转为JSON字符串
readValue:把字符串转为对象
JSON优点
- 单易用:语法简单,易于理解和编写,可以快速地进行数据交换
- 跨平台支持:JSON可以被多种编程语言解析和生成,可以在不同的平台和语言之间进行数据交换和传输。
- 轻量级:相较于XML格式,JSON数据格式更加轻量级,传输数据时占用带宽较小,可以提高数据传输速度。
- 易于扩展:JSON的数据结构灵活,支持嵌套对象和数组等复杂的数据结构,便于扩展和使用。
- 安全性:JSON数据格式是一种纯文本格式,不包含可执行代码,不会执行恶意代码,因此具有较高的安全性。
传递JSON对象
接收JSON对象,需要使用@RequestBody 注解
RequestBody:请求正文,意思是这个注解作用在请求正文的数据绑定,请求参数必须写在请求正文中。
后端实现:
@RequestMapping("/r10")
public Object r10(@RequestBody Person person){
return person.toString();
}
使用Postman来发送json请求参数
通过Fiddler观察一下请求参数:
去除掉@RequestBody,就会发生后端未能成功给Person对象赋值:
@RequestMapping("r11")
public Object r11(Person person){
return person.toString();
}

2.3.8 获取URL中参数@PathVariable
pathvariable:路径变量
和字面表达的意思一样,这个注解主要作用在请求URL路径上的数据绑定
默认传递参数写在URL上,SpringMVC就可以获取到
后端实现代码:
@RequestMapping("/article/{articleId}")
public String r12(@PathVariable("articleId") Integer articleId){
return "文章Id:" + articleId;
}

多个参数
@RequestMapping("/article/{articleId}/{type}")
public String r13(@PathVariable("articleId") Integer articleId, @PathVariable("type") String type){
return "文章Id:" + articleId + " ,文章类型:" + type;
}

如果方法参数名称和需要绑定的URL中的变量名称一致时,可以简写,不用给@PathVariable的属性赋值,如上述例子中的id变量。
如果方法参数名称和需要绑定的URL中的变量名称不一致时,需要@PathVariable的属性value赋值,如上述例子中的userName变量.
2.3.9 上传文件@RequestPart
@RequestMapping("r14")
public String r14(MultipartFile file) throws IOException {
//获取文件名
String originalFilename = file.getOriginalFilename();
//放到D盘
File destFile = new File("D:\\temp\\" + originalFilename);
//上传文件
file.transferTo(destFile);
return "文件上传成功!";
}


在postman上填写的key值需要和接口中的参数保持一致才能保证上传成功。
那如果有不一样的需求呢?
@RequestMapping("r15")
public String r15(@RequestPart("file111") MultipartFile file) throws IOException {
//获取文件名
String originalFilename = file.getOriginalFilename();
//放到D盘
File destFile = new File("D:\\temp\\" + originalFilename);
//上传文件
file.transferTo(destFile);
return "文件上传成功!";
}

文件上传功能实现?
- 使用第三方的文件管理服务(OSS\七牛云)
- 自己搭建一个文件管理服务
2.3.10 获取Cookie/Session
Cookie && Session
HTTP协议自身是属于"无状态”协议
“无状态”的含义指的是:默认情况下HTTP协议的客户端和服务器之间的这次通信,和下次通信之间没有直接的联系
但是实际开发中,我们很多时候是需要知道请求之间的关联关系的.
例如登陆网站成功后,第二次访问的时候服务器就能知道该请求是否是已经登陆过了.

上述图中的“令牌”通常就存储在Cookie字段中。服务器需要记录“令牌”信息,以及令牌对应的用户信息,这个就是Session机制所做的工作。
理解Session
会话:对话的意思。

在计算机领域,会话是一个客户与服务器之间的不中断的请求响应,对客户的每个请求,服务器能够识别出请求来自于同一个客户,当一个未知的客户向Web应用程序发送第一个请求时就开始了一个会话,当客户明确结束会话或服务器在一个时限内接收到客户的任何请求时,会话就结束了。
服务器同一时刻收到的请求是很多的,服务器需要清楚的区分每个请求是属于哪个用户,也就是属于哪个会话,就需要在服务器这边记录每个会话以及用户的信息的对应关系。
Session是服务器为了保存用户信息而创建的一个特殊的对象。

Session的本质就是一个“哈希表”,存储了一些键值对结构,key就是SessionID,Value就是用户信息(用户信息可以根据需求灵活设计)。
Sessionld是由服务器生成的一个"唯一性字符串",从Session机制的角度来看,这个唯一性字符串称为"Sessionld"但是站在整个登录流程中看待,也可以把这个唯一性字符串称为"token"
上述例子中的令牌ID,就可以看做是Sessionld,只不过令牌除了ID之外,还会带一些其他信息,比如时间,签名等.

- 当用户登陆的时候,服务器在Session中新增一个新记录,并把sessionld返回给客户端.(通过HTTP响应中的Set-Cookie字段返回).
- 客户端后续再给服务器发送请求的时候,需要在请求中带上sessionld.(通过HTTP请求中的Cookie字段带上).
- 服务器收到请求之后,根据请求中的sessionld在Session信息中获取到对应的用户信息,再进行后续操作.找不到则重新创建Session,并把SessionlD返回.

Session默认是保存在内存中的,如果重启服务器则Session数据就会丢失。
Cookie 和 Session的区别
- Cookie是客户端保存用户信息的一种机制.
- Session是服务器端保存用户信息的一种机制Cookie和Session之间主要是通过Sessionld关联起来的,Sessionld是Cookie和Session之间的桥梁
- Cookie和Session经常会在一起配合使用.但是不是必须配合.
- 完全可以用Cookie来保存一些数据在客户端.这些数据不一定是用户身份信息,也不一定是Sessionld
- Session中的sessionld也不需要非得通过Cookie/Set-Cookie传递,比如通过URL传递.
获取Cookie
- 传统获取Cookie
@RestController
@RequestMapping("/request")
public class CookieSessionController {
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if (cookies != null){
for (Cookie cookie:cookies) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
}
return "获取Cookie成功";
}
}
通过浏览器伪造Cookie:
项目控制台:
通过postman来伪造cookie:

使用127.0.0.1访问:
项目日志打印:
使用localhost来访问:
项目控制台打印:
- 获取Cookie2
@RequestMapping("getCookie2")
public String getCookie2(@CookieValue("name") String name){
return "获取Cookie成功" + name;
}
Cookie是由多个键值对组成的,从Cookie中获取key为name的值。

获取Session
- 设置Session
@RequestMapping("setSession")
public String setSession(HttpServletRequest request){
//request.getSession 就相当于从Cookie中获取到SessionId,
//根据SessionId获取session信息
HttpSession session = request.getSession();
//getSession默认是true:表示如果获取不到session,就自动创建新的session
// false:表示如果获取不到session,不进行创建
//一般都是指定为false
session.setAttribute("name", "session");
session.setAttribute("age", 1);
return "设置session成功";
}

获取session
@RequestMapping("getSession")
public String getSession(HttpServletRequest request){
HttpSession session = request.getSession(false);
String name = (String) session.getAttribute("name");
Integer age = (Integer) session.getAttribute("age");
return "获取session成功" + " name = " + name + " age = " + age;
}


@RequestMapping("getSession2")
public String getSession2(HttpSession session ){
String name = (String) session.getAttribute("name");
Integer age = (Integer) session.getAttribute("age");
return "获取session成功" + " name = " + name + " age = " + age;
}


@RequestMapping("getSession3")
public String getSession3(@SessionAttribute("name") String name){
return "获取session成功" + " name = " + name ;
}


2.3.11 获取Header
1. 传统获取方式
@RequestMapping("getHeader")
public String getHeader(HttpServletRequest request){
String userAgent = request.getHeader("User-Agent");
return "从Header中获取信息, user-agent: " + userAgent;
}

2. 注解的方式
@RequestMapping("getHeader2")
public String getHeader2(@RequestHeader("User-Agent") String userAgent){
return "从Header中获取信息, user-agent: " + userAgent;
}

更多推荐









所有评论(0)