一篇文章带你了解,走进,使用Spring Web MVC(非常容易理解的举例,让大家了解几个容易混淆的关系)

本文章内容分为
1. 学习常⻅的Spring Web MVC注解
2. 掌握使⽤SpringMVC来完成基础的功能开发
3. 了解MVC和三层架构的设计模式
4. 掌握企业开发中的⼀些命名规范

1.什么是 Spring Web MVC?

官⽅对于 Spring MVC 的描述是这样的:
Spring Web MVC is the original web framework built on the Servlet API and has been included
in the Spring Framework from the very beginning. The formal name, "Spring Web MVC",
comes from the name of its source module (spring-webmvc)
引⽤来⾃: https://docs.spring.io/spring-framework/reference/web/webmvc.html
翻译为中⽂:
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的 正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为"Spring
MVC".

什么是Servlet呢?
Servlet 是⼀种实现动态⻚⾯的技术. 准确来讲Servlet是⼀套 Java Web 开发的规范,或者说是⼀套
Java Web 开发的技术标准. 只有规范并不能做任何事情,必须要有⼈去实现它. 所谓实现 Servlet 规
范,就是真正编写代码去实现 Servlet 规范提到的各种功能,包括类、⽅法、属性等.
Servlet 规范是开放的,除了 Sun 公司,其它公司也可以实现 Servlet 规范,⽬前常⻅的实现了
Servlet 规范的产品包括 Tomcat、Weblogic、Jetty、Jboss、WebSphere 等,它们都被称
为"Servlet 容器". Servlet 容器⽤来管理程序员编写的 Servlet 类.

从上述定义我们可以得出⼀个信息: Spring Web MVC 是⼀个 Web 框架.
下⾯咱们简称之为: Spring MVC

1.1 MVC定义

MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构设计模式,它把软件系统分 为模型、视图和控制器三个基本部分.
View(视图) 指在应⽤程序中专⻔⽤来与浏览器进⾏交互,展⽰数据的资源.
Model(模型) 是应⽤程序的主体部分,⽤来处理程序中数据逻辑的部分.
Controller(控制器)可以理解为⼀个分发器,⽤ 来决定对于视图发来的请求,需要⽤哪⼀个模型来处理,以及处理完后需要跳回到哪⼀个视图。即 ⽤来连接视图和模型
⽐如去饭店吃饭
客⼾进店之后, 服务员来接待客⼾点餐, 客⼾点完餐之后, 把客⼾菜单交给前厅, 前厅根据客⼾菜单 给后厨下达命令. 后厨负责做饭, 做完之后, 再根据菜单告诉服务员, 这是X号餐桌客⼈的饭.
在这个过程中
服务员就是View(视图), 负责接待客⼾, 帮助客⼾点餐, 以及给顾客端饭
前厅就是Controller(控制器), 根据⽤⼾的点餐情况, 来选择给哪个后厨下达命令.
后厨就是Model(模型), 根据前厅的要求来完成客⼾的⽤餐需求
⽐如去公司⾯试
我们到了公司之后, HR会给我们安排会议室, 根据候选⼈去通知不同的部⻔来安排⾯试, ⾯试结束 后, 由HR来告诉⾯试结果
在这个过程中
HR就是View(视图), 负责接待候选⼈, 并告知候选⼈⾯试结果
不同的部⻔, 就是Controller(控制器), HR根据候选⼈来选择对应的部⻔来进⾏⾯试
⾯试官, 就是Model层, 来处理⾯试这个事情.

 1.2 什么是Spring MVC ?

MVC 是⼀种架构设计模式, 也⼀种思想, ⽽ Spring MVC 是对 MVC 思想的具体实现. 除此之外, Spring MVC还是⼀个Web框架.
总结来说,Spring MVC 是⼀个实现了 MVC 模式的 Web 框架.
所以, Spring MVC主要关注有两个点:
1. MVC
2. Web框架   Spring MVC 全称是 Spring Web MVC
其实, Spring MVC 我们在前⾯已经⽤过了, 在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架 其实就是 Spring MVC 框架:
可以看到,Spring Web的介绍是:
这时候可能有些人就懵了, 前⾯创建的不是SpringBoot项⽬吗? 怎么⼜变成了Spring MVC项⽬? 他们 之间到底有着什么样的关系?
SpringBoot是2014年发布的, Spring 是2004年发布的, 在2014年发布之前, 就不能⽤Spring实现MVC 架构吗? 显然不是了.
Spring Boot 只是实现Spring MVC的其中⼀种⽅式⽽已.
Spring Boot 可以添加很多依赖, 借助这些依赖实现不同的功能. Spring Boot 通过添加Spring Web
MVC框架, 来实现web功能.
⽐如: 厨房可以⽤来做饭, 但真实实现做饭功能的是⽕以及各种做饭相关的⻝材和⼯具.
厨房就好⽐是SpringBoot, 厨房可以装柜⼦, 实现收纳功能, 装燃⽓灶等, 实现做饭功能.
做饭这个事, 就是MVC, 在⼏千年前, 有⽕有⻝材就可以实现做饭
不过Spring在实现MVC时, 也结合⾃⾝项⽬的特点, 做了⼀些改变, 相对⽽⾔, 下⾯这个图或许更加合适 ⼀些.
不过核⼼没变
⽐如上⾯的例⼦中, 去饭店吃饭. ⼀些饭店是前厅来负责接待客⼾, 帮助客⼾点餐, 也就是Controller来 负责接收⽤⼾的请求.
去公司⾯试, 直接由⾯试官来接待候选⼈, 省去了HR中间的交接过程.

 2.学习Spring MVC

