3.4 获取 JSON 对象
前面获取的参数, 都是 URL 的形式获取的参数, 哪前端传来的是 JSON 对象, 那么该如何接受获取呢 ?
对于 UserInfo 这个对象, 如果传入的是一个 JSON 格式的 UserInfo, 在用之前的获取方式还能行得通吗 ?
@RequestMapping("/reg") public Object reg(UserInfo userInfo) { System.out.println(userInfo); return userInfo; }
利用 Postman 构造请求提交一个 UserInfo 到后端 :
可以看到, 虽然传入了一个 UserInfo 对象, 但是后端根本没有收到正确的对象, 该怎么解决这问题呢 ?
3.4.1 @RequestBody 注解
在 Spring MVC 里提供了 @RequestBody 注解来接收传来的 JSON 对象
@RequestMapping("/reg3") public Object reg3(@RequestBody UserInfo userInfo) { return userInfo; }
利用 Postman 构造请求 :
可以看到, 后端是成功的拿取到了前端传来的这个 JSON 格式的对象
对于前端传来的 JSON 对象, Spring Boot 框架和之前一样, 会将拿到的信息赋值给参数, 并自动适配数据格式
3.5 路径传参
3.5.1 @PathVariable 注解
这样传入参数, 和之前 URL 传参中 user?username=张三&password=123 来说, 搜索引擎抓取关键字的权重更高, 能让人更多的搜索到. 同时当传入的参数更多时, Path 传参的 URL 更简洁.
@RequestMapping("/reg4/{username}/{password}") public String reg3(@PathVariable String username, @PathVariable String password) { return "username : " + username + " password : " + password; }
通过 path : reg/4/zhangsan/123 访问, 此处username 就对应到了 zhangsan, password 就对应到了 123, 但这里并不是键值的关系, 因此它对位置是非常敏感的, 无法调换位置, 比如下面这段代码, 此时在去通过刚刚的 Path 获取是无法正确获取的
可以看到, 此时username=123, 而password=zhangsan, 所以 Path 路径的参数它不是键值的关系, 受到 URL 位置对应影响, 这点需要注意
3.5.2 @PathVariable 注解的重命名
对于 @PathVariable 也是有重命名规则的, 并且它还和我们之前的 @RequestParam 注解一样, 有则必传参数的原则, 当设置了 @PathVariable 注解后, 该参数就为必传参数, 如果不需要可以将 required 属性设置为 false.
@RequestMapping("/reg4/{username}/{password}") public String reg3(@PathVariable String username, @PathVariable String password) { return "username : " + username + " password : " + password; }
当我们不传入password 时, 再去访问就会报错, 想要 password 不传, 在 @PathVariable 参数后面设置 required 属性为 false 即可
在来看看 @PathVariable 重命名, 还是和前面的重命名一样的
同样还是重命名后, 将获取到的 name 的属性值赋值给 username 使用
3.6 获取上传文件
前端除了传来参数、对象意外, 还会传来一些文件, 因此对于如何获取上传的文件也是非常重要的
3.6.1 MultipartFile 对象
@RequestParam 注解中, 可以设置参数名称, 例如我设置的 myimg, 而 MultipartFile 接口代表了上传文件, 用于处理 HTTP 请求中上传文件的相关操作, 在 Spring MVC 中通常会将上传文件包装成 MultipartFile 对象进行处理
@RequestMapping("/upload") public String upLoad(@RequestParam("myimg") MultipartFile file) throws IOException { File saveFile = new File("E:\\Javacode\\spring\\spring-mvc\\myimg.png"); // 文件保存的路径 try { file.transferTo(saveFile); // 上传文件 - 本身是没有返回值的, 通过 try catch 来检验是否上传成功 return "文件上传成功! "; }catch (IOException e) { e.printStackTrace(); } return "文件上传失败 !"; }
利用 Postman 构造上传文件
当上传成功后, 可以在之间设置的保存文件路径中查看是否成功上传保存
同样, 该图片是可以正常打开的
PS : 需要注意的是, 图片上传的单次文件默认最大大小为 1MB, 单次请求默认最大大小为 10MB, 如果需要上传更大的, 可以在配置文件中进行设置
# 设置单次图片最大大小 spring.servlet.multipart.max-file-size=100MB # 设置单次请求最大大小 spring.servlet.multipart.max-request-size=100MB
虽然上面的代码是成功的获取了前端传来的文件, 但是它并不适用于生产之中, 原因在于每次上次的文件都会进行覆盖上一个文件, 因为它的名称是一样的, 会在相同路径下保存同一个名称的文件而覆盖上一个文件, 因此不具备生产能力. 那么, 要如何解决这个问题, 让每次生产的文件都不一样呢 ?
想要不重覆, 可以用时间戳、 时间戳加随机数、系统时间等方法, 但是尽管时间戳在不断变化, 但是当并发量够大时, 总有可能出现二者相同时间上传文件, 导致其重复的. 为了更好地解决上面问题, 我们引入了 UUID( 通用唯一标识符 ) .
文件名解决后, 还需要解决一件事, 那就是文件的后缀名, 之前我们用的是固定的文件后缀名 .png, 但是用户上传的文件是很多的, 后缀名不是固定的, 因此我们要想办法获取到用户上传的后缀名并且将获取到的后缀名添加到保存的文件后缀中.
@RequestMapping("upload2") public String upload2(@RequestParam("myimg") MultipartFile file) { // 创建文件名名称 String fileName = UUID.randomUUID() + //文件名 file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); // 后缀名 // lastIndexOf 左闭右开需要注意, 包含分割后的 . 的 File saveFile = new File("E:\\Javacode\\spring\\spring-mvc\\" + fileName); // 保存文件 try { file.transferTo(saveFile); // 上传文件 return "上传文件成功 !"; }catch (IOException e) { e.printStackTrace(); } return "上传文件失败"; }
启动后用 Postman 构建表达请求发送文件
查看是否发送成功
同时检查我们的文件是否保存在对应路径之中, 可以看到此时文件名已经正确保存并且生成了一串文件名非常复杂的名称, 这个就是 UUID 生成的
即使多次上传同一文件, 它也不会覆盖
3.7 获取 Cookie (@CookieValue 注解)
之前说过, Spring MVC 是基于 Servlet API 的, 因此之前的获取 Cookie 的方法任然可以用, 不同的是用 Servlet 里的 request 去获取 Cookie 获取的是整个页面的所有 Cookie, 也就是得到的是一个 Cookie 数组
@RequestMapping("getCK") public void getCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); }
如果想要去获取指定的某个 Cookie, 还需要去遍历这个 Cookie 数组去拿取指定的 Cookie, 会比较麻烦, 因此在 Spring MVC 中提供了 @CookieValue 注解, 该注解添加后, 一样是一个必传参数, 并且前端必须有这个 Cookie 否则会错误, 也可以通过设置 required 属性来解决这个问题
@RequestMapping("getCK2") public String getCookie(@CookieValue("spring") String spring) { return spring; }
由于并没有事先在浏览器中构造一个名为 spring 的 cookie, 因此此时直接去访问时获取不到的
构造好后, 再去获取就可以成功拿取到了
3.8 获取 Header (@RequestHeader 注解)
请求头里面包含了许多信息, 通过@RequestHeader 注解也是很容易获取到的, 先来回顾一下 Header 请求头中都有哪些字段 ( 下面是我的浏览器中的请求字段 )
这些字段都是可以通过 @RequestHeader 注解来获取的, 尝试获取一下当前的 Host, 还和之前一样, 获取注解里面指定的 Host 属性后赋值给 host 变量
@RequestMapping("getHD") public String getHeader(@RequestHeader("Host") String host) { return host; }
可以看到, 我当前的 Host 如下 :
其他的字段也是同样的获取方法, 只需修改获取的指定属性就行, 大家可以试试 !
3.9 获取 Session (@RequestAttribute 注解)
在那之前, 先回顾之前 Servlet 如何创建会话, 由于此处我没有具体的业务代码, 因此我手动建立一个会话 SESSION_KEY
private static final String SESSION_KEY = "DEFAULT_VALUE"; @RequestMapping("/createSession") public void CreateSession(HttpServletRequest request) { HttpSession session = request.getSession(true); // 没有 session 就创建 session session.setAttribute(SESSION_KEY, "username"); // 将username存入到会话中 }
通过 Servlet 来获取 Session, 在获取之前先要运行建立会话路由方法才能有会话可获取
@RequestMapping("/getSession") public String getSession2(HttpServletRequest request) { HttpSession session = request.getSession(false); // 有则获取, 没有就不建立 return " Servlet 获取 : " + (String) session.getAttribute(SESSION_KEY); }
访问 Servlet 获取 Session 的路由方法
访问注解方式获取 Session 的路由方法
4. 返回数据
获取前端的参数非常重要, 但是给前端返还数据一样是很重要的, 下面就来看几个常见的返回数据
4.1 返回静态页面
static 包底下创建一个 spring.html 文件, 并在文件中写一个 < h1 > Spring MVC < /h1 > 标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>Spring MVC</h1> </body> </html>
之前我们返回的都是数据, 而并非页面, 因此我们使用了 @RestBody 注解 或者 @RestController 注解, 但此处我们要返回的就是一个静态页面, 而并非数据, 因此此处不需要在加上面的注解了
@Controller @RequestMapping("/test") public class ControllerDemo { @RequestMapping("/getHtml/test2") public String getHtml() { return "spring.html"; // 返回的液面必须与创建的静态页面名称一致 } }
根据路由方法访问, 结果出现了 404, 根本获取不到对应的页面
这是为什么 ? 当我们在返回的 spring.html 中不加上斜杠, 也就是 “/spring.html” 时, 去访问路由方法并不会在根目录底下去获取, 因此获取不到指定页面, 当我们加上 " / " 以后, 就会让它在根目录底下去获取, 从而可以正确拿到返回的页面
4.2 返回 JSON 对象
除了静态页面, 后端也会经常给前端返回 JSON 格式的对象
@RestController @RequestMapping("/resJson") public HashMap<String, String> resJson() { HashMap<String, String> map = new HashMap<>(); map.put("zhangsan", "1"); map.put("lisi", "2"); return map; }
还是一样的, 加了注解以后, Spring Boot 框架会自动将你返回的数据进行包装.
4.3 请求转发和请求重定向
Spring MVC 天生就是返回静态页面的, 因此对于重定向、转发等返回是很容易的.
@Controller public class ControllerDemo { // 请求转发 @RequestMapping("/fw") public String index2() { return "forward: /spring.html"; } }
有没有发现, 这和我们之前返回静态页面好像是一样的 ? 其实不然, 当我们访问路由返回的是一个静态页面时, 由于我们访问的是接口, 而你要返回一个静态页面, 实际上就是一个请求转发的过程.
看看请求重定向 :
@Controller public class ControllerDemo { // 请求重定向 @RequestMapping("/re") public String index() { return "redirect: /spring.html"; } }
惊奇的发现, 我们访问重定向的路由地址不是 localhost:8080/re 嘛 ? 怎么页面展示出来结果后, URL 就变成了 localhost:8080/spring.html, 我们可以抓包看看
可以看到, 当访问路由地址时, 它直接 302 给我们跳转到了 spring.html 页面.
那么, 请求转发和请求重定向有什么区别呢 ?
- 请求转发地址没变, 而请求重定向后地址会发生改变
- 请求转发由服务端转发, 而请求重定向重新定位到资源
- 请求转发由服务端转发, 有可能造成原外部资源不能访问; 而请求重定向与直接访问新地址效果一样, 不存在原来外部资源不能访问
请求转发由服务器内部进行转发, 用户的感知是不明显的, 因为它的地址并没有发生变化, 但是给你展示的是你想要的页面; 但是重定向用户感知是比较明显的, 它类似于通过他人之手, 直接让你去找谁谁谁( 你重定向的位置 ).
外部资源不能访问 : 经过服务器进行转发的, 服务器并不像你重定向一样明确的知道你要的是什么, 有可能会出错, 并不觉得清楚你需要什么. 当层级目录过多, 服务器去转发一个静态页面的时候有可能就会出错.