官方文档的描述:
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它 的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为“Spring MVC” 。
从官方描述总结:
1、Spring MVC 是一个 Web 框架
2、Spring MVC 是基于 Servlet API 构建的
对于 Spring 里面的 web 中 必须掌握的框架之一。
MVC 时候 Model View Controller 的缩写,是软件工程中的一种软件架构模式,分为了 Model(模型),View(视图),Controller(控制器)三个基本部分。
以下图是流程图:
他两之间的关系就类似于 IoC与DI 的关系;MVC 是一种思想,而 Spring MVC 是对 MVC 思想的具体实现,是一个框架产品。
IoC 描述的是目标,DI描述的是具体可实现的思路。
总的来说:Spring MVC 是一个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。
尽然是 web 框架,那么当用户输入 url 后,那么 Spring MVC 项目是可以感知到用户的请求的。
创建 Spring MVC 是基于 Spring Boot 的所以前面创建的步骤与 Spring Boot 是一样的,直到下图,现在依赖的环节有所不同
这里需要 选择 Web 里面的 Spring Web 然后在里面的描述就可以看到有 Spring MVC 了。
点击 next之后,就把需要等待一下,等idea加载完成再把目录里面多余的文件给删除了;以及一些目录文件说明,项目的可以看之前 Spring Boot 创建的那篇文章。
学习 Spring MVC 只需要掌握 3 个功能:
1、连接的功能: 将用户(浏览器)与java程序连接起来,简单来说就是访问一个 url 地址可以调用我们 Spring 程序。
2、获取参数的功能: 用户访问的时候会带一些参数,在程序中要想办法获取到参数。
3、输出数据的功能: 执行了业务逻辑过后,需要把执行的结果返回给用户。
这三个功能是 Spring MVC 的核心,也就是说掌握了这三个功能也就掌握了 Spirng MVC,接下来都是关于这三个功能的详细分析及使用。
现在这里设置也下 Spring 的热部署,热部署就是 idea 会自动的更新部署最新的代码,不会让你每一次代码就重新启动一下 Spring MVC 程序,不然这样太麻烦了,使用热部署自动更新部署代码,不需要再次重启程序也能进行url访问。
但是在新加载类的时候可能是不成功的,等待了5秒左右,不成功的话可以重新启动一下。对于文件的变化可能热部署效果不是很好,但是对于修改代码是没有问题的
1、首先要添加依赖,dev-tool 框架支持
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
2、开启 idea 中当前项目的自动编译
3、开启运行时热部署
我的 idea 版本是比较老的,如果你当前的 idea 版本比较高,或者说是没有找到 …app.runinng 这个选项,可以执行一下操作:
最后使用 debug 来启动项目,而非 run 方法启动(不然无效)
以下的代码,都是在 Spring Boot 里面用到过,相对来说好理解。
@Controller
@ResponseBody // 返回一个非静态页面的数据
@RequestMapping("/user") // 路由地址
public class UserController {
@RequestMapping("/hi") //localhost:8080/user/hi
public String hi() {
return "Hello ,Spring MVC!!!";
}
}
启动Spring MVC 程序,输入 url 地址就可以访问浏览器页面了。
这段代码主要注意的地方就是 @RequestMapping
注解 ,他既可以修饰类也可以修饰方法,默认是既支持 GET 请求,也支持post 请求。
但是 @RequestMapping
注解 也是可以设置 请求类型的 ;
@RequestMapping(value = "/user" , method = RequestMethod.POST) // 路由地址
参数 value 默认的,只有路由的时候可以不用填,后面 method 参数 就是用来设置请求类型的,可以在 idea 里面打开查看源码支持那些请求参数。
这时候设置的是 Post 请求,当使用 Get 请求的时候,就会报错提示 401 Method Not Allowed
这两个注解其实跟在 @RequestMapping 这个注解里面的参数设置 method 效果是一样的,看自己习惯用那种方法。
@Controller
@ResponseBody // 返回一个非静态页面的数据
@RequestMapping("/user") // 路由地址
public class UserController {
@PostMapping("/hi2") // http://localhost:8080/user/hi2
public String hi2() {
return "Hello ,Spring MVC2!!!";
}
}
这段代码也就是说,仅支持 Post 请求,强行使用 Get 请求就会提示 405.@GetMapping用法也是一样的道理不再赘述。
代码里面的形参要与前端传递的形参保持一致
@Controller
@ResponseBody // 返回一个非静态页面的数据
@RequestMapping(value = "/user" ) // 路由地址 method = RequestMethod.POST
public class UserController {
@RequestMapping("/hi4") //localhost:8080/user/hi4
public String hi4(String name) {
return "Hello "+name;
}
}
url:
http://localhost:8080/user/hi4?name=python
代码要与前端传递的参数 name 保持一致,最后的打印结果就是 python ;如果什么也没有输入那么返回的就是一个null。
首先得有一个对象:
import lombok.Data;
@Data // 复合注解,基于lombok依赖,里面包含了 @Getter + @Setter + @ToString
public class User {
private int id;
private String name;
private String password;
private int age;
}
得到对象:
@Controller
@ResponseBody // 返回一个非静态页面的数据
@RequestMapping(value = "/user" ) // 路由地址 method = RequestMethod.POST
public class UserController {
@RequestMapping("/add") // localhost:8080/user/add
public String add(User user) { // 这里就是拿到了这个对象
return user.toString();
}
}
url:
http://localhost:8080/user/add?id=1&name=java&password=123&age=18
结果:
User(id=1, name=java, password=123, age=18)
如果 url 里面的参数名称与后端代码参数名称对应不上,那么对象里面不对应的属性就是 null
这个主要可以用 Postman 来传递,直接输入就可以了
毕竟前后端是分离的,那么代码也不是同一个人写的,有时候是交接工作的时候新来的人把老人的 参数名称改了,比如说 password 新人改成了 pwd,这样后端就与前端不一致了,就接收不到前端参数了,为了解决这个问题想到的有两个方法。
1、把后端的 password 改成 pwd ,这种方法是不可以的,当项目工程大的时候,名称一改动就会牵连别的后端代码地方,越改越出错,所以这种方法不可取 ❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌
2、使用 @RequestParam
注解来映射前后端参数值
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/login") //localhost:8080/user/login
public String login(String name ,@RequestParam("pwd") String password) {
return "name= " + name+ "| password=" +password;
}
}
将前端的 pwd 参数映射到后端的 password 变量中
url:
http://localhost:8080/user/login?name=java&pwd=987
结果:name= java| password=987
加上 @RequestParam
注解 就算前端 传过来的是 pwd ,后端的参数也能很好的映射前端。
由上面可知,第一个作用可以重命名实现前后端映射;第二个作用就是 参数不能为 null;例子:进行登入操作的时候是必须输入 账户和密码 的,为了防止为 null ,可以在那个参数前面加上 @RequestParam
注解;
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/login2") //localhost:8080/user/login2
public String login2(@RequestParam String name ,@RequestParam String password) {
return "name= " + name+ "| password=" +password;
}
}
url:
localhost:8080/user/login2?name=java
报错:
{
“timestamp”: “2023-08-09T07:28:59.552+00:00”,
“status”: 400,
“error”: “Bad Request”,
…
}
提示的是请求失败,所以加上 @RequestParam 注解之后,他们的参数都不能为 null
@RequestParam
第三个作用就是在设置前后端参数的映射后,是非必传的参数。在注解 @RequestParam
的源码中 有一段代码是 boolean required() default true; 这段代码的意思就是必传的意思,你把他参数改成false就非必传了。
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/login3") //localhost:8080/user/login2
public String login3(@RequestParam String name ,@RequestParam(value = "pwd",required = false) String password) {
return "name= " + name+ "| password=" +password;
}
}
url:
localhost:8080/user/login3?name=java
结果:
name= java| password=null
程序员通过设置 required = false 来设置成 非必传参数。
@RequestParam
作用总结:
1、前后端参数映射;
2、设置必传参数;
3、前后端参数映射+非必传参数 required = false
获取前端的 Json 对象需要一个注解 @RequestBody
这个注解就让后端知道前端传递的是一个 Json 格式的数据。
如果不加注解的话,后端接收不到会响应一个 null
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/loginByJson") // localhost:8080/user/loginByJson
public String loginByJson(@RequestBody User user){ // Json 是对象要用对象来接受形参
return "name= " + user.getName()+ "| password=" +user.getPassword();
}
}
Json:
{"name":"java","password":"123"}
结果:
name= java| password=123
获取前端 Json 格式的数据必须加注解 @RequestBody
,她表示提示后端从前端获取一个Json格式的数据。
一般情况下的 url 地址:http://localhost:8080/login?name…
但是有些 url 的地址:http://localhost:8080/{name}/{password}…
有些公司为了在搜索引擎上面搜索排名更高就采用关键字的方式,一般的url地址后面更的都是参数,但是有些 url :http://localhost:8080/java/如何学好java?/123/… 这样的url就会出现关键字,在搜索引擎上面排位高一点。
那么从 url 的 path 中获取参数需要用到 @PathVariable
注解,代码实现:
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/loginByPath/{name}/{passwrod}")
public String loginByPath( @PathVariable String name,
@PathVariable("passwrod") String pwd) {
return "name= " + name+ "| password=" +pwd;
}
}
url:
http://localhost:8080/user/loginByPath/java/234
结果:
name= java| password=234
总结 @PathVariable
有两个作用:
1、获取url里面的参数
2、与注解 @RequestParam
有前后端映射的功能,与前端url里面的{password}对应
当前端传递一张图片或者一份文件的时候,Spring 里面有的一个注解 @RequestPart
就是接收文件,再把文件放入指定的目录中。
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/upFile") // localhost:8080/user/upFile
public String upFile(Integer id, @RequestPart("photo")MultipartFile file) throws IOException {
//业务处理....
file.transferTo(new File("E:\\a.png"));
return "id:"+id+" 图片上传成功!";
}
}
注解 @RequestPart
里面的 ”photo” 是与前端参数对应起来的,这里先用 postman 来模拟一下前端
但是这段代码是有问题的,当需要批量传输图片的时候,传递到E盘会直接覆盖a.png,不会生成新的文件,所以代码是需要改进的,让每一个文件生成唯一的id,这样就不会被覆盖吞噬掉了。
@Controller
@ResponseBody
@RequestMapping(value = "/user" )
public class UserController {
@RequestMapping("/upFile") // localhost:8080/user/upFile
public String upFile(Integer id, @RequestPart("photo")MultipartFile file) throws IOException {
//业务处理....
// 1、生成唯一的id
String fileName = UUID.randomUUID().toString(); // UUID是唯一id:时间轴+网卡号+随机数....
// 2、获取上传文件名的后缀
String fileType = file.getOriginalFilename().substring(
file.getOriginalFilename().lastIndexOf(".")
); // 获取后缀名
fileName += fileType; // 组成完整的文件名
file.transferTo(new File("E:\\"+fileName));
return "id:"+id+" 图片上传成功!";
}
在 java 程序中使用 UUID 的方式生成唯一id,然后再截取文件的后缀名,将唯一id和后缀名叠加起来就组成了完整新图片传入指定的目录中;但是指定的目录最好配置到配置文件汇总,方便最后维修的时候不懂代码,改配置文件就行。
为什么会有 Cookie和Session尼?因为在 http协议或者说是 https 的协议他们的特性是无连接(无长连接,只是响应一次),无状态(后一次发送的请求以前一次无关系)的,就比如说是登录一个网站需要输入账号密码,那么由于http协议的特性当你每次访问一次这个网站里面的信息都会输入账号和密码,这是非常麻烦用户体验感极差的东西,为了解决这种问题,就出现了Cookie和session;
Cookie:用户刚开始使用账号和密码登录某个网站的时候,浏览器会把登录成功的信息存入 Cookie 里面以便访问这个网站的其他页面的时候已经验证你的信息无需再次进行登录操作;但是 Cookie 有个缺点,就是Cookie 的信息是存在客服端的,存在客户端就会考虑到安全的问题,用户可以模拟Cookie,在Cookie 里面写入一些自己的东西,为了解决安全的问题也就出现了 Session。
Session:为了解决 Cookie 带来的问题,那么 Session 是把用户的信息放在了服务器里面,Session 也是键值对存在了服务器里面,并且 Session 的 id值是动态变化的,利用 Cookie 机制把 Session 的 id 存入到Cookie里面,浏览器拿到 Session 的 id 就会到服务器里面找对应的 value ,从而解决了问题,但是Session也不是绝对安全,因为也会有人劫持 Session 的 id ,利用 id 到服务器里面找 value ,但 Session 的一个特性是 id 是动态变化的,当你拿到这个id 的时候,就可以能id 就已经变了,相对来说安全性是比较高,但也不是绝对的安全。
这里用的是 Spring MVC 注解的方式,使用 @CookieValue
,但 也可用 Sevelet 的方式。
@Controller
@ResponseBody
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/getCookie") // localhost:8080/user/getCookie
public String getCookie(@CookieValue String name) {
return "Cookie-name=" + name;
}
}
@CookieValue(“xxx”) ,这个括号里面是Cookie 的 name 参数,这里刚刚与 name 的参数保持一致所以不用加括号
因为本身就是模拟,所以自己在浏览器里面设置模拟
User-Agent就是请求中常见的header。
@Controller
@ResponseBody
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/getAgent") // localhost:8080/user/getAgent
public String getAgent(@RequestHeader("User-Agent") String userAgent) {
return userAgent;
}
}
获取 http 协议里面的 User-Agent使用的注解是 @Requestheader
。
因为是模拟首先自己先设置存储一个 Session。
@Controller
@ResponseBody
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/setSession") // localhost:8080/user/setSession
public String setSession(String name, HttpServletRequest req) {
HttpSession session = req.getSession(true); // 有Session就获取,没有就创建
if (session != null) {
session.setAttribute("name", name);
}
return "Session 设置成功!";
}
}
url:
localhost:8080/user/setSession?name=java
获取Session:
@Controller
@ResponseBody
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/getSession")
public String getSession(@SessionAttribute(name = "name", required = false) String name) {
return name;
}
}
url:
// localhost:8080/user/getSession
返回数据默认情况下返回的是一个静态页面,静态页面的输出,在目录下面写好静态页面的代码,然后再后端惊醒 url 访问就可以了。
@Controller
public class TestController {
@RequestMapping("/index") // localhost:8080/index
public String getIndex() {
return "index.html";
}
}
这上面的代码是没有加 @ResponseBody 的,所以输出的一直是静态页面,现在完成一个计算器功能使他返回的是一个数据。
@Controller
public class TestController {
@RequestMapping("/calc") //localhost:8080/calc
@ResponseBody
public String calc(Integer num1, Integer num2) {
return "计算结果:" + (num1 + num2);
}
}
url:
http://localhost:8080/calc?num1=5&num2=10
结果:
计算结果:15
这就是 使用 @ResponseBody 返回的页面是数据,前面也已经提到过很多次 @ResponseBody 不再额外追溯。
返回 Json 对象需要是要 HashMap 才能返回 Json 对象格式:
@Controller
public class TestController {
@RequestMapping("/json") // localhost:8080/json
@ResponseBody
public HashMap<String,String> json() {
HashMap<String ,String > hashMap = new HashMap<>();
hashMap.put("name","java");
hashMap.put("password","123");
return hashMap;
}
}
如果不使用HashMap的方式,使用的是字符串打印的方式,对于用户在浏览器页面看来说没有什么区别,但是对于前端传递的就是 html 格式的类型,当前端使用数据的时候会识别不到,就会可能出现更多的错误,所以使用 Json 数据的时候后端需要利用HashMap。
forward VS redirect
@Controller
//@ResponseBody
@RequestMapping("/test")
public class TestController {
@RequestMapping("/myForward") // localhost:8080/test/myForward
public String myForward() {
return "forward:/test.html";
}
@RequestMapping("/myRedirect") // localhost:8080/test/myRedirect
public String myRedirect() {
return "redirect:/test.html";
}
}
一遍情况下,使用重定向会比较方便,转发就麻烦容易出错,但是要记住这两个的区别,面试常考。