写点什么

Spring MVC 开发入门

作者:Java-fenn
  • 2022 年 9 月 25 日
    湖南
  • 本文字数:11424 字

    阅读完需:约 37 分钟

Spring MVC MVC 模式我正在参加「掘金·启航计划」


一、什么是 Spring MVC 根据官方对 Spring MVC 的描述:


Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从一开始就包含在 Spring 框架中。它 的正式名称“Spring Web MVC”来自其源模块的名称(Spring-webmvc),但它通常被称“Spring MVC”


从上述定义我们可以得出两个关键信息:


Spring MVC 是一个 Web 框架。Spring MVC 是基于 Servlet API 构建的。然而要真正的理解什么是 Spring MVC?我们首先要搞清楚什么是 MVC?


1.1 MVC 定义 MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构模式,它把软件系统分 为模型、视图和控制器三个基本部分。


Model(模型) 是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View(视图) 是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。视图又分为两种:


服务器端的视图(服务器端拿到数据,会对其使用模板和渲染,再传给客户端)客户端的视图 Controller(控制器) 是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据, 控制用户输入,并向模型发送数据。1.2 MVC 和 Spring MVC 的关系 MVC 是一种思想,而 Spring MVC 是对 MVC 思想的具体实现。


Spring MVC 是一个基于 MVC 设计模式和 Servlet API 实现的 Web 项目,同时 Spring MVC 又是 Spring 框架中的一个 Web 模块,它是随着 Spring 的诞生而存在的一个框架。


二、使用 Spring MVC 学习 Spring MVC 我们只需要掌握以下 3 个功能:


连接的功能 :将用户(浏览器)和 Java 程序连接起来,也就是访问一个地址能够调用到我们的 Spring 程序。获取参数的功能 :用户访问的时候会带一些参数,在程序中要想办法获取到参数。


输出数据的功能 :执行了业务逻辑之后,要把程序执行的结果返回给用户。


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


2.1 Spring MVC 创建和连接 Spring MVC 项目创建和 Spring Boot 创建项目相同(Spring MVC 使用 Spring Boot 的方式创建), 在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目。


在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射 ,也就是浏览器连接程序的作用。


2.1.1 创建 Spring MVC 项目 Spring MVC 可以基于 Spring Boot 创建,也就是创建一个 Spring Boot 项目,勾选上 Spring Web 模 块即可,如下图所示


接下来,创建一个 UserController 类,实现用户到 Spring 程序的互联互通,具体实现代码如下:


import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/sayhi")public String sayHi() {    return "hello, world";}
复制代码


}复制代码这样实现之后,当访问地址: http://localhost:8080/user/sayhi 时就能打印“hello,world”的信息 了。


2.1.2 方法一:使用 @RequestMapping ("/xxx") 建立连接 @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一,它是用来注册接口的路由映射的。


路由映射:所谓的路由映射指的是,当用户访问一个 url 时,将用户的请求对应到程序中某个类 的某个方法的过程就叫路由映射。


@RequestMapping 基础使用:


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/sayhi")public String sayHi() {    return "hello, world";}
复制代码


}复制代码


@RequestMapping 即可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类 + 方 法。


@RequestMapping 也可以直接修饰方法,代码实现如下:


@Controller@ResponseBodypublic class UserController {


@RequestMapping("/sayhi")public String sayHi() {    return "hello, world";}
复制代码


}复制代码方法地址为: http://localhost:8080/sayhi


@RequestMapping 是 post 还是 get 请求?默认情况下 @RequestMapping 既支持 POST 请求,也支持 GET 请求。


GET 请求:


POST 请求:


\


如何接收 post 请求?我们可以显式的指定 @RequestMapping 来接收 POST 的情况,如下:


使用 @RequestMapping(method = RequestMethod.POST, value = "/sayhi2")@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping(method = RequestMethod.POST, value = "/sayhi2")public String sayHi2() {    return "hello world2!!!";}
复制代码


}复制代码当使用 GET 请求时就会报错:


只有使用 POST 才行:


\


