Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来自其源模块的名称(Spring-webmvc),但它通常被称为“Spring MVC”。
它是软件工程中的一种软件架构模式,现在市面上绝⼤部分的 Java 项目约等于 Spring MVC 项目。
创建 Spring MVC 项目只需要在创建 Spring Boot 项目的时候选择添加 Spring Web框架即可
学习 Spring MVC 主要掌握3个功能
- 连接功能:将用户(浏览器)和程序连接起来
- 获取参数的功能:获取用户访问所带的参数
- 输出数据的功能:执行业务逻辑之后将结果返回给用户
在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射,也就是起到浏览器连接程序的作用。
例如:现在创建一个控制器类
@RestController // 启动框架时,自动加载
@RequestMapping("/user") // 路由器规则注册,也就是访问的地址
public class UserController {
@RequestMapping(value = "/hello") // 路由器规则注册,也就是访问的地址
public String print(){
return "hello world";
}
}
当启动项目后,通过访问地址:http://127.0.0.1:8080/user/hello,就可以得到函数中所返回的值
用来注册接口的路由映射。
所谓的路由映射指的是,当用户访问一个 url 时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射
@RequestMapping 即可修饰类,也可以修饰方法,如果同时修饰了类和方法,那么访问的地址就是 类+方法
如上述代码,如果使用 Get 方式请求可以访问成功,那么使用 Post 方式请求同样也是可以访问成功的
@RequestMapping 注解默认是两种方式都可以访问
那么有些场景下,指定只能使用 Get或Post 一种方式访问,也是可以做到的。@RequestMapping 注解可是设置参数,比如映射地址为一个参数,还可以设置第二个参数,例如:
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping(value = "/hello", method = RequestMethod.GET) // 路由器规则注册,也就是访问的地址
public String print(){
return "hello world";
}
}
设置了 method 参数就是指定该访问只能通过某种方式访问,参数的值是一个 enum 枚举类型。
**注意:当 @RestController 注解只有一个参数时,前面的参数类型名可以缺省。多个参数时必须执行参数类型,例如上述的映射地址前需要加上 value = **
可以看到指定了 Get 方式后,使用 Post 请求就会失败
除了上述使用 @RequestMapping 注解外,还可以使用 @GetMapping 和 @PostMapping 这两个注解去指定
@RestController // 启动框架时,自动加载
public class UserController {
@PostMapping("/hello") // 路由器规则注册,也就是访问的地址
public String print(){
return "hello world";
}
@GetMapping ("/hello2") // 路由器规则注册,也就是访问的地址
public String print2(){
return "hello world";
}
}
在 Spring MVC 中可以直接使用方法中的参数来实现传参
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("hello")
public String print(String name){
return "hello " + name;
}
}
这时只需要在访问的 url 后面直接传参即可收到
当然使用浏览器直接输入 url 中带参数也是可以的
接收多个参数也是同理,并且在 url 中的顺序是不要求的
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("hello")
public String print(String name, String password){
return "hello " + name + ": " + password;
}
}
对于传递参数有需要注意的事项:
- 在后端方法中传递的参数类型推荐使用包装类的类型而不是使用基本数据类型,假如使用了基本数据类型例如int,那么如果 url 中没有指定参数值则会报错。但是使用包装类则不会报错,因为其默认值为 null
- 如果像上述代码为例:则在前端 url 访问中,url中参数的名称必须与后端方法中的名称一样才能被接收到,例如上述的方法中参数名称为name,则url中的参数名称也要为name才能被接收到。下文会提到如何更改
在Spring MVC 中有两个接口,HttpServletRequest 和 HttpServletResponse,这两个接口的对象就是分别对应着处理请求和响应。
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("/user")
public String print(HttpServletRequest request, HttpServletResponse response){
return "name: " + request.getParameter("name");
}
}
当使用 HttpServletRequest 接口对象时,可以通过其 getParameter方法去获取指定的参数值的
而使用 HttpServletResponse 接口对象时,则可指定响应的形式,例如调用其方法可以设置响应属性
并且 Spring MVC 可以自动实现参数对象的赋值,例如现在有一个用户类
@Getter
@Setter
public class User {
private int id;
private String name;
private String password;
private int age;
}
则在方法中就可以直接通过这个实体类去接收参数数据
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("user")
public Object print(User user){
return user;
}
}
可以看到确实是接收到了参数
上面提到了关于参数名的匹配,那么在 Spring MVC 中也可以去修改指定 url中所传参数的名字,通过**@RequestParam** 注解去指定
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("user")
public String print(@RequestParam("username")String name, String password){
return name + ": " + password;
}
}
像上述设置了之后,在 url 中就需要传递username才能被后端的 name 所接收
如果像上述代码中使用了 @RequestParam 注解去重命名,那么默认这个参数就是必须要传的,如果不传就会报错
这个参数是可以设置为非必传的,也就是@RequestParam 注解中可以设置其参数,例如:
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("user")
public String print(@RequestParam(value = "username", required = false)String name, String password){
return name + ": " + password;
}
}
加上 required = false 后这个参数就变成了非必传参数
不传 username 参数后就会变成默认值 null
默认的接收方式是不可以接收到 JSON对象的
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("user")
public Object print(User user){
return user;
}
}
可以看到传递值和打印出来的根本不一致。
那么要想让参数可以接收到 JSON 对象就需要在前面加上 @RequestBody 注解
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("user")
public Object print(@RequestBody User user){
return user;
}
}
这样子就可以接收到了
上面所讲的接收都是需要在 url 中指定参数的,但是有些场景下是直接将参数写为地址的。例如我的博客系统就可以直接通过 XXX/10101 这样去获取一篇文章,而后面的10101就可以是我这篇文章的编号。
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("user/{id}")
public String print(@PathVariable String id){
return "id: " + id;
}
}
注意 路由映射地址的格式,加上{}的就代表可以获取的参数,然后接收的对象名需要一致。或者@PathVariable 注解也支持重命名和非必传的
分为几个步骤
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("upload")
public Object Load(@RequestPart("file")MultipartFile file){
File save = new File("D:\\my.png");
try{
file.transferTo(save);
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
这种方式默认是有文件大小限制的,需要在配置文件中设置:
spring:
servlet:
multipart:
max-request-size: 100MB
max-file-size: 100MB
上述的代码还是会有问题,如果保存的位置里有和设置的文件名同名的话就会覆盖到原有的文件。因此可以使用 UUID 的方式去取文件名,而在使用UUID 前得想获取到文件的后缀名,这样才能确保文件格式。
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("upload")
public Object Load(@RequestPart("file")MultipartFile file){
// 获取文件名
String filename = file.getOriginalFilename();
// 截取文件后缀名
int pos = filename.lastIndexOf(".");
filename = filename.substring(pos);
// 使用UUID + 后缀名作为文件名并加上路径
String path = "D://" + UUID.randomUUID() + filename;
// 保存文件
try {
file.transferTo(new File(path));
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
直接使用 HttpServletResponse 接口对象
@RequestMapping("/user")
public String print(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取所有cookie
Cookie[] cookies = request.getCookies();
// 获取指定header
String userAgent = request.getHeader("User-Agent");
}
上面的代码是获取到了全部的cookie
使用@CookieValue 注解可以获取指定的cookie
@RequestMapping("/cookie")
public String cookie(@CookieValue("java") String java) {
return "cookie:" + java;
}
这个注解默认是必传的,所以可以设置为非必传
@RequestMapping("/cookie")
public String cookie(@CookieValue("java", required = false) String java) {
return "cookie:" + java;
}
@RequestMapping("/cookie")
public String header(@RequestHeader("User-Agent") String userAgent) {
return "userAgent:"+userAgent;
}
同样可以设置非必传
Session 获取和Servlet 类似,可以使用 HttpServletRequest 接口的 getSession 方法去获取,如果该方法的参数设置为 true 则代表没有Session时自动创建一个session。设置为false则不会自动创建。默认是true。然后通过 setAttribute 方法去设置session。这也就是存储的方法
如果确定了这个session是存在的则可以使用 @SessionAttribute 注解去获取指定的session,这个注解同样可以设置非必传。
因此可以演示以下,访问一个地址去创建session,再去访问另一个地址获取session
@RestController // 启动框架时,自动加载
public class UserController {
@RequestMapping("/set")
public void setSess(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("username", "zhangsan");
}
@RequestMapping("/get")
public String getSess(@SessionAttribute("username")String name) {
return "username: " + name;
}
}
首先访问/set去创建,再访问/get获取
了解返回数据前需要先知道这三个注解的区别:
@ResponseBody 返回的值如果是字符会转换成 text/html,如果返回的是对象会转换成 application/json 返回给前端
@ResponseBody 可以用来修饰方法或者是修饰类,修饰类表示类中的所有方法都会返回 html 或者 json,而不是视图
@RestController 是前面两个注解的结合,也就是一个注释包含了两个注解的功能
所以如果想返回一个静态资源则不能使用@ResponseBody 或者 @RestController注解
也就是可以返回项目中的 html文件。首先新建一个简单的html文件,注意这种静态文件需要放到/src/main/resources/static 目录下
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<h1>你好h1>
body>
html>
然后在控制类返回这个静态资源
@Controller
public class TestController {
@RequestMapping("index")
public Object index(){
return "/index.html";
}
}
返回/index就会显示刚刚index.html这个静态页面
注意:在返回值那里需要加上 / 。如果不加上 / 就会报错,因为加上 / 才说明是在项目的根目录下去找这个静态资源,不加的话就是在访问的/index下面去找
- 加了:/static/index.html
- 不加:127.0.0.1:8080/index/index.html
如果不返回文件的话也是可以显示页面的,返回 text/html 形式。也就是直接将html文件以字符串的形式返回,那么浏览器会自动识别并显示。这时候就得使用@RestController或者@ResponseBody注解了
@RestController
public class TestController {
@RequestMapping("index")
public String index(){
return "你好
";
}
}
可以看到效果都是一样的
通过上面的方式,就可以实现一个前后端联合的简单计算器了。
前端使用form表单提交参数,后端直接返回结果
前端代码:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<form action="http://localhost:8080/sum">
<h1>加法计算器h1>
数字1:<input type="text" name="num1"><br>
数字2:<input type="text" name="num2"><br>
<input type="submit" value="提交">
form>
body>
html>
注意这个form跳转的地址需要与后端执行方法的地址相同才能调用到方法
@Controller
public class TestController {
@RequestMapping("index")
public Object index(){
return "/index.html";
}
@RequestMapping("sum")
@ResponseBody
public String Sum(Integer num1, Integer num2){
return String.format("结果为: %d
", num1 + num2);
}
}
通过访问/index去访问到前端,然后前端提交后将参数传递给sum方法,sum方法再将结果以 text/html 的形式返回
JSON 对象在后端可以使用哈希表去存储,例如:
@Controller
public class TestController {
@RequestMapping("index")
@ResponseBody
public HashMap<String, String> index(){
HashMap<String, String> res = new HashMap<>();
res.put("zhangsan", "123");
res.put("lisi", "123");
res.put("wangwu", "123");
return res;
}
}
这样去访问的话就可以得到这个哈希表转换为 JSON后的数据