既然是 Web 框架, 那么当⽤⼾在浏览器中输⼊了 url 之后,我们的 Spring MVC 项⽬就可以感知到⽤⼾ 的请求, 并给予响应.
咱们学习Spring MVC, 重点也就是学习如何通过浏览器和⽤⼾程序进⾏交互.
主要分以下三个⽅⾯:
1. 建⽴连接:将⽤⼾(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调⽤到我们的
Spring 程序。
2. 请求: ⽤⼾请求的时候会带⼀些参数,在程序中要想办法获取到参数, 所以请求这块主要是 获取参数 的功能.
3. 响应: 执⾏了业务逻辑之后,要把程序执⾏的结果返回给⽤⼾, 也就是响应.
⽐如⽤⼾去银⾏存款
1. 建⽴连接: 去柜台
2. 请求: 带着银⾏卡, ⾝份证去存款
3. 响应: 银⾏返回⼀张存折.

对于 Spring MVC 来说,掌握了以上 3 个功能就相当于掌握了 Spring MVC.

2.1 项⽬准备

Spring MVC 项⽬创建和 Spring Boot 创建项⽬相同,在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项⽬.
Spring MVC 使⽤ Spring Boot 的⽅式创建
Spring MVC更早期的实现⽅式, 课程中不再讲解

 创建项⽬时, 勾选上 Spring Web 模块即可,如下图所⽰:

2.2 建⽴连接

在 Spring MVC 中使⽤ @RequestMapping 来实现 URL 路由映射 ,也就是浏览器连接程序的作⽤
我们先来看看代码怎么写
创建⼀个 UserController 类,实现⽤⼾通过浏览器和程序的交互,具体实现代码如下:
package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MVC_07_22_1 {
@RequestMapping("/sayhi")
    public String methed(){
    return "hello 各位";
}
}

⽅法名和路径名称⽆需⼀致
接下来访问: http://127.0.0.1:8080/sayHi , 就可以看到程序返回的数据了

2.2.1 @RequestMapping 注解介绍

@RequestMapping 是 Spring Web MVC 应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的
路由映射的.
表⽰服务收到请求时, 路径为 /sayHi 的请求就会调⽤ sayHi 这个⽅法的代码.
路由映射: 当⽤⼾访问⼀个 URL 时, 将⽤⼾的请求对应到程序中某个类的某个⽅法的过程就叫路由映射.
既然 @RequestMapping 已经可以达到我们的⽬的了, 我们为什么还要加 @RestController
呢?
我们把 @RestController 去掉, 再来访问⼀次:
可以看到, 程序报了404, 找不到该⻚⾯.
这就是 @RestController 起到的作⽤.
⼀个项⽬中, 会有很多类, 每个类可能有很多的⽅法, Spring程序怎么知道要执⾏哪个⽅法呢?
Spring会对所有的类进⾏扫描, 如果类加了注解@RestController, Spring才会去看这个类⾥⾯的⽅法 有没有加 @RequestMapping 这个注解, 当然他的作⽤不⽌这⼀点, 咱们先⽤, 后⾯再详细讲.

2.2.2 @RequestMapping 使⽤

@RequestMapping 即可修饰类,也可以修饰⽅法 ,当修饰类和⽅法时,访问的地址是类路径 + ⽅ 法路径.
@RequestMapping标识⼀个类:设置映射请求的请求路径的初始信息
@RequestMapping标识⼀个⽅法:设置映射请求请求路径的具体信
@RequestMapping("/user")
@RestController
public class UserController {
 @RequestMapping("/sayHi")
 public String sayHi(){
 return "hello,Spring MVC";
 }
}

访问地址: http://127.0.0.1:8080/user/sayHi
注意:
@RequestMapping 的URL 路径最前⾯加不加 / (斜杠)都可以, Spring程序启动时, 会进⾏判断, 如果 前⾯没有加 / , Spring会拼接上⼀个 /.
@RequestMapping("user")
@RestController
public class UserController {
 @RequestMapping("sayHi")
 public String sayHi(){
 return "hello,Spring MVC";
 }
}
访问 http://127.0.0.1:8080/user/sayHi , 依然可以正确响应
通常情况下, 我们加上 /.
@RequestMapping 的URL路径也可以是多层路径, 最终访问时, 依然是 类路径 + ⽅法路径 .
@RequestMapping("/user/m1")
@RestController
public class UserController {
 @RequestMapping("/say/hi")
 public String sayHi(){
 return "hello,Spring MVC";
 }
}
访问路径: http://127.0.0.1:8080/user/m1/say/hi

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="/user/sayHi" method="post">
 <input type="submit" value="提交">
 </form>
</body>
</html>
前端代码放在static⽬录下, 访问⽅式为: http://127.0.0.1:8080/test_1.html
如果有多层⽬录, 访问链接从static⽬录开始写.
访问⽅式为: http://127.0.0.1:8080/html/test_1.html
从运行结果可以看出 @RequestMapping既可以支持GET和又支持POST,也支持其他的请求方式.
那如何指定类型呢?
我们可以显示的指定 @RequestMapping来接收POST的情况.如下所示:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@RestController
public class UserController {
    @RequestMapping(value = "/getRequest",method= RequestMethod.POST)
   public String sayHi(){
        return "get request...";
    }
}

2.3 Postman介绍

从上面的案例中,也发现了个新的问题,就是我们测试后端方法时,还需要去写前端代码.这对我们来说,是一件麻烦又痛苦的事情.
随着互联网的发展,也随着项目难度的增加,企业也按照开发的功能,把人员拆分成了不同的团队.界面显示交给"前端开发工程师",业务逻辑的实现交给"后端开发工程师"后端开发工程师,不要求也不需要掌握前端技能了.那后端开发工程师,如何测试自己的程序呢?--使用专业的接口测试工具咱们课堂中使用的是Postman,接下来我们来学习Postman的使用.

2.3.1 下载安装Postman

下载链接: https://www.postman.com/downloads

2.3.2 创建请求

界面介绍

2.3.3 传参介绍

1. 传参介绍,也就是通过字符串来传参.

HTTP时,我们通过URL来访问互联网上某一个资源.

其中,字符串就是请求的参数.

2.form-data(完整表示为:multipart/form-data)

表单提交的数据,在 form 标签中加上 enctyped="multipart/form-data"
通常用于提交图片/文件.对应 Content-Type: multipart/form-data

3.x-www-form-urlencoded

form表单,对应 Content-Type: application/x-www-from-urlencoded

4.raw

可以上传任意格式的文本,可以上传test,json,xml,html等

2.4 请求

访问不同的路径,就是发送不同的请求,,在发送请求时,可能会带有一些参数,所以学习Spring的请求,主要是学习如何传递参数到后端以及后端如何接收.

传递参数,我们主要以浏览器和Postman形式:

后端开发人员无需过度关注如何传递参数,了解即可,实际开发中以Postman测试为主.比如餐厅的厨师,不关注用户是在店里下单,还是外卖平台下单,或者小程序下单,只需要知道如何接收订单,根据订单做出对应的菜看就可以了.

2.4.1 传递一个参数

接受单个参数,在Spring MVC中直接用方法中的参数就可以,比如:

@RequestMapping("/m1")
    public String m1(String name){
        return "接收参数name ="+name;
    }

我们使用Postman发送URL

http://127.0.0.1:8080/m1?name=spring

可以看到:Spring MVC会根据方法的参数名,找到相应的参数,赋值给方法.

如果参数不一致是拿不到结果的.

比如

注意:

使用基本类型来接收参数时,参数必须传(除boolean类型),否则会报500错误.

类型不匹配时,会报400错误.

我们可以通过日志来查看错误.

对于包装类型,如果不传对应的参数,Spring对应接收到的参数是null

所以企业开发中,对于参数可能传空的情况,都使用包装类型,这样就不会报错.

2.4.2 传递多个参数

如何接收多个参数?

和接收单个参数⼀样, 直接使⽤⽅法的参数接收即可. 使⽤多个形参.
 @RequestMapping("/m3")
    public Object m3(String name,int age){
        return name+age;
    }

http://127.0.0.1:8080/m3?name=spring&age=11

当有多个参数时,前后端进⾏参数匹配时,是以参数的名称进⾏匹配的,因此参数的位置是不影响后 端获取参数的结果.

2.4.3 传递对象

如果参数⽐较多时, ⽅法声明就需要有很多形参. 并且后续每次新增⼀个参数, 也需要修改⽅法声明.
我们不妨把这些参数封装为⼀个对象.
Spring MVC 也可以⾃动实现对象参数的赋值,⽐如 Person 对象:
package com.example.demo;

public class Person {
    String name;
    int age;
    String sex;

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public String toString(){
return name+"/"+age+"/"+sex;
    }
}


@RequestMapping("m4")
    public Person m4(Person p){
        return p;
    }

http://127.0.0.1:8080/m4?name=happy&age=11&sex=nan

Spring 会根据参数名称⾃动绑定到对象的各个属性上, 如果某个属性未传递, 则赋值为null(基本类型则 赋值为默认初识值, ⽐如int类型的属性, 会被赋值为0)

2.4.4 后端参数重命名(后端参数映射)

某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,⽐如前端传递了⼀个 time 给后端,⽽后端是使⽤ createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现 这种情况,我们就可以使⽤ @RequestParam 来重命名前后端的参数值.
具体⽰例如下,后端实现代码.
后端实现代码:
@RequestMapping("m5")
    public String m5(@RequestPart("na") String name){
        return name;
    }

http://127.0.0.1:8080/m5?na=spring

如果我们传不是修改的名字

http://127.0.0.1:8080/m5?name=spring

就会响应失败.

日志提示:应该写成na.

可以得出结论:
1. 使⽤ @RequestParam 进⾏参数重命名时, 请求参数只能和 @RequestParam 声明的名称⼀
致, 才能进⾏参数绑定和赋值.
2. 使⽤ @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 来避免不传递时报错,
具体实现如下:
@RequestMapping("/m5")
    public Object m5(@RequestParam(value = "na",required = false) String name){
        return name;
    }
可以看到, 添加required=false之后, time前⾯也加了key, 变成了 value = "time"
注解属性赋值时, 没有指明key的话, 默认为value属性.
如果需要有多个属性进⾏赋值时, 需要写上key.

2.4.5 传递数组

Spring MVC 可以⾃动绑定数组参数的赋值

后端实现代码:
@RequestMapping("/m5")
public String method5(String[] arrayParam) {
 return Arrays.toString(arrayParam);
}
使⽤浏览器发送请求并传参:
@RequestMapping("/m6")
public String m6(String[] str){
    return Arrays.toString(str);
}

http://127.0.0.1:8080/m6?str=aaa&str=bbb

http://127.0.0.1:8080/m6?str=aaa,bbb

http://127.0.0.1:8080/m6?str=aaa%2cbbb

如果2c没有c只有2

%2c 是逗号的转义编码, 解码后的url为: http://127.0.0.1:8080/m6?
str=aaa,bbb

2.4.6 传递集合

集合参数:和数组类似, 同⼀个请求参数名有为多个, 且需要使⽤ @RequestParam 绑定参数关系

默认情况下,请求中参数名相同的多个值,是封装到数组. 如果要封装到集合,要使⽤
@RequestParam 绑定参数关系
 @RequestMapping("m7")
    public List<String> m7(@RequestParam List<String> list){
        return list;
    }

2.4.7 传递JSON数据

JSON概念
JSON:JavaScript Object Notation 【JavaScript 对象表⽰法】
JSON是⼀种轻量级的数据交互格式. 它基于 ECMAScript (欧洲计算机协会制定的js规范)的⼀个⼦集,
采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据。--百度百科
简单来说:JSON就是⼀种数据格式, 有⾃⼰的格式和语法, 使⽤⽂本表⽰⼀个对象或数组的信息, 因此
JSON本质是字符串. 主要负责在不同的语⾔中数据传递和交换.
类似于:
国际通⽤语⾔-英语
中国56个⺠族不同地区的通⽤语⾔-普通话
有⾃⼰的语法, 其他语⾔也认识

JSON与Javascript的关系
没有关系, 只是语法相似, js开发者能更快的上⼿⽽已, 但是他的语法本⾝⽐较简单, 所以也很好学.
JSON语法
JSON 是⼀个字符串,其格式⾮常类似于 JavaScript 对象字⾯量的格式
我们先来看⼀段JSON数据
"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 r
 }, {
 "name": "Eternal Flame",
 "age": 1000000,
 "secretIdentity": "Unknown",
 "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation
 }]
}
也可以压缩表⽰:
{"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的语法:
1. 数据在 键值对 (Key/Value)
2. 数据由逗号 , 分隔
3. 对象⽤ {} 表⽰
4. 数组⽤ [] 表⽰
5. 值可以为对象, 也可以为数组, 数组中可以包含多个对象
JSON的两种结构
1. 对象: ⼤括号 {} 保存的对象是⼀个⽆序的 键值对 集合. ⼀个对象以左括号 { 开始, 右括号 }
结束。每个"键"后跟⼀个冒号 : ,键值对使⽤逗号 , 分隔
2. 数组: 中括号 [] 保存的数组是值(value)的有序集合. ⼀个数组以左中括号 [ 开始, 右中括
] 结束,值之间使⽤逗号 , 分隔。
所以, 以下都是合法的JSON数据
{ "name" : "admin" , "age" : 18 }
[ "hello" , 3.1415 , "json" ]
[{ "name" : "admin" , "age" : 18 },{ "name" : "root" , "age" : 16 },{ "name" : " 张三 " , "age" : 20 }]
可以使⽤在线JSON格式化⼯具来进⾏校验和书写: 在线JSON校验格式化⼯具(Be JSON)
JSON字符串和Java对象互转
JSON本质上是⼀个字符串, 通过⽂本来存储和描述数据
Spring MVC框架也集成了JSON的转换⼯具, 我们可以直接使⽤, 来完成JSON字符串和Java对象的互转
本质上是jackson-databind提供的功能, Spring MVC框架中已经把该⼯具包引⼊了进来, 咱们直接使 ⽤即可, 如果脱离Spring MVC使⽤, 需要引⼊相关依赖.
< dependency >
< groupId >com.fasterxml.jackson.core</ groupId >
< artifactId >jackson-databind</ artifactId >
< version >2.13.5</ version >
</ dependency >
JSON的转换⼯具包有很多, jackson-databind只是其中的⼀种
public class JSONUtils {
 private static ObjectMapper objectMapper = new ObjectMapper();
 public static void main(String[] args) throws JsonProcessingException {
 Person person = new Person();
 person.setId(5);
 person.setName("zhangsan");
 person.setPassword("123456");
 //对象转为JSON字符串
 String jsonStr = objectMapper.writeValueAsString(person);
 System.out.println("JSON字符串为:"+jsonStr);
 //JSON字符串转为对象
 Person p = objectMapper.readValue(jsonStr,Person.class);
 System.out.println("转换的对象id:"+p.getId()+",name:"+p.getName()+",passwo
 }
}

使⽤ObjectMapper 对象提供的两个⽅法, 可以完成对象和JSON字符串的互转
writeValueAsString: 把对象转为JSON字符串
readValue: 把字符串转为对象
JSON优点
1. 简单易⽤: 语法简单,易于理解和编写,可以快速地进⾏数据交换
2. 跨平台⽀持: JSON可以被多种编程语⾔解析和⽣成, 可以在不同的平台和语⾔之间进⾏数据交换和 传输
3. 轻量级: 相较于XML格式, JSON数据格式更加轻量级, 传输数据时占⽤带宽较⼩, 可以提⾼数据传输 速度
4. 易于扩展: JSON的数据结构灵活,⽀持嵌套对象和数组等复杂的数据结构,便于扩展和使⽤
5. 安全性: JSON数据格式是⼀种纯⽂本格式,不包含可执⾏代码, 不会执⾏恶意代码,因此具有较⾼ 的安全性
基于以上特点, JSON在Web应⽤程序中被⼴泛使⽤, 如前后端数据交互、API接⼝数据传输等.
传递JSON对象
接收JSON对象, 需要使⽤ @RequestBody 注解
RequestBody: 请求正⽂,意思是这个注解作⽤在请求正⽂的数据绑定,请求参数必须在写在请求正⽂中

 后端实现:

使⽤Postman来发送json请求参数:
可以看到, 后端正确接收了
通过Fiddler观察⼀下请求参数:
发现fiddle抓包的结果是json>
尝试去除掉 @RequestBody 试试
请求响应结果如下:
@RequestMapping("m8")
    public String m8( Person person){
        return person.toString();
    }

接收不到.

2.4.8 获取URL中参数@PathVariable

path variable: 路径变量
和字⾯表达的意思⼀样, 这个注解主要作⽤在请求URL路径上的数据绑定
默认传递参数写在URL上,SpringMVC就可以获取到
后端实现代码:
@RequestMapping("m9/{id}/{name}")
    public String m9(@PathVariable Integer id,@PathVariable("name")String usename){
return id+usename;
    }

如果⽅法参数名称和需要绑定的URL中的变量名称⼀致时, 可以简写, 不⽤给@PathVariable的属性赋 值, 如上述例⼦中的id变量
如果⽅法参数名称和需要绑定的URL中的变量名称不⼀致时, 需要@PathVariable的属性value赋值,
如上述例⼦中的userName变量

2.4.9 上传⽂件@RequestPart

后端代码实现:

2.4.10 获取Cookie/Session

回顾 Cookie
HTTP 协议⾃⾝是属于 "⽆状态" 协议
"⽆状态" 的含义指的是:
默认情况下 HTTP 协议的客⼾端和服务器之间的这次通信, 和下次通信之间没有直接的联系
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的.
例如登陆⽹站成功后, 第⼆次访问的时候服务器就能知道该请求是否是已经登陆过了.
上述图中的 "令牌" 通常就存储在 Cookie 字段中.
⽐如去医院挂号
1. 看病之前先挂号. 挂号时候需要提供⾝份证号, 同时得到了⼀张 "就诊卡", 这个就诊卡就相当于患
者的 "令牌".
2. 后续去各个科室进⾏检查, 诊断, 开药等操作, 都不必再出⽰⾝份证了, 只要凭就诊卡即可识别出当
前患者的⾝份.
3. 看完病了之后, 不想要就诊卡了, 就可以注销这个卡. 此时患者的⾝份和就诊卡的关联就销毁了. (类
似于⽹站的注销操作)
4. ⼜来看病, 可以办⼀张新的就诊卡, 此时就得到了⼀个新的 "令牌"
此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的⽤⼾信息, 这个就是 Session 机制所做的⼯ 作.

 理解Session

我们先来了解⼀下什么是会话.
会话: 对话的意思
在计算机领域, 会话是⼀个客⼾与服务器之间的不中断的请求响应. 对客⼾的每个请求,服务器能够识 别出请求来⾃于同⼀个客⼾. 当⼀个未知的客⼾向Web应⽤程序发送第⼀个请求时就开始了⼀个会话.
当客⼾明确结束会话或服务器在⼀个时限内没有接受到客⼾的任何请求时,会话就结束了.
⽐如我们打客服电话
每次打客服电话, 是⼀个会话. 挂断电话, 会话就结束了
下次再打客服电话, ⼜是⼀个新的会话.
如果我们⻓时间不说话, 没有新的请求, 会话也会结束.

服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是从属于哪个⽤⼾, 也就是属于 哪个会话, 就需要在服务器这边记录每个会话以及与⽤⼾的信息的对应关系.
Session是服务器为了保存⽤⼾信息⽽创建的⼀个特殊的对象.
Session的本质就是⼀个 "哈希表", 存储了⼀些键值对结构. Key 就是SessionID, Value 就是⽤⼾信息(⽤ ⼾信息可以根据需求灵活设计).
SessionId 是由服务器⽣成的⼀个 "唯⼀性字符串", 从 Session 机制的⻆度来看, 这个唯⼀性字符串称 为 "SessionId". 但是站在整个登录流程中看待, 也可以把这个唯⼀性字符串称为 "token".
上述例⼦中的令牌ID, 就可以看做是SessionId, 只不过令牌除了ID之外, 还会带⼀些其他信息, ⽐如时间, 签名等.

 

1. 当⽤⼾登陆的时候, 服务器在 Session 中新增⼀个新记录, 并把 sessionId返回给客⼾端. (通过 HTTP 响应中的 Set-Cookie 字段返回).
2. 客⼾端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的 Cookie 字段带上).
3. 服务器收到请求之后, 根据请求中的 sessionId在 Session 信息中获取到对应的⽤⼾信息, 再进⾏后 续操作.找不到则重新创建Session, 并把SessionID返回
Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.

 Cookie 和 Session 的区别

Cookie 是客⼾端保存⽤⼾信息的⼀种机制. Session 是服务器端保存⽤⼾信息的⼀种机制.
Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session 之间的桥梁.
Cookie 和 Session 经常会在⼀起配合使⽤. 但是不是必须配合.
完全可以⽤ Cookie 来保存⼀些数据在客⼾端. 这些数据不⼀定是⽤⼾⾝份信息, 也不⼀定是
SessionId
Session 中的sessionId 也不需要⾮得通过 Cookie/Set-Cookie 传递, ⽐如通过URL传递.
获取Cookie
传统获取Cookie
@RequestMapping("m11")
    public String m11(HttpServletRequest request, HttpServletResponse response){
        Cookie[] cookies = request.getCookies();
        StringBuilder builder = new StringBuilder();
        if(cookies != null){
            for(Cookie ck:cookies){
                builder.append(ck.getName()+":"+ck.getValue());
            }
        }
        return "Cookie信息:"+builder;
    }
Spring MVC是基于 Servlet API 构建的原始 Web 框架, 也是在Servlet的基础上实现的
HttpServletRequest , HttpServletResponse 是Servlet提供的两个类, 是Spring
MVC⽅法的内置对象. 需要时直接在⽅法中添加声明即可.
HttpServletRequest 对象代表客⼾端的请求, 当客⼾端通过HTTP协议访问服务器时,HTTP请 求头中的所有信息都封装在这个对象中,通过这个对象提供的⽅法,可以获得客⼾端请求的所有信 息.
HttpServletResponse 对象代表服务器的响应. HTTP响应的信息都在这个对象中, ⽐如向客⼾
端发送的数据, 响应头, 状态码等. 通过这个对象提供的⽅法, 可以获得服务器响应的所有内容
Spring MVC在这两个对象的基础上进⾏了封装, 给我们提供更加简单的使⽤⽅法.
此时 没有设置Cookie, 通过浏览器访问: http://127.0.0.1:8080/m11  ,得到Cookie为null.
我们设置⼀下Cookie的值 .
按F12进入这个界面,手动添加值.
我们再次访问:
从这个例⼦中, 也可以看出Cookie是可以伪造的, 也就是不安全的, 所以使⽤Cookie时, 后端需要进⾏ Cookie校验.
从这个例⼦中, 也可以看出Cookie是可以伪造的, 也就是不安全的, 所以使⽤Cookie时, 后端需要进⾏ Cookie校验.
简洁获取Cookie
也有更简洁的⽅式获取Cookie
 @RequestMapping("m12")
    public String m12(@CookieValue("bite") String b){
        return "bite"+b;
    }

获取Session
Session 存储和获取
Session是服务器端的机制, 我们需要先存储, 才能再获取.
Session 也是基于HttpServletRequest 来存储和获取的.
Session存储
@RequestMapping("m13")
    public String m13(HttpServletRequest request){
        HttpSession session = request.getSession();
if(session != null){
    session.setAttribute("usename","java");
}
return "session 存储成功";
    }

这个代码中看不到 SessionId 这样的概念的. getSession 操作内部提取到请求中的Cookie ⾥的
SessionId, 然后根据SessionId获取到对应的Session 对象, Session 对象⽤HttpSession来描述.
获取Session有两种⽅式
HttpSession getSession ( boolean create);
HttpSession getSession ();

HttpSession getSession(boolean create) : 参数如果为 true, 则当不存在会话时新建会话; 参数如果
为 false, 则当不存在会话时返回 null .
HttpSession getSession(): 和getSession(true) 含义⼀样, 默认值为true.
void setAttribute(String name, Object value): 使⽤指定的名称绑定⼀个对象到该 session 会话.
Session读取
读取 Session 可以使⽤ HttpServletRequest.
@RequestMapping("m14")
    public String m14(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        String usename = null;
        if (session != null && session.getAttribute("usename") != null) {
            usename = (String) session.getAttribute("usename");
        }
        return "usename+"+usename;
    }
Object getAttribute(String name): 返回在该 session 会话中具有指定名称的对象,如果没有指定名
称的对象,则返回 null.
运⾏
先设置Session: 127.0.0.1:8080/m13
通过Fiddler观察Http请求和响应情况:
可以看到, Http响应中, 通过Set-Cookie告知客⼾端, 把SessionID存储在Cookie中
通过浏览器, 可以观察到运⾏结果:
获取Session:
通过Fiddler观察Http请求和响应.
可以看到, Http请求时, 把SessionId通过Cookie传递到了服务器.
简洁获取 Session(1)
@RequestMapping("m16")
    public String m16(@SessionAttribute(value = "usename",required = false)String usename){
return "usename:"+usename;
    }

简洁获取 Session(2)
通过Spring MVC内置对象HttpSession 来获取
@RequestMapping("m17")
    public String m17(HttpSession session){
        String usename = (String)session.getAttribute("usename");
        return "usename:"+usename;
    }

HttpSession session = request.getSession();
Session 不存在的话, 会⾃动进⾏创建

2.4.11 获取Header

传统获取 header
获取Header也是从 HttpServletRequest 中获取.
@RequestMapping("m15")
    public String m15(HttpServletRequest request,HttpServletResponse response){
String userAgent = request.getHeader("User-Agent");
        return userAgent+":";
    }

通过fiddle观察
简洁获取 Header
@RequestMapping("m18")
    public String m18(@RequestHeader("user-agent")String useer){
        return useer;
    }
@RequestHeader注解的参数值为HTTP请求报头中的"Key"

2.5 响应

在我们前⾯的代码例⼦中,都已经设置了响应数据, Http响应结果可以是数据, 也可以是静态⻚⾯,也可 以针对响应设置状态码, Header信息等.

2.5.1 返回静态⻚⾯

创建前端⻚⾯ index.html(注意路径)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>我是静态页面</h1>
</body>
</html>

结果却发现,页面没有返回.而且我们这个访问方法也有问题.我们这样是直接通过浏览器访问这个,并不是通过 返回 得到的.

下面我们写后端代码,通过后端代码(服务器)返回我们之前写的.就是我们想要的页面了.

public String m19(){
        return "/index.html";
    }

但是这样也不是页面呀.

怎么回事呢??

我们把就可以了.

@RestController @Controller 有着什么样的关联和区别呢?
咱们前⾯讲了MVC模式, 后端会返回视图, 这是早期时的概念
如果想返回视图的话, 只需要把 @ResponseBody 去掉就可以了, 也就是 @Controller
@ResponseBody不可以单独使用,想返回数据就用 @RestController.想返回页面等就使用@Controller

2.5.2 返回数据@ResponseBody

我们上⾯讲到, @ResponseBody 表⽰返回数据

2.5.3 返回HTML代码⽚段

后端返回数据时, 如果数据中有HTML代码, 也会被浏览器解析
@RequestMapping("m20")
    @ResponseBody
    public String m20(){
        return "<h1>Hello,HTML~</h1>";
    }

响应中的 Content-Type 常⻅取值有以下⼏种:
text/html : body 数据格式是 HTML

2.5.5 设置状态码

Spring MVC会根据我们⽅法的返回结果⾃动设置响应状态码, 程序员也可以⼿动指定状态码
通过Spring MVC的内置对象HttpServletResponse 提供的⽅法来进⾏设置
@RequestMapping("m21")
    public String m21(HttpServletResponse response){
        response.setStatus(401);
        return "设置状态码成功";
    }

状态码不影响⻚⾯的展⽰
String[] consumes() default {};
String[] produces() default {};

1. value: 指定映射的URL
2. method: 指定请求的method类型, 如GET, POST等
3. consumes: 指定处理请求(request)的提交内容类型(Content-Type),例如application/json,
text/html;
4. produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
5. Params: 指定request中必须包含某些参数值时,才让该⽅法处理
6. headers: 指定request中必须包含某些指定的header值,才能让该⽅法处理请求
了解即可, 更多说明参考 Request Mapping :: Spring Framework
设置Content-Type
我们通过设置 produces属性的值, 设置响应的报头Content-Type
@RequestMapping(value = "m22",produces = "application/json")
    public String m22(){
        return "success";
    }

设置其他Header
设置其他Header的话, 需要使⽤Spring MVC的内置对象HttpServletResponse 提供的⽅法来进⾏设置 .
@RequestMapping("m23")
    public String m23(HttpServletResponse response){
        response.setHeader("MyHead","MyHeadvalue");
return "设置成功";
    }
Header(String name, String value) 设置⼀个带有给定的名称和值的 header. 如果 name
已经存在, 则覆盖旧的值

3

3.1.1 约定前后端交互接⼝

概念介绍
约定 "前后端交互接⼝" 是进⾏ Web 开发中的关键环节.
接⼝⼜叫 API(Application Programming Interface), 我们⼀般讲到接⼝或者 API,指的都是同⼀个东 西.
是指应⽤程序对外提供的服务的描述, ⽤于交换信息和执⾏任务(与JavaSE阶段学习的[类和接⼝]中的接 ⼝是两回事).
简单来说, 就是允许客⼾端给服务器发送哪些 HTTP 请求, 并且每种请求预期获取什么样的 HTTP 响应.
现在"前后端分离"模式开发, 前端和后端代码通常由不同的团队负责开发. 双⽅团队在开发之前, 会提前 约定好交互的⽅式. 客⼾端发起请求, 服务器提供对应的服务. 服务器提供的服务种类有很多, 客⼾端按 照双⽅约定指定选择哪⼀个服务.
接⼝, 其实也就是我们前⾯⽹络模块讲的的"应⽤层协议". 把约定的内容写在⽂档上, 就是"接⼝⽂档" ,接 ⼝⽂档也可以理解为是 应⽤程序的"操作说明书".
接⼝定义
请求路径: calc/sum
请求⽅式: GET/POST
接⼝描述:计算两个整数相加

 请求参数:

参数名      类型      是否必须              备注
num1      Integer         是                   参与计算的第⼀个数
num2      Integer         是                   参与计算的第一个数
⽰例: num1=5&num2=3
响应数据:

Content-Type: text/html
响应内容 : 计算机计算结果 : 8
服务器给浏览器返回计算的结果.

3.1.2 服务器代码

@RequestMapping("m24")
    public String m24(Integer num1,Integer num2){
        Integer sum = num1+num2;
        return "<h1>计算机计算结果: "+sum+"</h1>";
    }

3.2.1 约定前后端交互接⼝

需求分析
对于后端开发⼈员⽽⾔, 不涉及前端⻚⾯的展⽰, 只需要提供两个功能
1. 登录⻚⾯: 通过账号和密码, 校验输⼊的账号密码是否正确, 并告知前端
2. ⾸⻚: 告知前端当前登录⽤⼾. 如果当前已有⽤⼾登录, 返回登录的账号, 如果没有, 返回空
接⼝定义
1. 校验接⼝
请求路径: /user/login
请求⽅式: POST
接⼝描述:校验账号密码是否正确

请求参数: 

参数名         类型        是否必须          备注
userName   String        是                  校验的账号
password    String        是                  校验的密码
响应数据:
@RequestMapping("/login")
    public boolean login(String userName, String password, HttpSession session) {
        //账号或密码为空
        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return false;
        }
        //校验账号密码是否正确
        //理论上应该从数据库中获取账号密码, 校验是否正确, 但当前还没讲数据库的操作, 先把账
        if (!"zhangsan".equals(userName) || !"123456".equals(password)) {
            return false;
        }
        //密码验证成功, 把⽤⼾名存储在Session中
        session.setAttribute("userName", userName);
        return true;
    }