2.1.3 方法二:使用 @PostMapping("/xxx")只支持 POST 类型的访问


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@PostMapping("/sayhi3")public String sayHi3() {    return "hello, world3 !!";}
复制代码


}复制代码可以使用 POST 请求:


不能使用 GET 请求:


\


2.1.4 方法三:@GetMapping("/xxx")只支持 GET 类型的访问


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@GetMapping("/sayhi4")public String sayHi4() {    return "hello,World4~~";}
复制代码


}复制代码只能使用 GET 请求:


不能使用 POST 请求:


\


\


2.2 获取用户请求参数现在 model 包底下定义一个 UserInfo 类:


import lombok.Data;


@Datapublic class UserInfo {private Integer id;private String username;private Integer age;}复制代码 2.2.1 获取单个参数在 controller 包底下的 UserController 类中获取单个参数:


package com.example.demo.controller;


import com.example.demo.model.UserInfo;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/getuserbyid")public UserInfo getUserById(Integer id) {    // 不查数据库,伪代码,返回 UserInfo 对象    UserInfo user = new UserInfo();    user.setId(1);    user.setUsername("王五");    user.setAge(18);    return user;}
复制代码


}复制代码运行结果:


注意一点:,此参数一定要和前端传递的产生名保持一致,否则就获取不到参数的值了。 示例如下:


当我们要在网址中显示指定参数的话,要把指定的参数传入 user 中:


@RequestMapping("/getuserbyid")public UserInfo getUserById(Integer id) {// 不查数据库,伪代码,返回 UserInfo 对象 UserInfo user = new UserInfo();user.setId(id);user.setUsername("王五");user.setAge(18);return user;}复制代码在网址中指定 id 为 4 的话:


当网址中指定 ids 为 4 的话:(此 ids 不是 中的 id):


\


还要注意一点,传参的类型一定要使用包装类,比如,如果使用 int 类型,在没有对相应的参数传参的话,就会出现报错。下面为示例:


@RequestMapping("/getuserbyid")public UserInfo getUserById(int id) {// 不查数据库,伪代码,返回 UserInfo 对象 UserInfo user = new UserInfo();//user.setId(id); // 没有设置 iduser.setUsername("王五");user.setAge(18);return user;}复制代码报错: ,没有传入参数的话,默认为 null,但是 int 类型是不接收 null 的。


而当使用包装类 Integer 时,即使没有传入参数,也不会报错,会使用 null 做替代值:@RequestMapping("/getuserbyid")public UserInfo getUserById(Integer id) {// 不查数据库,伪代码,返回 UserInfo 对象 UserInfo user = new UserInfo();// user.setId(id);user.setUsername("王五");user.setAge(18);return user;}复制代码


2.2.2 获取多个参数 package com.example.demo.controller;


import com.example.demo.model.UserInfo;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/login")public String login(String username,String passward) {    return "username: " + username + " | passward: " + passward;}
复制代码


}复制代码运行结果:


重要说明:当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置 是不影响后端获取参数的结果。


2.2.3 获取对象当要获取的参数很多的时候,上面的获取多个参数的方法显然不适用了,因为在后端中,我要手动的一个一个加,很麻烦!(前端还是要一个一个传参数的)这时,我们可以使用 传递对象 的方法。


package com.example.demo.controller;


import com.example.demo.model.UserInfo;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/reg")public String reg(UserInfo userInfo) {    return "用户信息:" + userInfo;}
复制代码


}复制代码传入一个参数:


传入两个参数:


这时候就比较方便了。


扩展 2.2.4 后端参数重命名(后端参数映射)某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不一致,比如前端传递了一个 name 给后端,而后端又是有 username 字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值。


示例:


package com.example.demo.controller;


import com.example.demo.model.UserInfo;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/login")public String login(@RequestParam("name") String username,String passward) {    return "username: " + username + " | passward: " + passward;}
复制代码


}复制代码后端参数为 username,前端传入参数为 name,没有报错:


\


2.2.5 设置参数必传 @RequestParam 上面的列子,如果我们是前端传递一个非 name 的参数,就会出现程序报错的情况,如下图所示:


