Spring Web MVC (Model View Controller)是基于 Servlet API 构建的原始 Web 框架,从一开始就包含在 Spring Framework 中。正式名称“Spring Web MVC”来自其源模块的名称(spring-webmvc),但它通常被称为“Spring MVC”。
MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分,通常模型对象负责在数据库中存取数据。
View(视图)是应用程序中处理数据显示的部分,通常视图是依据模型数据创建的,是前端(客户端)的可视的页面。
Controller(控制器)是应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
🎯MVC 执行流程:
🎯MVC与Spring MVC之间的区别?
MVC 是一种思想,而 Spring MVC 是对 MVC 思想的具体实现。
Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架,当⽤户在浏览器中输⼊了 URL 之后,我们的 Spring MVC 项目就可以感知到用户的请求。
现在绝大部分的 Java 项目都是基于 Spring(或 Spring Boot)的,而 Spring 的核心就是 Spring MVC;也就是说 Spring MVC 是 Spring 框架的核心模块,而 Spring Boot 是 Spring 的脚手架,因此我们可以推断出,现在市面上绝大部分的 Java 项目基本上都是 Spring MVC 项目,这也是我们要学 Spring MVC 的原因。
学习SpringMVC项目,主要学习的是三个功能:
Spring 程序。
首先,我们需要新建 SpringBoot 项目。
要注意,在添加依赖的时候,一定要添加 Web 模块,它其实就是 SpringMVC 的依赖。
设置路由可以实现程序与用户之间的映射(路由映射),也就是当用户访问某一个 URL 时,会将用户的请求对应到程序中的某个类的某个方法中,实现浏览器连接程序的作用,设置路由可以使用@RequestMapping注解实现,注意要实现路由访问,必须使用 @Controller 将对象储存到 Spring 中。
package com.example.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller // 让 spring 框架启动时, 加载 @ResponseBody // 返回⾮⻚⾯数据 @RequestMapping("/user") // 一级路由 @Slf4j // 日志框架 public class UserController { @RequestMapping("/hi") // 二级路由 public String sayHi() { return "Hi, Spring MVC.
"; } }
其中,访问方法所返回数据或页面的 URL 为http://IP:端口号/类上的路由地址/方法上加的路由地址,这样就能访问到每个方法所返回的页面或者数据了,也可以只在方法上设置路由地址;且当类中只有一个方法时,类上/方法上也可以不设置路由地址(挑一个设置即可),此时直接使用一级路由就能直接访问了。
由于历史原因,方法返回默认是一个静态页面,如果要设置返回一个页面数据,需要搭配使用 @ResponseBody注解,放在类上,类里面所有的方法都会默认放回的是页面数据,而不是一个静态页面,放到方法上生效的只是当前方法。
启动程序,我们去浏览器通过访问http://127.0.0.1:8080/user/hi地址,观察结果。
@RequestMapping默认情况下,同时支持 GET 和 POST 等其他请求方式;而如果我们想要仅支持一种请求方式,需要做一些配置。
我们需要设置 @RequestMapping 的method属性为RequestMethod.xxx,RequestMethod 其实是一个枚举,里面储存了多种的请求方式。
我们这里设置一个 POST 请求。
@Controller @ResponseBody @Slf4j @RequestMapping("/user") public class UserController { @RequestMapping(method = RequestMethod.POST, value = "/onlypost") public String func1() { return "post"; } }
我们使用 postman 构造 GET 与 POST 请求看看能不能进行处理。
首先我们来试一试GET请求,
通过抓包,可以看到返回给用户的结果是405,出错了,这就表示该方法是不支持 GET 请求的,我们再来试一试POST请求。
构造 POST 请求:
返回结果:
返回了一个post数据,与我们预期相同,所以像上面那样进行配置,可以使一个方法只支持 GET 或 POST 一种请求格式。
当然还有另外的方法,如果只支持 POST 请求,我们还可以使用@PostMapping注解来实现。
@PostMapping("/hello") public String func2() { return "hello"; }
同理,如果需要只支持 GET 请求我们也可以使用@GetMapping注解来进行实现。
在 MVC 项目中,因为 SpringMVC 是基于 Servlet 的,所以使用 Servlet 那一套操作也是可以的,只不过有点繁琐,Spring 中就有了更方便获取参数的方式。
假设有一个类User,客户端会传一个用户id到后端,我们需要返回一个User对象。
package com.example.demo.model; import lombok.Data; @Data public class User { private int id; private String name; private String password; }
根据 id 查询 User 对象,这里主要是示范 Spring Web API 的使用,就不添加数据库查询了,这里直接构造相应 id 的对象,返回即可,重点是模拟数据的传入与传出。
我们直接在方法中加上一个形参(要与前端传入的参数名相同),访问路径时就会后会自动根据形参的名字以前端传来参数的 key 进行匹配,如果能够对应,后端就能成功接收参数。
@RequestMapping("/getuserbyid") public User getUserById(Integer id) { User user = new User(); user.setId(id); user.setName("张三"); user.setPassword("zs12345678"); return user; }
我们再通过 postman 来构造一个 id 参数,并获取到一个 User 对象。
Spring 会根据响应的返回值,决定返回数据的类型,比如你返回一个对象或 Map,就会返回一个 json 格式的数据,你返回一个页面,那它返回的就是页面类型的数据。
因为 SpringMVC 是基于 Servlet 的,所以 Servlet 中的那一套 API 在这里也适用,在MVC中,方法中的HttpServletRequest 与 HttpServletResponse 参数默认是隐藏的,如果想要使用,需要显示地进行声明,获取请求参数时需要通过 HttpServeletRequest 对象手动获取,我们只声明一个即可,相较来看写法上就比较繁琐了。
@RequestMapping("/getUserById2") public User getUserById2(HttpServletRequest request) { User user = new User(); user.setId(Integer.parseInt(request.getParameter("id"))); user.setName("李四"); user.setPassword("ls12345678"); return user; }
还需要知道默认情况下,前端多传或者少传参数都是可以的,少传参数的情况下,结果就是 null,而多传参数并不会被 SpringMVC 读取。
我们还可以获取多个参数,此时可以给方法设置多个参数,也可以设置一个对象参数直接获取多个前端传来的参数。
比如,一个登录的逻辑,需要用户传来账户名与密码,这里就不实现一个登录逻辑了,我们直接将获取到的账户名与密码作为响应返回出去。
@RequestMapping("/login") public String login(String name, String password) { return "用户名:" + name + "密码:" + password; }
我们从前端传入同名的键值对,后端就能够自动获取到。
当然,我们也可以使用对象来进行接收前端的参数,后端会根据 key 与对象中的属性名自动地进行匹配。
@RequestMapping("/object") public String getObject(User user) { return user.toString(); }
User 中的属性有 id,name 和 password,我们直接从前端传递三个对应名称的键值对,SpringMVC 会自动进行对象的初始化(参数映射)。
除此之外,我们还可以接收form表单的形式的参数,可以使用是 GET/POST 请求方式。
使用@DateTimeFormat注解完成日期参数格式转换,该注解有个pattern的属性,值为我们所传递的日期参数的格式即可。
@RequestMapping("/dateParam") public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) { return updateTime.toString(); }
如果后端方法形参名和与客户端请求参数名不匹配时,就会出现参数接收不到的情况,这种情况,我们可以使用@RequestParam来重命名后端的参数值(后端参数映射)。
其中@RequestParam里面的参数为前端传来的参数名字,它可以自动匹配到注解所在的形参中。
@RequestMapping("/rename") public String getNameAndPass(@RequestParam("name") String username, @RequestParam("pass") String password) { return "用户名:" + username + "密码:" + password; }
还需要注意,使用 @RequestParam 注解修饰的参数,默认情况下,参数是必传,如果不传或少传就会报 400 请求错误。
这个情况是因为 @RequestParam 注解里面还有一个属性required,默认情况下值为true,这个属性的意思是是否是必须参数,所以参数重命名后,重命名的参数默认情况下不能缺少,如果不想将参数设置成必须,但是想重命名,那么就需要额外设置required属性值为false。
@RequestMapping("/rename") public String getNameAndPass(@RequestParam(name = "name", required = false) String username, @RequestParam(name = "pass", required = false) String password) { return "用户名:" + username + " 密码:" + password; }
如果我们想要获取 json 格式的参数,需要使用@RequestBody注解修饰参数(声明了这个参数接收 json 数据)。
@RequestMapping("/object2") public String getObject2(@RequestBody User user) { return user.toString(); }
像上面这样,我们能够获取到 json 格式的参数了。
但这样设置后,就不能接收其他格式的数据了。
这里所说的从 URL 获取参数,并不是获取查询字符串部分的参数,而是直接拿 URL 地址中的参数,简单来说,就是从 URL 中?前面的字段中获取参数;这种方式的优点是搜索引擎抓取关键字权重更高,有利于提高曝光度,且让 URL 更简洁。
获取方法:使用特定 URL 格式和在参数前加上注解@PathVariable
@RequestMapping("/hero/{id}/{name}") public String geuURL(@PathVariable Integer id, @PathVariable String name) { return "id:" + id + " name:" + name; }
我们访问http://127.0.0.1:8080/user/hero/1234/梧桐网页就能够获取到如下内容:
其中hero表示二级目录地址,后面使用{}的就表示参数。
文件的上传,我们需要使用到@RequestPart注解和MultipartFile,我们写方法形参定义的时候,需要一个MultipartFile变量来接收前端传递的文件参数,并设置@RequestPart的属性来表示接收前端参数的名字。
//上传文件 @RequestMapping("/upimg") public boolean updateImg(@RequestPart("img") MultipartFile file) { boolean result = false; try { //将图片保存 file.transferTo(new File("D:\\image\\yan.jpg")); result = true; } catch (IOException e) { log.error("图片上传失败!" + e.getMessage()); } return result; }
我们使用 postman 构造一个请求,给服务器端发送一个图片,我们来看看目标目录下是否得到了我们上传的文件。
目标目录:
在上面的代码中是将保存文件的路径写死的了,但是,在实际开发到上线的过程当中,是涉及到至少三个环境的(开发,测试,生产),所以,保存的文件路径并不会写死,而是将保存文件的路径放在配置文件当中,然后读取配置环境中的保存图片路径。
还要注意, 一次上传文件大小默认是1MB,一次请求上传文件大小默认是10MB,可以在配置文件中进行修改。
# 设置一次上传文件大小(默认是1MB) spring.servlet.multipart.max-file-size=100MB # 设置一次请求上传文件大小(默认是10MB) spring.servlet.multipart.max-request-size=100MB
这里我们来实现一个小案例,就是实现一下上传头像的功能,那这个功能的实现有如下过程:
对于不重名文件名的设定,我们可以使用 UUID,UUID 可以自动生成一个唯一的标识序列,简单理解就是 UUID 可以生成一个全球唯一 id,它由MAC + 随机种子 + 加密算法得出。
# 设置文件保存路径 img.path=D:\\image\\ //获取配置文件的保存路径 @Value("${img.path}") private String path; @RequestMapping("/upload") public String upLoad(@RequestPart("img") MultipartFile file) throws IOException { // 1.生成一个唯一的 id String name = UUID.randomUUID().toString().replace("-", ""); // 2. 得到源文件的后缀名, 和 id 组合成新的文件名 name += (file.getOriginalFilename(). substring(file.getOriginalFilename().lastIndexOf("."))); // 保存文件 System.out.println(path); path += name; file.transferTo(new File(path)); return path; }
一般用户是不会传递这种参数的,基本上都是浏览器构造发过来的,Cookie 和 Session 通常配合使用在登录验证的场景下。
这里同样可以使用 Servlet 那一套 API 获取。
@RequestMapping("/servlet") public void get(HttpServletRequest req, HttpServletResponse resp) { //... }
🍂使用 Servlet 方式获取 Cookie:
@RequestMapping("/cookie") public void getCookie(HttpServletRequest req) { //获取请求中全部的 ookie Cookie[] cookies = req.getCookies(); for (Cookie c : cookies) { log.info("CookieKey : " + c.getName() + " CookieValue : " + c.getValue()); } }
我们可以在浏览器随便加上一些 Cookie。
此时我们访问http://127.0.0.1:8080/user/cookie,来看看控制台是否有输出我们所传的 Cookie。
有记录,就说明我们成功获取到了 Cookie。
🍂简洁地获取Cookie,使用@CookieValue注解,所传参数为你需要获取 Cookie 的 key,使用该注解修饰一个变量,变量里面就会自动获取对应的value值。
@RequestMapping("/cookie2") public String getCookie2(@CookieValue("zhangsan") String cookie) { return "zhangsan : " + cookie; }
如果需要多个 Cookie 值,就写多个 CookieValue 修饰变量就行。
Header 用的比较多的就是获取用户的浏览器的一些基本信息了。
🍂使用 Servlet 获取 Header
@RequestMapping("/getua") public String getUA(HttpServletRequest req) { return "用户设备信息User-Agent : " + req.getHeader("User-Agent"); }
🍂更简单地获取 Header,使用 @RequestHeader 注解
和前面使用注解 @CookieValue 用法类似,也是设置一个 key 来获取对应的 value。
@RequestMapping("/getua2") public String getUA2(@RequestHeader("User-Agent") String ua) { return "用户设备信息User-Agent : " + ua; }
在登录逻辑中,我们需要存储 Session 对象,这一步存储操作,SpringMVC 与 Servlet 操作是一样的,获取方式则有两种方式。
🍂存储Session对象:
@RequestMapping("/setsession") public boolean setSession(HttpServletRequest req) { boolean result = false; //1. 获取Session HttpSession session = req.getSession(true); //2. 设置Session里面的内容 session.setAttribute("sunli", "sl1213456"); result = true; return result; }
访问路径,当新建一个Session对象后,后端会返回一个 Cookie 给前端,根据这个 Cookie 下次验证的时候就能自动登录,不用再次输入账号密码登录了。
🍂使用 Servlet 获取 Session**:**
@RequestMapping("/getsession") public String getSession(HttpServletRequest req) { String result = ""; //1.获取Session HttpSession session = req.getSession(false); //2.验证 if (session != null && session.getAttribute("sunli") != null) { result = (String) session.getAttribute("sunli"); } return result; }
🍂更加简洁获取 Session,使用@SessionAttribute
该注解有两个属性,value表示需要获取 Session 对象里面内容的 key 值,还有一个require表示修饰的参数是否必须,一般需要设置为false,如果设置成true,没有获取到对应的value就会返回一个400的页面。
@RequestMapping("/getsession2") public String getSession2(@SessionAttribute(value = "sunli", required = false) String session) { return session; }
SpringMVC/SpringBoot 默认返回的是视图(xxx.html),这是历史原因了,在方法中返回的数据默认是一个静态页面,如果没有找到这个静态页面,就会显示404。
@Controller public class TestController { @RequestMapping("/sayhi") public String sayHi() { return "hello.html"; } }
页面访问结果:
我们再简单写一个静态页面,放入static目录,来验证一下是否能够获取到。
访问页面结果:
而如果想要返回一个 text 文本数据,则需要使用注解@ResponseBody修饰对应的方法或者修饰类才能实现。
@Controller @ResponseBody public class TestController { @RequestMapping("/sayhi") public String sayHi() { return "hello.html"; } }
@ResponseBody既可以修饰类,也可以修饰方法;修饰类时,类中所有方法都会返回一个非静态页面数据;修饰方法时,只有被修饰的方法返回的是一个非静态页面的数据;返回的值如果是字符会就转换为 text/html,如果返回的是对象会转换成 application/json 返回给前端。
组合注解:@RestController = @Controller + @ResponseBody
🍂小案例,实现一个加法计数器。
前端页面:
calc
后端处理代码,返回计算结果:
package com.example.demo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class CalcController { @RequestMapping("/calc") public String calc(Integer num1, Integer num2) { // 非空验证 if (num1 == null || num2 == null) { return "参数错误!
返回"; } return "" + (num1+num2) + "
"; } }
前端输入数据:
后端返回结果:
要将指定数据构造成 JSON 格式返回,直接返回一个HashMap
@RequestMapping("/getjson") public HashMapmethod_8() { HashMap map = new HashMap<>(); map.put("Java", "Java Value"); map.put("MySQL", "MySQL Value"); map.put("Redis", "Redis Value"); return map; }
🍂小案例,实现登录功能。
前端代码如下,实现的功能大致是发送含有账号与密码参数的请求,等待后端验证是否登录成功,给出简单的提示。
login 登录
⽤户:
密码:
在后端,我们首先需要接收账号与密码参数,然后对这些参数进行确认,为了方便起见,我们将账号与密码写死,即账号是zhangsan密码是123才能登录成功。
我们返回一个 json 格式的数据给前端,里面包含是否登录成功msg,状态码succ,将这两种参数以键值对的形式储存到哈希表中返回即可。
package com.example.demo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; @RestController public class LoginController { @RequestMapping(value = "/login") public HashMaplogin(String username, String password){ HashMap res = new HashMap<>(); int succ = 200; if(username != null && password != null && username.equals("zhangsan") && password.equals("123")){ res.put("msg","登录成功"); }else{ res.put("msg","登录失败"); } res.put("succ",succ); return res; } }
实现效果:
return 不但可以返回一个视图,还可以实现跳转,跳转的方式分别为两种:
🎯请求转发是服务器端的行为,不管几次转发都是只发生服务器端,经过多次转发拿到数据后发送给浏览器。
请求转发实现方式1:
@RequestMapping("/fw") public String myForward() { //转发 html 静态页面 return "forward:/hello.html"; }
这种方式其实forward:/是可以省略的,因为我们知道默认情况下,返回的就是一个静态页面。
转发是发生在服务器内,所以,浏览器中的路由地址自始至终是不会发生改变的
请求转发实现方式2:
@RequestMapping("/fw2") public void myForward2(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("hello.html").forward(req, resp); }
效果是一样的:
🎯请求重定向其实是多次客户端和浏览器的交互,是浏览器和服务器共同的行为,浏览器发送请求后,服务端只是发送一个重定向响应,告诉浏览器要继续访问哪个 URL,浏览器收到服务器的重定向响应后,就会去访问目标地址。
请求重定向的实现方式1: 与请求转发类似,只不过将forward改为redirect。
@RequestMapping("/rd") public String myRedirect() { return "redirect:/hello.html"; }
效果图:
从rd路径跳转到hello.html页面了,重定向浏览器中的路由地址是会发生改变的。
请求重定向的实现方式2:
@RequestMapping("/rd2") public void myRedirect2(HttpServletResponse resp) throws IOException { resp.sendRedirect("hello.html"); }
效果和上面一样的,就不添效果图了。
🍂请求转发(forward)与请求重定向(redirect)之间的区别:
上一篇:SQL plus简单使用