StringUtils.hasLength() 是Spring提供的⼀个⼯具⽅法, 判断字符串是否有值
字符串为null或者""时, 返回false, 其他返回true.
2. 查询登录⽤⼾接⼝
@RequestMapping("/getLoginUser")
    public String getLoginUser(HttpSession session){
        //从Session中获取⽤⼾登录信息
        String userName = (String)session.getAttribute("userName");
        //如果⽤⼾已经登录, 则直接返回⽤⼾登录
        if (StringUtils.hasLength(userName)){
            return userName;
        }
        return "";
    }
当前登录⽤⼾需要从后端获取, 并显⽰到前端
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></scr
<script>
 $.ajax({
 type: "get",
 url: "/user/getLoginUser",
 success: function (result) {
 $("#loginUser").text(result);
 }
 });
</script>

3.2.2 更快捷的引入依赖

上述引⼊lombok依赖, 需要去找lombok的坐标
接下来介绍更简单引⼊依赖的⽅式
1. 安装插件EditStarter, 重启Idea
2. 在pom.xml⽂件中, 单击右键, 选择Generate, 操作如下图所⽰
在pom这么文件页面按两下shift即可搜索这个.
进⼊Edit Starters的编辑界⾯, 添加对应依赖即可.

4.应⽤分层

通过上⾯的练习, 我们学习了Spring MVC简单功能的开发, 但是我们也发现了⼀些问题
⽬前我们程序的代码有点"杂乱", 然⽽当前只是"⼀点点功能"的开发. 如果我们把整个项⽬功能完成呢?
代码会更加的"杂乱⽆章"(⽂件乱, 代码内容乱)
那么什么是应⽤分层呢?
应⽤分层 是⼀种软件开发设计思想, 它将应⽤程序分成N个层次, 这N个层次分别负责各⾃的职责, 多个 层次之间协同提供完整的功能. 根据项⽬的复杂度, 把项⽬分成三层, 四层或者更多层.
常⻅的MVC设计模式, 就是应⽤分层的⼀种具体体现.
为什么需要应⽤分层?
在最开始的时候,为了让项⽬快速上线,我们通常是不考虑分层的. 但是随着业务越来越复杂,⼤量的 代码混在⼀起,会出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动⼀处就牵⼀发⽽动全⾝等 问题. 所以学习对项⽬进⾏分层就是我们程序员的必修课了.
如何分层(三层架构)
咱们上⼀节中学习的 "MVC", 就是把整体的系统分成了 Model(模型), View(视图)和Controller (控制器)三个层次,也就是将⽤⼾视图和业务处理隔离开,并且通过控制器连接起来,很好地实现 了表现和逻辑的解耦,是⼀种标准的软件分层架构。
Spring MVC 站在后端开发⼈员的⻆度上, 也进⾏了⽀持, 把上⾯的代码划分为三
个部分:
请求处理、响应数据:负责,接收⻚⾯的请求,给⻚⾯响应数据.
逻辑处理:负责业务逻辑处理的代码.
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作.
这三个部分, 在Spring的实现中, 均有体现:
⼆者其实是从不同⻆度对软件⼯程进⾏了抽象.
MVC模式强调数据和视图分离, 将数据展⽰和数据处理分开, 通过控制器对两者进⾏组合.
三层架构强调不同维度数据处理的⾼内聚和低耦合, 将交互界⾯, 业务处理和数据库操作的逻辑分开.
⻆度不同也就谈不上互相替代了,在⽇常的开发中可以经常看到两种共存的情况,⽐如我们设计模型
层的时候往往也会拆分出业务逻辑层(Service层)和数据访问层(Dao层)。
但是⼆者的⽬的是相同的, 都是"解耦,分层,代码复⽤"
软件设计原则:⾼内聚低耦合.
⾼内聚指的是:⼀个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联 系程度越⾼,则内聚性越⾼,即 "⾼内聚"。
低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。修改⼀处代码, 其他模块的代码 改动越少越好.
⾼内聚低耦合⽭盾吗?
不⽭盾, ⾼内聚指的是⼀个模块中各个元素之间的联系的紧密程度, 低耦合指的是各个模块之间的紧 密程度.
这就好⽐⼀个企业, 包含很多部⻔, 各个部⻔之间的关联关系要尽可能的⼩, ⼀个部⻔发⽣问题, 要尽 可能对降低对其他部⻔的影响, 就是耦合. 但是部⻔内部员⼯关系要尽量紧密, 遇到问题⼀起解决.

 ⼩驼峰: 除了第⼀个单词,其他单词⾸字⺟⼤写,⽐如: userController