这是因为后端已经声明了前端必须传递一个 name 的参数,但是前端没有给后端传递,我们查看 @RequestParam 注解的实现细节就可以发现端倪,注解实现如下:


\


非必传参数设置如果我们的实际业务前端的参数是一个非必传的参数,我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错,具体实现如下:


package com.example.demo.controller;


import com.example.demo.model.UserInfo;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;


@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/login")public String login(@RequestParam(value = "name",required = false) String username,String passward) {    return "username: " + username + " | passward: " + passward;}
复制代码


}复制代码此时,前端没有传递 name 参数也不会报错了:


\


2.3 获取 JSON 格式的数据 @RequestMapping("/reg")public String reg(UserInfo userInfo) {return "用户信息:" + userInfo;}复制代码使用 Postman 模拟发送 JSON 数据给服务器:


使用 Fiddle 来查看请求和响应:


请求:


响应:


可以看出在响应中,正文 body 并没有接收到 JSON 格式的数据。


所以服务器端实现 JSON 数据的接收需要使用 @RequestBody 注解:


@RequestMapping("/reg")public String reg(RequestBody UserInfo userInfo) {return "用户信息:" + userInfo;}复制代码此时再重复上述过程,就可以发现,在响应正文 body 中有 JSON 格式的数据了:


\


2.4 从 URL 地址中获取参数 @PathVariable 为什么要使用 @PathVariable 如果我们要获取 DOTA 游戏里一个英雄的信息,如影魔,一般情况下,我们获取英雄信息的方式,都是在 URL 的查询字符串的部分来获取的,如 data2.uuu9.com?heroname=影魔。


而通过 URL 地址的方式来获取英雄信息: data2.uuu9.com/66/影魔/


假设 66 为它的 ID。那么通过以往的方式去获取这个英雄的信息,可以吗?


显然不可以,因为之前学过的方法中,就没有一个针对 URL 地址部分的。


这时候就提出了一个问题:为什么要使用这种非主流的方式来获取参数呢?这就涉及到 SU(搜索引擎)的优化了。


什么是 SU 的优化?在浏览器中搜索 DOTA2 影魔,就会出现很多相关的内容:


网站是有排名的,除了那些人民币玩家使自己的排名靠前外,排名,对于“普通玩家”是很重要的,排名的先后,会直接影响到网站的点击率的,点击率收到影响,其盈利也会收到影响。


为了实现这一 “排名机制”,就会采用类似 http://data2.uuu9.com/66/影魔/ 的方式:通过 URL 的地址来(66/影魔/ 被视为网址的一部分,而不是查询字符串)获取网站排名。这一方式,包含了查询的关键字 【影魔】。


对于类似 http://data2.uuu9.com?heroname=影魔 这种方式,虽然也包含关键字,但是这种关键字,它的 SEO(搜索引擎优化)的权重是比较低的。因为 heroname=影魔 是参数嘛,对于大部分浏览器的搜索引擎来说:参数是很容易改变的【原因:业务的不同】。


但是 URL 是不会变的,所以使用类似 http://data2.uuu9.com/66/影魔/ 的访问方式的 SEO(搜索引擎优化)效果是比较好的。


总结下:地址部分的“级别”比参数部分 高,而类似 http://data2.uuu9.com/66/影魔/ 的方式是将参数“伪装”成 URL 地址的一部分。


因此,在前提条件一样的情况下,类似 http://data2.uuu9.com/66/影魔/ 这种方式的网址排名是比类似 http://data2.uuu9.com?heroname=影魔 要高的!


注意:类似 http://data2.uuu9.com/66/影魔/ 这种方式也是存在缺点的!


这种方式,对于 SU、公司的发展 和 网络流量来说,是非常不错的!


但是,对于开发人员就带来了新的挑战!


因为类似 http://data2.uuu9.com?heroname=影魔 很容易获取到参数,但是类似 http://data2.uuu9.com/66/影魔/ 就搞不定,因此要使用 @PathVariable 来获取其参数


@PathVariable 的使用注意:不是从 URL 地址中的参数部分获取参数。


如果没有使用注解 @PathVariable 来看看是什么效果


