入门 简介 SpringMVC 是一站式的web框架 。他是Spring框架中的web模块 ;因为非常重要,所以我们一般独立出来称为 SpringMVC;
官网:https://docs.spring.io/spring-framework/reference/web/webmvc.html#mvc
SpringMVC 是 Spring 的 web 模块,用来开发Web应用
SprinMVC 应用最终作为 B/S、C/S 模式下的 Server 端
Web应用的核心就是 处理HTTP请求响应
开发模式 回顾两种开发模式 :
前后分离开发
SpringMVC提供常见注解:@RestController
、@RequestMapping
、@ResponseBody
等
前端开发人员编写独立的前端项目
前端项目自己控制页面跳转逻辑
后端仅需返回前端需要的JSON数据
后端无需关心页面效果等问题
优点:分工明确,快速协同,专注用户体验
缺点:成本高,技术复杂,门槛高
前后不分离开发
后端开发人员要控制页面跳转逻辑(利用转发、重定向)
服务器要拿到业务数据,全部填充到页面,然后整体把页面返回给客户端
模版引擎作用:将数据填充到页面模板
JSP其实就是一种模板引擎
优点:弱前端、低成本、速度快
缺点:不专业、体验差、效率低、易扯皮
SpringMVC功能 官网:https://docs.spring.io/spring-framework/reference/web/webmvc.html#mvc
SpringMVC功能如下 :
HelloWorld 场景:浏览器发送/hello
请求,服务端响应 Hello,SpringMVC!
创建项目 编写HelloController package com.lfy .springmvc .controller ; import org.springframework .stereotype .Controller ;import org.springframework .web .bind .annotation .RequestMapping ;import org.springframework .web .bind .annotation .ResponseBody ;import org.springframework .web .bind .annotation .RestController ;@RestController public class HelloController { @RequestMapping ("/hello" ) public String handle ( ) { System .out .println ("handle()方法执行了!" ); return "Hello,Spring MVC! 你好!~~~" ; } }
访问测试
启动 Springmvc01HelloworldApplication
浏览器访问 http://localhost:8080/hello
收到服务器响应字符串:Hello,Spring MVC! 你好!~~~
细节
@Controller
与 @RestController
@Controller
:用来开发前后不分离应用:方法的返回值 代表一个页面地址
@RestController
:用来开发前后分离应用:方法的返回值 代表给浏览器响应的内容 (文本/JSON )
通配符 精确路径必须全局唯一 路径位置通配符 : 如果SpringMVC发现请求路径多个方法都能匹配上,那就 精确优先 ; \*
: 匹配任意多个字符(0~N); 不能匹配多个路径 **\**
*: 匹配任意多层路径 ?
: 匹配任意单个字符(1) *精确程度 :* 完全匹配
> ?
*> \*
> \**
@RestController public class HelloController { @ResponseBody @RequestMapping ("/hello" ) public String handle ( ) { System .out .println ("handle()方法执行了!" ); return "Hello,Spring MVC! 你好!~~~" ; } @ResponseBody @RequestMapping ("/he?ll" ) public String handle01 ( ) { System .out .println ("handle01方法执行了!" ); return "handle01" ; } @RequestMapping ("/he*ll" ) public String handle02 ( ) { System .out .println ("handle02方法执行了!" ); return "handle02" ; } @ResponseBody @RequestMapping ("/he/**" ) public String handle03 ( ) { System .out .println ("handle03方法执行了!" ); return "handle03" ; } }
请求处理 路径映射 - @RequestMapping
@RequestMapping 注解是SpringMVC中进行请求映射的重要注解;
请求映射是指 :浏览器发送的请求路径,需要映射一个对应的Controller方法进行处理
路径映射
请求限定
请求方式:method
请求参数:params
请求头:headers
请求内容类型:consumes
响应内容类型:produces
package com.lfy .springmvc .controller ; import org.springframework .stereotype .Controller ;import org.springframework .web .bind .annotation .RequestMapping ;import org.springframework .web .bind .annotation .RequestMethod ;import org.springframework .web .bind .annotation .RestController ;@RestController public class RequestMappingLimitController { @RequestMapping (value = "/test01" ,method = {RequestMethod .DELETE ,RequestMethod .GET }) public String test01 ( ){ return "hello world" ; } @RequestMapping (value = "/test02" ,params = {"age=18" ,"username" ,"gender!=1" }) public String test02 ( ){ return "test02" ; } @RequestMapping (value = "/test03" ,headers = "haha" ) public String test03 ( ){ return "test03" ; } @RequestMapping (value = "/test04" ,consumes = "application/json" ) public String test04 ( ){ return "test04" ; } @RequestMapping (value = "/test05" ,produces = "text/html;charset=utf-8" ) public String test05 ( ){ return "<h1>你好,张三</h1>" ; } }
前置概念复习 HTTP请求/响应 HTTP请求会带来各种数据;HTTP协议格式如下(也就是HTTP携带不同数据的几个位置)
请求首行 :(请求方式、请求路径、请求协议)
请求头 :(k: v \n k: v)
请求头: 有很多重要信息,SpringMVC 可以快速获取到
请求体 :(此次请求携带的其他数据)
请求体 携带大量数据,特别是POST请求,会把参数放在请求体中
URL URL 也可以携带大量数据,特别是GET请求,会把参数放在URL上
JSON JavaScript Object Notation(JavaScript 对象表示法)
JSON用于将结构化数据表示为 JavaScript 对象的标准格式,通常用于在网站上表示和传输数据
JSON 可以作为一个对象或者字符串存在
前者用于解读 JSON 中的数据,后者用于通过网络传输 JSON 数据。
JavaScript 提供一个全局的 可访问的 JSON 对象来对这两种数据进行转换。
JSON 是一种纯数据格式,它只包含属性,没有方法 。如下JSON,如何获取指定位置的属性值
请求处理:实验
环境准备
通过如下实验,测试 SpringMVC
如何获取各种数据
在项目 static
下创建 index.html
文件。启动项目做以上实验;
index.html 文件内容如下 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <link href ="https://cdn.bootcdn.net/ajax/libs/layui/2.9.15/css/layui.min.css" rel ="stylesheet" > <script src ="https://cdn.bootcdn.net/ajax/libs/layui/2.9.15/layui.min.js" > </script > <style > .layui-col-xs6 { padding : 10px ; } </style > </head > <body > <div class ="layui-row" > <div class ="layui-col-xs6" > <h1 > SpringMVC - 请求测试</h1 > <div class ="layui-collapse" > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验1:使用普通变量,收集请求参数</div > <div class ="layui-colla-content" > <div style ="display: flex;justify-content: center" > <form class ="layui-form" action ="/handle01" > <div style ="width: 250px;padding: 10px" > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-username" > </i > </div > <input type ="text" name ="username" placeholder ="用户名" class ="layui-input" > </div > </div > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-password" > </i > </div > <input type ="password" name ="password" placeholder ="密码" class ="layui-input" lay-affix ="eye" > </div > </div > <div class ="layui-form-item" > <div class ="layui-row" > <div class ="layui-col-xs12" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-cellphone" > </i > </div > <input type ="text" name ="cellphone" placeholder ="手机号" class ="layui-input" > </div > </div > </div > </div > <div class ="layui-form-item" > <input type ="checkbox" name ="agreement" title ="同意" > <a href ="#terms" target ="_blank" style ="position: relative; top: 6px; left: -15px;" > <ins > 用户协议</ins > </a > </div > <div class ="layui-form-item" > <button class ="layui-btn layui-btn-fluid" lay-submit > 注册</button > </div > </div > </form > </div > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验2:使用@RequestParam,逐一封装多个参数</div > <div class ="layui-colla-content" > <div style ="display: flex;justify-content: center" > <form class ="layui-form" action ="/handle02" > <div style ="width: 250px;padding: 10px" > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-username" > </i > </div > <input type ="text" name ="username" placeholder ="用户名" class ="layui-input" > </div > </div > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-password" > </i > </div > <input type ="password" name ="password" placeholder ="密码" class ="layui-input" lay-affix ="eye" > </div > </div > <div class ="layui-form-item" > <div class ="layui-row" > <div class ="layui-col-xs12" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-cellphone" > </i > </div > <input type ="text" name ="cellphone" placeholder ="手机号" class ="layui-input" > </div > </div > </div > </div > <div class ="layui-form-item" > <input type ="checkbox" name ="agreement" title ="同意" > <a href ="#terms" target ="_blank" style ="position: relative; top: 6px; left: -15px;" > <ins > 用户协议</ins > </a > </div > <div class ="layui-form-item" > <button class ="layui-btn layui-btn-fluid" lay-submit > 注册</button > </div > </div > </form > </div > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验3:使用POJO,统一封装多个参数</div > <div class ="layui-colla-content" > <div style ="display: flex;justify-content: center" > <form class ="layui-form" action ="/handle03" method ="post" > <div style ="width: 250px;border: 5px solid black;padding: 10px" > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-username" > </i > </div > <input type ="text" name ="username" placeholder ="用户名" class ="layui-input" > </div > </div > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-password" > </i > </div > <input type ="password" name ="password" placeholder ="密码" class ="layui-input" lay-affix ="eye" > </div > </div > <div class ="layui-form-item" > <div class ="layui-row" > <div class ="layui-col-xs12" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-cellphone" > </i > </div > <input type ="text" name ="cellphone" placeholder ="手机号" class ="layui-input" > </div > </div > </div > </div > <div class ="layui-form-item" > <input type ="checkbox" name ="agreement" title ="同意" > <a href ="#terms" target ="_blank" style ="position: relative; top: 6px; left: -15px;" > <ins > 用户协议</ins > </a > </div > <div class ="layui-form-item" > <button class ="layui-btn layui-btn-fluid" lay-submit > 注册</button > </div > </div > </form > </div > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验4:使用@RequestHeader获取请求头数据</div > <div class ="layui-colla-content" > <a type ="button" class ="layui-btn" href ="/handle04" > 随便发个请求</a > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验5:使用@CookieValue获取Cookie数据</div > <div class ="layui-colla-content" > <a type ="button" class ="layui-btn" href ="/handle05" > 随便又发个请求</a > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验6:使用POJO,级联封装复杂对象</div > <div class ="layui-colla-content" > <div style ="display: flex;justify-content: center" > <form class ="layui-form layui-form-pane" action ="/handle06" > <div style ="width: 400px;padding: 10px" > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-username" > </i > </div > <input type ="text" name ="username" placeholder ="用户名" class ="layui-input" > </div > </div > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-password" > </i > </div > <input type ="password" name ="password" placeholder ="密码" class ="layui-input" lay-affix ="eye" > </div > </div > <div class ="layui-form-item" > <div class ="layui-row" > <div class ="layui-col-xs12" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-cellphone" > </i > </div > <input type ="text" name ="cellphone" placeholder ="手机号" class ="layui-input" > </div > </div > </div > </div > <div class ="layui-form-item" > <div class ="layui-row" > <div class ="layui-col-xs4" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-website" > </i > </div > <input type ="text" name ="address.province" placeholder ="省" class ="layui-input" > </div > </div > <div class ="layui-col-xs4" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-find-fill" > </i > </div > <input type ="text" name ="address.city" placeholder ="市" class ="layui-input" > </div > </div > <div class ="layui-col-xs4" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-tree" > </i > </div > <input type ="text" name ="address.area" placeholder ="区" class ="layui-input" > </div > </div > </div > </div > <div class ="layui-form-item" > <div class ="layui-form-item" > <label class ="layui-form-label" > 性别</label > <div class ="layui-input-block" > <input type ="radio" name ="sex" value ="男" title ="男" > <input type ="radio" name ="sex" value ="女" title ="女" > </div > </div > </div > <div class ="layui-form-item" > <div class ="layui-form-item" > <label class ="layui-form-label" > 爱好</label > <div class ="layui-input-block" > <input type ="checkbox" name ="hobby" value ="足球" title ="足球" > <input type ="checkbox" name ="hobby" value ="篮球" title ="篮球" > <input type ="checkbox" name ="hobby" value ="乒乓球" title ="乒乓球" > </div > </div > </div > <div class ="layui-form-item" > <div class ="layui-form-item" > <label class ="layui-form-label" > 年级</label > <div class ="layui-input-block" > <select name ="grade" > <option value ="一年级" > 一年级</option > <option value ="二年级" > 二年级</option > <option value ="三年级" > 三年级</option > <option value ="四年级" > 四年级</option > </select > </div > </div > </div > <div class ="layui-form-item" > <input type ="checkbox" name ="agreement" title ="同意" > <a href ="#terms" target ="_blank" style ="position: relative; top: 6px; left: -15px;" > <ins > 用户协议</ins > </a > </div > <div class ="layui-form-item" > <button class ="layui-btn layui-btn-fluid" lay-submit > 注册</button > </div > </div > </form > </div > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验7:使用@RequestBody,封装JSON对象</div > <div class ="layui-colla-content" > <button type ="button" class ="layui-btn layui-bg-blue" > 去Postman测试,自己带上【实验6】中数据的json</button > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验8:使用@RequestPart/@RequestParam,封装文件,测试文件上传</div > <div class ="layui-colla-content" > <div style ="display: flex;justify-content: center" > <form class ="layui-form layui-form-pane" action ="/handle08" method ="post" enctype ="multipart/form-data" > <div style ="width: 350px;padding: 10px" > <div class ="layui-form-item" > <fieldset class ="layui-elem-field" style ="background-color: lemonchiffon" > <legend > 文件上传要求</legend > <div class ="layui-field-box" > <p > 1. 表单:method=post</p > <p > 2. enctype="multipart/form-data"</p > <p > 3. 注意:SpringMVC对上传文件有大小限制(默认单文件最大:1MB;整个请求最大:10MB)</p > </div > </fieldset > </div > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-username" > </i > </div > <input type ="text" name ="username" placeholder ="用户名" class ="layui-input" > </div > </div > <div class ="layui-form-item" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-password" > </i > </div > <input type ="password" name ="password" placeholder ="密码" class ="layui-input" lay-affix ="eye" > </div > </div > <div class ="layui-form-item" > <div class ="layui-row" > <div class ="layui-col-xs12" > <div class ="layui-input-wrap" > <div class ="layui-input-prefix" > <i class ="layui-icon layui-icon-cellphone" > </i > </div > <input type ="text" name ="cellphone" placeholder ="手机号" class ="layui-input" > </div > </div > </div > </div > <div class ="layui-form-item" > <label class ="layui-form-label" > 头像</label > <div class ="layui-input-block" > <input type ="file" name ="headerImg" class ="layui-input" > </div > </div > <div class ="layui-form-item" > <label class ="layui-form-label" > 生活照</label > <div class ="layui-input-block" > <input type ="file" name ="lifeImg" multiple class ="layui-input" > </div > </div > <div class ="layui-form-item" > <input type ="checkbox" name ="agreement" title ="同意" > <a href ="#terms" target ="_blank" style ="position: relative; top: 6px; left: -15px;" > <ins > 用户协议</ins > </a > </div > <div class ="layui-form-item" > <button class ="layui-btn layui-btn-fluid" lay-submit > 注册</button > </div > </div > </form > </div > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验9:使用HttpEntity,封装请求原始数据</div > <div class ="layui-colla-content" > <a class ="layui-btn layui-bg-blue" href ="/handle09?user=admin&age=18" > 随便㕛发个请求</a > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验10:使用原生Servlet API,获取原生请求对象</div > <div class ="layui-colla-content" > <a class ="layui-btn layui-bg-blue" href ="/handle09?user=admin&age=18" > 随便叒发个请求</a > </div > </div > </div > </div > <div class ="layui-col-xs6" > <h1 > SpringMVC - 响应测试</h1 > <div class ="layui-collapse" > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验1:返回json数据</div > <div class ="layui-colla-content" > <a type ="button" class ="layui-btn" href ="/resp01" > 给个JSON</a > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验2:文件下载测试</div > <div class ="layui-colla-content" > <a type ="button" class ="layui-btn" href ="/download" > 给个美女</a > </div > </div > <div class ="layui-colla-item" > <div class ="layui-colla-title" > 实验3:使用Thymeleaf模版引擎,实现服务端渲染</div > <div class ="layui-colla-content" > <fieldset class ="layui-elem-field" style ="background-color: lemonchiffon" > <legend > 服务端渲染</legend > <div class ="layui-field-box" > <h3 > 现在服务端渲染的方式用的很少了;项目基本都是前后分离。</h3 > <h3 > 这样各端专注于自己的开发,快速协同分工</h3 > <fieldset class ="layui-elem-field" style ="background-color: lightcyan" > <legend > 前后分离</legend > <div class ="layui-field-box" > <h5 > 1. 前端开发人员编写独立的前端项目</h5 > <h5 > 2. 前端项目自己控制页面跳转逻辑</h5 > <h5 > 3. 后端仅需返回前端需要的JSON数据</h5 > <h5 > 4. 后端无需关心页面效果等问题</h5 > <h5 > 优点:分工明确,快速协同,专注用户体验</h5 > <h5 > 缺点:成本高,技术复杂,门槛高</h5 > </div > </fieldset > <fieldset class ="layui-elem-field" style ="background-color: lightcyan" > <legend > 前后不分离(服务端渲染)</legend > <div class ="layui-field-box" > <h5 > 1. 后端开发人员要控制页面跳转逻辑(利用转发、重定向)</h5 > <h5 > 2. 服务器要拿到业务数据,全部填充到页面,然后整体把页面返回给客户端</h5 > <h5 > 3. 模版引擎作用:将数据填充到页面模板</h5 > <h5 > 4. JSP其实就是一种模板引擎</h5 > <h5 > 优点:弱前端、低成本、速度快</h5 > <h5 > 缺点:不专业、体验差、效率低、易扯皮</h5 > </div > </fieldset > </div > </fieldset > </div > </div > </div > </div > </div > </body > <style > </style > </html >
实验1:使用普通变量,收集请求参数 发送请求: http://localhost:8080/handle01?username=zhangsan&password=123456&cellphone=13333333333&agreement=on
Controller代码 :
@RequestMapping("/handle01") public String handle01 (String username, String password, String cellphone, boolean agreement) { System.out.println(username); System.out.println(password); System.out.println(cellphone); System.out.println(agreement); return "ok" ; }
实验2:使用@RequestParam,逐一封装多个参数 发送请求 :http://localhost:8080/handle02?username=zhangsan&password=123456&cellphone=13333333333&agreement=on
Controller代码:
@RequestMapping ("/handle02" )public String handle02 (@RequestParam ("username" ) String name, @RequestParam (value = "password" ,defaultValue = "123456" ) String pwd, @RequestParam ("cellphone" ) String phone, @RequestParam (value = "agreement" ,required = false ) boolean ok ){ System .out .println (name); System .out .println (pwd); System .out .println (phone); System .out .println (ok); return "ok" ; }
实验3:使用POJO,统一封装多个参数 发送请求: 请求体
Controller代码:
@RequestMapping ("/handle03" )public String handle03 (Person person ){ System .out .println (person); return "ok" ; }
@Data public class Person { private String username = "zhangsan" ; private String password; private String cellphone; private boolean agreement; }
发送请求: 请求头
Controller代码:
@RequestMapping ("/handle04" )public String handle04 (@RequestHeader (value = "host" ,defaultValue = "127.0.0.1" ) String host, @RequestHeader ("user-agent" ) String ua ){ System .out .println (host); System .out .println (ua); return "ok~" +host; }
实验5:使用@CookieValue获取Cookie数据 发送请求 :自己添加一个cookie,名为 haha,值为任意; 然后发送请求,尝试获取值
Controller代码:
@RequestMapping ("/handle05" )public String handle05 (@CookieValue ("haha" ) String haha ){ return "ok:cookie是:" + haha; }
实验6:使用POJO,级联封装复杂对象 Controller代码
@RequestMapping ("/handle06" )public String handle06 (Person person ){ System .out .println (person); return "ok" ; }
@Data public class Person { private String username = "zhangsan" ; private String password; private String cellphone; private boolean agreement; private Address address; private String sex; private String [] hobby; private String grade; } @Data class Address { private String province; private String city; private String area; }
实验7:使用@RequestBody,封装JSON对象 @RequestMapping ("/handle07" )public String handle07 (@RequestBody Person person ){ System .out .println (person); return "ok" ; }
实验8:使用@RequestPart/@RequestParam,封装文件,测试文件上传 Controller代码:
@RequestMapping("/handle08") public String handle08 (Person person, @RequestParam("headerImg") MultipartFile headerImgFile, @RequestPart("lifeImg") MultipartFile[] lifeImgFiles) throws IOException { String originalFilename = headerImgFile.getOriginalFilename(); long size = headerImgFile.getSize(); InputStream inputStream = headerImgFile.getInputStream(); System.out.println(originalFilename + " ==> " + size); headerImgFile.transferTo(new File ("D:\\img\\" + originalFilename)); System.out.println("===============以上处理了头像=================" ); if (lifeImgFiles.length > 0 ) { for (MultipartFile imgFile : lifeImgFiles) { imgFile.transferTo(new File ("D:\\img\\" + imgFile.getOriginalFilename())); } System.out.println("=======生活照保存结束==========" ); } System.out.println(person); return "ok!!!" ; }
实验9:使用HttpEntity,封装请求原始数据 @RequestMapping("/handle09") public String handle09 (HttpEntity<Person> entity) { HttpHeaders headers = entity.getHeaders(); System.out.println("请求头:" +headers); Person body = entity.getBody(); System.out.println("请求体:" +body); return "Ok~~~" ; }
实验10:使用原生Servlet API,获取原生请求对象 @RequestMapping("/handle10") public void handle10 (HttpServletRequest request, HttpServletResponse response, HttpMethod method) throws IOException { System.out.println("请求方式:" +method); String username = request.getParameter("username" ); System.out.println(username); response.getWriter().write("ok!!!" +username); }
小结 常见接收三种数据的办法
接收普通参数
无论是GET请求,还是POST请求,携带的 k=v&k=v数据;还是请求头的数据
使用实验1-6的方式:
直接写变量名
@RequestParam、@RequestHeader、@CookieValue 注解
POJO
接收JSON数据 JSON数据放在请求体中。使用 @ReqeustBody + 对象;直接获取数据并封装为对象
接收文件上传 @RequestParam/@RequestPart 可以获取普通项或者文件项;
@RequestPart (“imgs”) MultipartFile[] files
响应处理 实验1:返回JSON数据 注意:SpringMVC 底层使用 HttpMessageConverter 处理json数据的序列化与反序列化
@ResponseBody @RequestMapping ("/resp01" )public Person resp01 ( ) { Person person = new Person (); person.setUsername ("张三" ); person.setPassword ("1111" ); person.setCellphone ("22222" ); person.setAgreement (false ); person.setSex ("男" ); person.setHobby (new String []{"足球" , "篮球" }); person.setGrade ("三年级" ); return person; }
实验2:文件下载 基本原理:
写出的数据必须告诉浏览器数据类型 。文件下载需要两个
Content-Type:application/octet-stream
;内容类型:二进制流
Content-Disposition:attachment;filename=哈哈美女.jpg
;内容处置方式:附件;文件名=xx
注意:文件名有可能有中文,所以需要提前进行URL编码
二进制流写出要小心OOM;
InputStreamResource resource = new InputStreamResource(inputStream);
@RequestMapping("/download") public ResponseEntity<InputStreamResource> download () throws IOException { FileInputStream inputStream = new FileInputStream ("C:\\Users\\53409\\Pictures\\Saved Pictures\\必应壁纸(1200张)\\AutumnNeuschwanstein_EN-AU10604288553_1920x1080.jpg" ); String encode = URLEncoder.encode("哈哈美女.jpg" , "UTF-8" ); InputStreamResource resource = new InputStreamResource (inputStream); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .contentLength(inputStream.available()) .header("Content-Disposition" , "attachment;filename=" +encode) .body(resource); }
这种方式会OOM
@GetMapping("/download") public ResponseEntity<byte []> handleDownload() throws IOException { byte [] bytes = Files.readAllBytes(Paths.get(new File ("aaaa.png" ).toURI())); return ResponseEntity .ok() .header("Content-Disposition" , "attachment; filename=aaaa.png" ) .contentType(MediaType.APPLICATION_OCTET_STREAM) .contentLength(bytes.length) .body(bytes); }
RESTful - CRUD:案例 RESTful 简介 REST (Representational State Transfer 表现层状态转移)是一种软件架构风格;
官网:https://restfulapi.net/
完整理解 :Resource Representational State Transfer
Resource :资源
Representational :表现形式:比如用JSON,XML,JPEG等
State Transfer :状态变化:通过HTTP的动词(GET、POST、PUT、DELETE)实现
一句话:使用资源名作为URI,使用HTTP的请求方式表示对资源的操作
满足REST 风格的系统,我们称为是 RESTful 系统
设计 RESTful API 以前,接口的设计可能是这样的
**/getEmployee?id=1
**:查询员工
**/addEmployee?name=zhangsan&age=18
**:新增员工
**/updateEmployee?id=1&age=20
**:修改员工
**/deleteEmployee?id=1
**:删除员工
**/getEmployeeList
**:获取所有员工
路径变量 - @PathVariable
RESTful的接口中,经常路径的某些位置是动态的。比如 /employee/``{id}
;这个id 可能是6、7、8、9等;
这时我们程序就需要能获取到这个动态的值。SpringMVC 提供了 @PathVariable
注解获取路径变量的值;
语法规则如下:
/resources/{name}
:{} 中的值封装到 name 变量中
/resources/{*path}
:{} 中的值封装到 path 变量中;可以获取多层路径
**/resources/image.png
**: path = /image.png
**/resources/css/spring.css
**:path = /css/spring.css
/resources/{filename:\\w+}.dat
:{} 中的值封装到 filename 变量中;
filename 满足 \w+ 正则要求
**/resources/{
filename
:
\\w+
}.dat
**;如:/resources/xxx.dat:xxx是一个或多个字母
CRUD 案例 系统设计
规划 RESTful 接口
创建统一返回 R 对象
实现简单的 CRUD,暂不考虑复杂查询与分页查询
测试 CRUD 的功能
前端联动测试
找到 资料 中 nginx.zip ,解压到 非中文无空格 目录下
运行 nginx.exe ,访问 localhost 即可访问前端项目
前端项目源码为 rest-crud-vue.zip ,学完前端工程化,就可以二次开发
注意:还要解决 跨域问题
数据库 创建数据库:restful_crud
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0 ;DROP TABLE IF EXISTS `employee`;CREATE TABLE `employee` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键' , `name` varchar (255 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '员工名字' , `age` int NULL DEFAULT NULL COMMENT '年龄' , `email` varchar (255 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱' , `gender` varchar (255 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别' , `address` varchar (255 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '住址' , `salary` decimal (10 , 2 ) NULL DEFAULT NULL COMMENT '薪资' , PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic ; INSERT INTO `employee` VALUES (1 , '张三' , 11 , 'aa@qq.com' , '男' , '西安' , 9999.00 );INSERT INTO `employee` VALUES (4 , 'leifengyang' , 10 , 'aaa' , '男' , 'sss' , 100.00 );SET FOREIGN_KEY_CHECKS = 1 ;
统一返回R package com.lfy .practice .common ; import lombok.Data ;@Data public class R <T> { private Integer code; private String msg; private T data; public static <T> R<T> ok (T data ){ R<T> tr = new R<>(); tr.setCode (200 ); tr.setMsg ("ok" ); tr.setData (data); return tr; } public static R ok ( ){ R tr = new R<>(); tr.setCode (200 ); tr.setMsg ("ok" ); return tr; } public static R error ( ){ R tr = new R<>(); tr.setCode (500 ); tr.setMsg ("error" ); return tr; } public static R error (Integer code,String msg ){ R tr = new R<>(); tr.setCode (code); tr.setMsg (msg); return tr; } public static R error (Integer code,String msg,Object data ){ R tr = new R<>(); tr.setCode (code); tr.setMsg (msg); tr.setData (data); return tr; } }
Employee - 模型 package com.lfy .practice .bean ; import lombok.Data ;import java.math .BigDecimal ;@Data public class Employee { private Long id; private String name; private Integer age; private String email; private String gender; private String address; private BigDecimal salary; }
Service - 业务层 service接口 package com.lfy.practice.service.impl;import com.lfy.practice.bean.Employee;import com.lfy.practice.dao.EmployeeDao;import com.lfy.practice.service.EmployeeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import java.util.List;@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired EmployeeDao employeeDao; @Override public Employee getEmp (Long id) { Employee empById = employeeDao.getEmpById(id); return empById; } @Override public void updateEmp (Employee employee) { Long id = employee.getId(); if (id == null ){ return ; } Employee empById = employeeDao.getEmpById(id); if (StringUtils.hasText(employee.getName())){ empById.setName(employee.getName()); } if (StringUtils.hasText(employee.getEmail())){ empById.setEmail(employee.getEmail()); } if (StringUtils.hasText(employee.getAddress())){ empById.setAddress(employee.getAddress()); } if (StringUtils.hasText(employee.getGender())){ empById.setGender(employee.getGender()); } if (employee.getAge() != null ){ empById.setAge(employee.getAge()); } if (employee.getSalary() != null ){ empById.setSalary(employee.getSalary()); } employeeDao.updateEmp(empById); } @Override public void saveEmp (Employee employee) { employeeDao.addEmp(employee); } @Override public void deleteEmp (Long id) { employeeDao.deleteById(id); } @Override public List<Employee> getList () { return employeeDao.getList(); } }
Service - 业务层 service接口 package com.lfy.practice.service.impl;import com.lfy.practice.bean.Employee;import com.lfy.practice.dao.EmployeeDao;import com.lfy.practice.service.EmployeeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import java.util.List;@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired EmployeeDao employeeDao; @Override public Employee getEmp (Long id) { Employee empById = employeeDao.getEmpById(id); return empById; } @Override public void updateEmp (Employee employee) { Long id = employee.getId(); if (id == null ){ return ; } Employee empById = employeeDao.getEmpById(id); if (StringUtils.hasText(employee.getName())){ empById.setName(employee.getName()); } if (StringUtils.hasText(employee.getEmail())){ empById.setEmail(employee.getEmail()); } if (StringUtils.hasText(employee.getAddress())){ empById.setAddress(employee.getAddress()); } if (StringUtils.hasText(employee.getGender())){ empById.setGender(employee.getGender()); } if (employee.getAge() != null ){ empById.setAge(employee.getAge()); } if (employee.getSalary() != null ){ empById.setSalary(employee.getSalary()); } employeeDao.updateEmp(empById); } @Override public void saveEmp (Employee employee) { employeeDao.addEmp(employee); } @Override public void deleteEmp (Long id) { employeeDao.deleteById(id); } @Override public List<Employee> getList () { return employeeDao.getList(); } }
service实现 package com.lfy.practice.service.impl;import com.lfy.practice.bean.Employee;import com.lfy.practice.dao.EmployeeDao;import com.lfy.practice.service.EmployeeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import java.util.List;@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired EmployeeDao employeeDao; @Override public Employee getEmp (Long id) { Employee empById = employeeDao.getEmpById(id); return empById; } @Override public void updateEmp (Employee employee) { Long id = employee.getId(); if (id == null ){ return ; } Employee empById = employeeDao.getEmpById(id); if (StringUtils.hasText(employee.getName())){ empById.setName(employee.getName()); } if (StringUtils.hasText(employee.getEmail())){ empById.setEmail(employee.getEmail()); } if (StringUtils.hasText(employee.getAddress())){ empById.setAddress(employee.getAddress()); } if (StringUtils.hasText(employee.getGender())){ empById.setGender(employee.getGender()); } if (employee.getAge() != null ){ empById.setAge(employee.getAge()); } if (employee.getSalary() != null ){ empById.setSalary(employee.getSalary()); } employeeDao.updateEmp(empById); } @Override public void saveEmp (Employee employee) { employeeDao.addEmp(employee); } @Override public void deleteEmp (Long id) { employeeDao.deleteById(id); } @Override public List<Employee> getList () { return employeeDao.getList(); } }
Dao - 数据访问层 Dao接口 package com.lfy.practice.dao;import com.lfy.practice.bean.Employee;import java.util.List;public interface EmployeeDao { Employee getEmpById (Long id) ; void addEmp (Employee employee) ; void updateEmp (Employee employee) ; void deleteById (Long id) ; List<Employee> getList () ; }
Dao实现 package com.lfy.practice.dao.impl;import com.lfy.practice.bean.Employee;import com.lfy.practice.dao.EmployeeDao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Component;import java.util.List;@Component public class EmployeeDaoImpl implements EmployeeDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Employee getEmpById (Long id) { String sql = "select * from employee where id=?" ; Employee employee = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper <>(Employee.class), id); return employee; } @Override public void addEmp (Employee employee) { String sql = "insert into employee(name,age,email,gender,address,salary) values (?,?,?,?,?,?)" ; int update = jdbcTemplate.update(sql, employee.getName(), employee.getAge(), employee.getEmail(), employee.getGender(), employee.getAddress(), employee.getSalary()); System.out.println("新增成功,影响行数:" + update); } @Override public void updateEmp (Employee employee) { String sql = "update employee set name=?,age=?,email=?,gender=?,address=?,salary=? where id=?" ; int update = jdbcTemplate.update(sql, employee.getName(), employee.getAge(), employee.getEmail(), employee.getGender(), employee.getAddress(), employee.getSalary(), employee.getId()); System.out.println("更新成功,影响行数:" + update); } @Override public void deleteById (Long id) { String sql = "delete from employee where id=?" ; int update = jdbcTemplate.update(sql, id); } @Override public List<Employee> getList () { String sql = "select * from employee" ; List<Employee> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper <>(Employee.class)); return list; } }
Controller - 控制器 package com.lfy .practice .controller ; import com.lfy .practice .common .R ;import com.lfy .practice .bean .Employee ;import com.lfy .practice .service .EmployeeService ;import org.springframework .beans .factory .annotation .Autowired ;import org.springframework .web .bind .annotation .*;import java.util .List ;@CrossOrigin @RequestMapping ("/api/v1" )@RestController public class EmployeeRestController { @Autowired EmployeeService employeeService; @GetMapping ("/employee/{id}" ) public R get (@PathVariable ("id" ) Long id ){ Employee emp = employeeService.getEmp (id); return R.ok (emp); } @PostMapping ("/employee" ) public R add (@RequestBody Employee employee ){ employeeService.saveEmp (employee); return R.ok (); } @PutMapping ("/employee" ) public R update (@RequestBody Employee employee ){ employeeService.updateEmp (employee); return R.ok (); } @DeleteMapping ("/employee/{id}" ) public R delete (@PathVariable ("id" ) Long id ){ employeeService.deleteEmp (id); return R.ok (); } @GetMapping ("/employees" ) public R all ( ){ List <Employee > employees = employeeService.getList (); return R.ok (employees); } }
最佳实践 拦截器:HandlerInterceptor 简介 SpringMVC 内置拦截器机制 ,允许在请求被目标方法处理的前后进行拦截 ,执行一些额外操作;比如:权限验证、日志记录、数据共享等…
使用步骤
实现 HandlerInterceptor 接口的组件即可成为拦截器
创建 WebMvcConfigurer 组件,并配置拦截器的拦截路径
查看执行顺序效果:preHandle => 目标方法 => postHandle => afterCompletion
拦截器配置 拦截器代码
package com.atguigu.practice.interceptor;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;@Component public class MyHandlerInterceptor0 implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyHandlerInterceptor0...preHandle..." ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyHandlerInterceptor0...postHandle..." ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyHandlerInterceptor0...afterCompletion..." ); } }
拦截器配置
package com.atguigu.practice.config;import com.atguigu.practice.interceptor.MyHandlerInterceptor0;import com.atguigu.practice.interceptor.MyHandlerInterceptor1;import com.atguigu.practice.interceptor.MyHandlerInterceptor2;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class MySpringMVCConfig implements WebMvcConfigurer { @Autowired MyHandlerInterceptor0 myHandlerInterceptor0; @Autowired MyHandlerInterceptor1 myHandlerInterceptor1; @Autowired MyHandlerInterceptor2 myHandlerInterceptor2; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(myHandlerInterceptor0) .addPathPatterns("/**" ); registry.addInterceptor(myHandlerInterceptor1) .addPathPatterns("/**" ); registry.addInterceptor(myHandlerInterceptor2) .addPathPatterns("/**" ); } }
拦截器执行顺序 拦截器执行顺序 :顺序preHandle
=>目标方法
=>倒序postHandle
=>渲染
=>倒序afterCompletion
只有执行成功的 preHandle
会倒序执行 afterCompletion
postHandle
、afterCompletion
从哪里炸,倒序链路从哪里结束
postHandle
失败不会影响 afterCompletion
执行
以下是多拦截器执行顺序流程图
异常处理 SpringMVC 提供了非常方便的声明式异常处理 ;
编程式异常处理 :
try - catch、throw、exception
声明式异常处理:
**SpringMVC
**提供了 @ExceptionHandler
、 @ControllerAdvice
等便捷的声明式注解来进行快速的异常处理
**@ExceptionHandler
**:可以处理指定类型异常
**@ControllerAdvice
**:可以集中处理所有Controller的异常
@ExceptionHandler
+ **@ControllerAdvice
**: 可以完成全局统一异常处理
通用逻辑 统一返回:R对象 package com.atguigu .practice .common ; import io.swagger .v3 .oas .annotations .media .Schema ;import lombok.Data ;@Schema (description = "统一返回" )@Data public class R <T> { @Schema (description = "状态码" ) private Integer code; @Schema (description = "提示信息" ) private String msg; @Schema (description = "数据" ) private T data; public static <T> R<T> ok (T data ){ R<T> tr = new R<>(); tr.setCode (200 ); tr.setMsg ("ok" ); tr.setData (data); return tr; } public static R ok ( ){ R tr = new R<>(); tr.setCode (200 ); tr.setMsg ("ok" ); return tr; } public static R error ( ){ R tr = new R<>(); tr.setCode (500 ); tr.setMsg ("error" ); return tr; } public static R error (Integer code,String msg ){ R tr = new R<>(); tr.setCode (code); tr.setMsg (msg); return tr; } public static R error (Integer code,String msg,Object data ){ R tr = new R<>(); tr.setCode (code); tr.setMsg (msg); tr.setData (data); return tr; } }
业务异常封装 package com.atguigu.practice.exception;import lombok.Data;@Data public class BizException extends RuntimeException { private Integer code; private String msg; public BizException (Integer code, String message) { super (message); this .code = code; this .msg = message; } public BizException (BizExceptionEnume exceptionEnume) { super (exceptionEnume.getMsg()); this .code = exceptionEnume.getCode(); this .msg = exceptionEnume.getMsg(); } }
异常枚举 package com.atguigu.practice.exception;import lombok.Getter;public enum BizExceptionEnume { ORDER_CLOSED(10001 , "订单已关闭" ), ORDER_NOT_EXIST(10002 , "订单不存在" ), ORDER_TIMEOUT(10003 , "订单超时" ), PRODUCT_STOCK_NOT_ENOUGH(20003 , "库存不足" ), PRODUCT_HAS_SOLD(20002 , "商品已售完" ), PRODUCT_HAS_CLOSED(20001 , "商品已下架" ); @Getter private Integer code; @Getter private String msg; private BizExceptionEnume (Integer code, String msg) { this .code = code; this .msg = msg; } }
@ExceptionHandler - 本类异常处理 package com.atguigu.practice.controller;import com.atguigu.practice.common.R;import org.springframework.web.bind.annotation.*;import java.io.FileInputStream;import java.io.FileNotFoundException;@RestController public class HelloController { @GetMapping("/hello") public R hello (@RequestParam(value = "i",defaultValue = "0") Integer i) throws FileNotFoundException { int j = 10 / i; String s = null ; s.length(); return R.ok(j); } @ResponseBody @ExceptionHandler(ArithmeticException.class) public R handleArithmeticException (ArithmeticException ex) { System.out.println("【本类】 - ArithmeticException 异常处理" ); return R.error(100 ,"执行异常:" + ex.getMessage()); } @ExceptionHandler(FileNotFoundException.class) public R handleException (FileNotFoundException ex) { System.out.println("【本类】 - FileNotFoundException 异常处理" ); return R.error(300 ,"文件未找到异常:" + ex.getMessage()); } @ExceptionHandler(Throwable.class) public R handleException02 (Throwable ex) { System.out.println("【本类】 - Throwable 异常处理" ); return R.error(500 ,"其他异常:" + ex.getMessage()); } }
@ControllerAdvice - 全局异常处理 package com.atguigu.practice.advice;import com.atguigu.practice.common.R;import com.atguigu.practice.exception.BizException;import org.springframework.stereotype.Component;import org.springframework.validation.BindingResult;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;import java.util.List;import java.util.Map;@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ArithmeticException.class) public R error (ArithmeticException e) { System.out.println("【全局】 - ArithmeticException 处理" ); return R.error(500 , e.getMessage()); } @ExceptionHandler(BizException.class) public R handleBizException (BizException e) { Integer code = e.getCode(); String msg = e.getMsg(); return R.error(code, msg); } @ExceptionHandler(value = MethodArgumentNotValidException.class) public R methodArgumentNotValidException (MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List<FieldError> errors = result.getFieldErrors(); Map<String, String> map = new HashMap <>(); for (FieldError error : errors) { String field = error.getField(); String message = error.getDefaultMessage(); map.put(field, message); } return R.error(500 , "参数错误" , map); } @ExceptionHandler(Throwable.class) public R error (Throwable e) { System.out.println("【全局】 - Exception处理" + e.getClass()); return R.error(500 , e.getMessage()); } }
SpringBoot底层默认异常处理机制 SpringBoot 依然 使用 SpringMVC 的异常处理机制;不过 SpringBoot 编写了一些默认的处理配置;
默认行为:自适应的异常处理;
最佳实践:项目架构是一开始就决定好的(前后分离?)
数据校验 JSR 303 是 Java 为 Bean 数据合法性校验 提供的标准框架 ,它已经包含在 JavaEE 6.0 标准中 。JSR 303 通过在 Bean 属性上 标注 类似于 @NotNull、@Max 等标准的注解 指定校验规则,并通过标准的验证接口 对Bean进行验证 。
数据校验使用流程
1、引入校验依赖:spring-boot-starter-validation
2、定义封装数据的Bean
3、给Bean的字段标注校验注解,并指定校验错误消息提示
4、使用@Valid、@Validated开启校验
5、使用 BindingResult 封装校验结果
6、使用自定义校验注解 + 校验器(implements ConstraintValidator) 完成gender字段自定义校验规则
7、结合校验注解 message属性 与 i18n 文件,实现错误消息国际化
8、结合全局异常处理,统一处理数据校验错误
流程实现 引入依赖 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-validation</artifactId > </dependency >
校验注解 @Data public class Employee { private Long id; @NotBlank (message = "姓名不能为空" ) private String name; @Min (value = 0 , message = "年龄不能小于0岁" ) @Max (value = 150 , message = "年龄不能大于150岁" ) @NotNull (message = "年龄不能为空" ) private Integer age; @Email (message = "邮箱格式不正确" ) private String email; private String gender; private String address; private BigDecimal salary; private Date birth; }
开启校验:@Valid @Operation(summary="新增员工") @PostMapping("/employee") public R add (@RequestBody @Valid Employee employee) { employeeService.saveEmp(employee); return R.ok(); }
BindingResult:获取校验结果 BindingResult 要紧跟在 @Valid 标注的校验Bean上。
@Operation(summary="新增员工") @PostMapping("/employee") public R add (@RequestBody @Valid Employee employee,BindingResult result) { if (!result.hasErrors()) { } Map<String, String> errorsMap = new HashMap <>(); for (FieldError fieldError : result.getFieldErrors()) { String field = fieldError.getField(); String message = fieldError.getDefaultMessage(); errorsMap.put(field, message); } return R.error(500 , "校验失败" , errorsMap); }
自定义校验注解+自定义校验器 自定义校验注解
自定义校验器@Documented @Constraint(validatedBy = {GenderValidator.class}) @Target({ FIELD }) @Retention(RUNTIME) public @interface Gender { String message () default "性别不能为null" ; Class<?>[] groups() default { }; Class<? extends Payload >[] payload() default { }; }
自定义校验器
public class GenderValidator implements ConstraintValidator <Gender , String > { @Override public boolean isValid (String value, ConstraintValidatorContext context ) { return "男" .equals (value) || "女" .equals (value); } }
错误消息国际化 多种语言环境下的错误消息配置文件。
浏览器发送请求,改变语言环境,会自动展示对应的错误内容
全局校验错误,统一处理 package com.atguigu.practice.advice;import com.atguigu.practice.common.R;import com.atguigu.practice.exception.BizException;import org.springframework.stereotype.Component;import org.springframework.validation.BindingResult;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;import java.util.List;import java.util.Map;@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = MethodArgumentNotValidException.class) public R methodArgumentNotValidException (MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List<FieldError> errors = result.getFieldErrors(); Map<String, String> map = new HashMap <>(); for (FieldError error : errors) { String field = error.getField(); String message = error.getDefaultMessage(); map.put(field, message); } return R.error(500 , "参数错误" , map); } @ExceptionHandler(Throwable.class) public R error (Throwable e) { System.out.println("【全局】 - Exception处理" + e.getClass()); return R.error(500 , e.getMessage()); } }
VO分层 /** * 设计模式:单一职责; * JavaBean也要分层,各种xxO: * Pojo:普通java类 * Dao:Database Access Object : 专门用来访问数据库的对象 * DTO:Data Transfer Object: 专门用来传输数据的对象; * TO:transfer Object: 专门用来传输数据的对象; * BO:Business Object: 业务对象(Service),专门用来封装业务逻辑的对象; * VO:View/Value Object: 值对象,视图对象(专门用来封装前端数据的对象) * * * 新增员工; * 要求:前端发送请求把员工的json放在请求体中 * 要求:如果校验出错,返回给前端。 * { * "code": 500, * "msg": "校验失败", * "data": { * "name": "姓名不能为空", //这些就是为了让前端知道是哪些输入框错了,怎么错误,给用户要显示提示。 * "age": "年龄不能超过150" * } * } * @param vo * @return */
接口文档 后端会产生很多接口 (所有Controller暴露的请求,我们称为HTTP API,也叫HTTP接口 );前后分离开发模式下,前端需要参考这些接口的定义,才能知道给哪里发送请求,接收什么数据,能实现什么功能;后端开发人员需要编写对应的接口文档 ,可以使用以下技术:
Swagger 可以快速生成实时接口文档,方便前后开发人员进行协调沟通。遵循 OpenAPI 规范。
Knife4j 是基于 Swagger之上的增强套件
版本适配 Knife4j 和 Swagger的版本适配经常会出现问题。一定使用对应版本的。推荐如下
boot版本 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 3.4.7</version > <relativePath /> </parent >
knife4j版本 knife4j 底层有 springdoc,springdoc和springboot的版本适配很重要,负责无法工作
<dependency > <groupId > com.github.xiaoymin</groupId > <artifactId > knife4j-openapi3-jakarta-spring-boot-starter</artifactId > <version > 4.5.0</version > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > <version > 2.7.0</version > </dependency >
springdoc版本
主要的是springdoc和springboot的适配;
knife4j 底层是依赖 springdoc 的
Spring Boot Versions
Springdoc OpenAPI Versions
3.4.x
2.7.x - 2.8.x
3.3.x
2.6.x
3.2.x
2.3.x - 2.5.x
3.1.x
2.2.x
3.0.x
2.0.x - 2.1.x
2.7.x, 1.5.x
1.6.0+
2.6.x, 1.5.x
1.6.0+
2.5.x, 1.5.x
1.5.9+
2.4.x, 1.5.x
1.5.0+
2.3.x, 1.5.x
1.4.0+
2.2.x, 1.5.x
1.2.1+
2.0.x, 1.5.x
1.0.0+
配置文件 springdoc.swagger-ui.path
配置swagger的页面地址
springdoc.api-docs.path
配置openapi文档地址
springdoc.group-configs
配置分组
springdoc: swagger-ui: path: /swagger-ui.html tags-sorter: alpha api-docs: path: /v3/api-docs group-configs: - group: 'default' display-name: '测试' paths-to-match: '/**' packages-to-scan: com.lfy.fyadmin.controller default-flat-param-object: true
访问测试 访问:localhost:8080/doc.html ,可以看到knife4j的文档展示
访问:localhost:8080/swagger-ui.html,可以看到swagger的文档展示