蛇形: ⽤下划线(_)作⽤单词间的分隔符, ⼀般⼩写, ⼜叫下划线命名法, ⽐如: user_controller
串形: ⽤短横线(-)作⽤单词间的分隔符, ⼜叫脊柱命名法, ⽐如: user-controller

5.总结

1. 学习Spring MVC, 其实就是学习各种Web开发需要⽤的到注解

a. @RequestMapping: 路由映射
b. @RequestParam: 后端参数重命名
c. @RequestBody: 接收JSON类型的参数
d. @PathVariable: 接收路径参数
e. @RequestPart: 上传⽂件
f. @ResponseBody: 返回数据
g. @CookieValue: 从Cookie中获取值
h. @SessionAttribute: 从Session中获取值
i. @RequestHeader: 从Header中获取值
j. @Controller: 定义⼀个控制器, Spring 框架启动时加载, 把这个对象交给Spring管理. 默认返回
视图.
k. @RestController: @ResponseBody + @Controller 返回数据

2.

Cookie 和Session都是会话机制, Cookie是客⼾端机制, Session是服务端机制. ⼆者通过SessionId

来关联. Spring MVC内置HttpServletRequest, HttpServletResponse两个对象. 需要使⽤时, 直接在
⽅法中添加对应参数即可, Cookie和Session可以从HttpServletRequest中来获取, 也可以直接使⽤
HttpServletResponse设置Http响应状态码.

3.

Java EE学习阶段会涉及较多⼯具, 插件的学习, 来帮助我们提⾼开发效率. ⽐如Postman, lombok,
EditStarter, 后⾯还会继续学习其他的⼯具或插件.
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值