@RequestMapping("/hero/{id}/{name}")public String getHeroInfo(Integer id,String name) {return "ID: " + id + " | Name: " + name;}复制代码可以看到,没有获取到参数:


\


下面来使用注解 @PathVariable:


@RequestMapping("/hero/{id}/{name}")public String getHeroInfo(@PathVariable Integer id,@PathVariable String name) {return "ID: " + id + " | Name: " + name;}复制代码


\


分析:


hero:url 地址。{id}、{name}:参数。2.5 上传文件 @RequestPart 通过 @RequestPart 注解,将 文件名称 作为参数放入其中,同时还需要借助 Spring 提供的 MultipartFile 对象。


MultipartFile ,就是专门用来接收文件的。还需要配合 RequestPart 注解,来将文件(img)存储到对象中,@RequestPat 注解中的参数就是 文件名称(前端定义的 name)


下面来看示例代码:


@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/upimg")public boolean upImg(Integer id, @RequestPart("img")MultipartFile file) {    boolean result = false;    // 保存图片到本地目录(工作中就是保存到服务器)    try {        // 承载对象 MultipartFile file 中提供了 transferTo 方法,将对象中的数据存储一个指定的位置        // transferTo 方法的参数,要求是一个 File 对象,所以先 new File 对象,将存储路径作为其参数。        // 同时存储过程中可能会失败,因此它会出现异常,我们使用 try catch。        file.transferTo(new File("E:/img.jpg"));        result = true;    } catch (IOException e) {        log.error("图片上传失败:" + e.getMessage());    }
return result;}
复制代码


}复制代码启动项目后,我们使用 Postman 来上传一个图片:


点击 Send 后,可以看到 body 中出现了 true,代表上传图片完成:


然后我们去 目录底下查看图片:


\


但是上述代码有一个问题:图片和存储路径,被写死了。


想象一个场景,A 上传一个图像保存为 img.jpg,然后 B 也上传一个图像也是保存为 img.jpg,这样后续上传的图像就会把之前的图片给覆盖了。


对程序进行优化:


文件命名不能重复。(重复就会出现上面讲的覆盖的情况)存储的目录。(如果在 Linux 系统上,并没有 CEFD 盘,如何解决这个问题? 通过配置文件去设置存储的位置 )获取原先上传图片的格式。(保证原先的文件格式,不会因为上传而被改变)扩展:不同平台运行的配置文件设置 - 优化存储目录使用配置文件可以针对不同的运行环境,写不同的配置文件。


根据不同的环境,来选择对应的参数即可,通过修改一个参数就可以做到。


即:开发环境使用的配置信息 -> 配置文件 application-dev.yml


生产环境使用的配置信息 - > 配置文件 application-prod.yml


(名字可以随便写,但是前面必须是 application,后面加 横杠作为分隔符)


创建一个主配置文件:项目启动后,就能读取别的配置文件。(配置文件的文件名,只能是 application,至于格式 properties 和 yml 都可以)这里我们使用 yml 格式的配置文件:


\


创建开发环境的配置文件 application-dev.yml,并在里面写上路径


\


创建生产环境的配置文件 application-prod.yml,在里面写上路径


\


在主配置文件 application.yml 中有一个属性 spring.profiles.active 可以设置执行的是哪一个配置文件(这里使用开发环境的配置文件)


使用:通过 @Value 注解读取配置文件的属性:(在 saiHi 方法中验证结果)@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


// 从配置文件中读取图片的保存路径@Value("${img.path}")private String imgPath;
@GetMapping("/sayhi")public String sayHi() { return "Hello World! " + imgPath;}
复制代码


}复制代码


\


总结:针对各个平台,创建专属的配置文件。配置文件的命名规则:application + 分隔符 "-" + 平台名称(随意写) + .格式在主配置文件中设置 运行的配置文件【spring.profiles.active】图片名称不能重复 && 获取原图格式 问题解决图片名称问题:时间戳通过时间戳来命名文件,还是存在问题的。但是这种巧合在互联网中很常见:总有那么几个人,同时上传图片,还是会造成图片名称的重复,导致原先的图片被覆盖。


只是发生的概率要小些。因此是不可取的,因为它不适合用于 并发执行的情况。


UUID(全局唯一标识符)UUID 就不会出现时间戳里面的情况。


UUID 会使用 你的网卡、随机数 等等,各种信息来对文件进行命名。


在这种条件下,命名重复的概率几乎不可能出现。


解决获取原图格式问题:在 MutipartFile 对象中,提供了一个 API: getOriginalFilename:获取原始文件的名称。


然后我们可以通过文件名称里面的文件后缀获得文件的格式。


package com.example.demo.controller;


import com.example.demo.model.UserInfo;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;


import java.io.File;import java.io.IOException;import java.util.UUID;


@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/upimg")public boolean upImg(Integer id, @RequestPart("img")MultipartFile file) {    boolean result = false;    // 先获取图片名称    String fileName = file.getOriginalFilename();    // 从最好一个 . 位置,向后截取 剩余部分(获取文件格式)    fileName = fileName.substring(fileName.lastIndexOf("."));    // 利用 UUID 对 文件名称 进行处理,得到唯一的文件名称    // 处理:生成唯一的随机数,与 文件后缀 进行拼接    // 最终的结果 就是 文件的名称    fileName = UUID.randomUUID().toString() + fileName;    try {
file.transferTo(new File(imgPath + fileName)); result = true; } catch (IOException e) { log.error("图片上传失败:" + e.getMessage()); }
return result;}
复制代码


}复制代码启动项目后,使用 Postman 发送一个图片,可以看到保存的图片名称很复杂:


\


2.6 获取 Cookie/Session/header 获取 Cookie 获取 Cookie 有两种方式:


基于 Servlet 提供的 API 来获取 Cookie@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/cookie")public void getCookie(HttpServletRequest request) {// 得到全部的 CookieCookie[] cookies = request.getCookies();for(Cookie item:cookies) {// cookie 采用 键值对 的方式来存储的 log.info("Cookie Name: " + item.getName() +"Cookie Value: " + item.getValue());}}}复制代码如果这个时候访问网站,很有可能造成空指针异常(因为没有获取 cookie):


下面通过浏览器构造几个 cookie。


打开开发者工具:


刷新后,网页就没有报错了:


这就获取到了 Cookie:


虽然方法可以,但是这种读取方式是有点繁琐的!


因为需要先获取请求对象,通过对象提供的方法,来获取 所有的 cookie。


通过情况下,我们获取 cookie 数据的时候,只拿取其中的一个。


由此,Spring MVC 提供一个新的注解:


简洁的获取 Cookie - @CookieValue@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/cookie2")public String cookie2(@CookieValue("zzy") String cookie_value) {    return "Cookie Value: " + cookie_value;}
复制代码


}复制代码


其中, 意思为,将键为“zzy”的 cookie 的值赋值给变量 cookie_value。


获取 header(请求头)基于 Servlet 提供的 API 来获取 header 比如获取 header 中 User-Agent :


@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/getua")public String getHead(HttpServletRequest request) {    return "header: " + request.getHeader("User-Agent");}
复制代码


}复制代码


@RequestHeader@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/getua2")public String getHead2(@RequestHeader("User-Agent") String userAgent){    return "header: " + userAgent;}
复制代码


}复制代码


\


存储和获取 Session 为什么 Session 会多出一个 存储 的操作呢?


原因就是,我必须要先有 session,才能够去获取。


\


特别的是:关于存储操作,只能通过 servlet API。


因为存储操作,是没有办法用参数来表示的。


@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/setsession")public boolean setSession(HttpServletRequest request) {    boolean result = false;    // 1. 先获取到 HttpSession 对象    // 获取 session 对象时,参数必须置为 true(默认值为false)    // true 的意思,如果会话不存在,则创建一个会话    HttpSession session = request.getSession(true);    //2. 使用 setAttribute 方法来设置会话属性    session.setAttribute("userInfo","userInfoValue");    result = true;    return result;}
复制代码


}复制代码重启项目后,访问它:


所以说,每一次请求,都会把 session ID(JSESSIONID)传给后端。


然后后端拿到 sessionId 后,有没有这个信息 ,如果有,我们就可以通过 getAttribute 获取 中 userInfo 的 value 值了。


session 存储的原理:链接


获取 Session 操作,有两个方法:


servlet API@Slf4j@Controller@ResponseBody@RequestMapping("/user")public class UserController {


@RequestMapping("/getsession")public String getSession(HttpServletRequest request) {    String result = null;    // 获取 session 对象    // fasle表示会话不存在,就不在创建会话了    HttpSession session = request.getSession(false);    if (session != null && session.getAttribute("userInfo") != null) {        // 因为获取的 value 值是一个 object 类型,所以需要类型转换        result = (String)session.getAttribute("userInfo");    }    return result;}
复制代码


}复制代码使用注解 @SessionAttribute@RequestMapping("getsession2")public String getSession2(@SessionAttribute(value = "userInfo",required = false) String str) {return "注解方式: " + str;}复制代码参数:


value :代表的是 key 值。required = false:获取一个 key 为 userInfo 的键值对,但不是必须要获取的。此注解的含义是,把 key 为 userInfo 对应的 value 赋给 str 。


\


为什么要加上 require 属性并设置为 false?实际在网络中,setSession 并不一定在 getSession 之前操作,这就意味着 session 中不存在此属性的情况下,你去获取一个不存在的属性。


在下面的代码中没有加入 required=false,我们来看下效果:


(前提是我把浏览器里的 session 里面的属性清空了)代码:


@RequestMapping("getsession2")public String getSession2(@SessionAttribute(value = "userInfo") String str {return "注解方式: " + str;}复制代码重启项目,直接进入网址 http://localhost:8080/user/getsession2 (不要进 http://localhost:8080/user/setsession)


下面我们在代码中加入 required=false,来看看效果。其他情况跟上面一样(先清空 session 属性,然后直接进入 getsession2)


@RequestMapping("getsession2")public String getSession2(@SessionAttribute(value = "userInfo",required = false) String str) {return "注解方式: " + str;}复制代码它就会传个 null,不会让页面直接报错:


\


2.7 返回数据给前端默认情况下,无论是 Spring MVC 或者是 Spring Boot 返回的是视图(xxx.html),而现在都是前后端分离的,后端只需要返回给前端数据即可,这个时候我们就需要使用 @ResponseBody 注解了。


\


演示下不加注解 ResponseBody 的情况:


@Controllerpublic class TestController {@RequestMapping("/sayhi")public String sayHi() {return "hello";}}复制代码预期效果是 页面返回个“hello”字符串,但是直接去访问就会出错:


这是因为 Spring MVC 中,默认返回的是静态页面。当我们返回一个 hello 字符串时,它以为你返回的时一个叫做 hello 的静态页面,它就会去找一个叫做 hello 的页面。


而我们并没有编写叫做 hello 的静态页面,它是找不到的,就会报错。


下面我们来创建一个静态页面:


命名为 hello.html:


在里面编写前端代码:


<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title></head><body><h1 style="color: red"> 这是 Hello 的页面</h1></body></html>复制代码然后修改下返回值:


@Controllerpublic class TestController {@RequestMapping("/sayhi")public String sayHi() {return "hello.html";}}复制代码看下效果:


\


下面是加注解 @ResponseBody 的情况:


@Controller@ResponseBodypublic class TestController {@RequestMapping("/sayhi")public String sayHi() {return "hello.html";}}复制代码直接返回一个非静态页面的数据:


\


有点麻烦,需要使用两个注解 @Controller 和 @ResponseBody,于是 Spring MVC 又提供了一个新的注解 @RestController ,结合了 @Controller 和 @ResponseBody 之间的功能


@RestControllerpublic class TestController {@RequestMapping("/sayhi")public String sayHi() {return "hello,RestController.";}}复制代码


\


为什么 @RestController 可以替代那两个注解的功能呢?进入源码看下:

用户头像

Java-fenn

关注

需要Java资料或者咨询可加我v : Jimbye 2022.08.16 加入

还未添加个人简介

评论

发布
暂无评论
Spring MVC 开发入门_Java_Java-fenn_InfoQ写作社区