先赞后看,养成习惯!!!?? ?? ??
资源收集不易,如果喜欢可以关注我哦!
?如果本篇内容对你有所启发,欢迎访问我的个人博客了解更多内容:链接地址
Spring MVC是Spring框架提供的一个基于MVC(Model-View-Controller)设计模式的Web应用程序开发框架。它提供了一组组件,如控制器、模型和视图,用于快速开发Web应用程序。与其他Web框架相比,Spring MVC具有灵活的配置方式、可扩展性和可测试性等优点。
SpringMVC是一种表现层框架技术,用于进行表现层功能开发
SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。
controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载呢?
分析清楚谁该管哪些bean以后,接下来要解决的问题是如何让Spring和SpringMVC分开加载各自的内容。
在SpringMVC的配置类SpringMvcConfig中使用注解@ComponentScan,我们只需要将其扫描范围设置到controller即可
在Spring的配置类SpringConfig中使用注解@ComponentScan,当时扫描的范围中其实是已经包含了controller
从包结构来看的话,Spring已经多把SpringMVC的controller类也给扫描到,所以针对这个问题该如何解决,就是咱们接下来要学习的内容。
加载Spring控制的bean的时候排除掉SpringMVC控制的bean
具体该如何排除:
指请求的页面由服务器上预先准备好的静态web资源组成,如HTML、CSS、JS、IMG等,返回给客户端的信息内容是不变的。
由服务器直接将请求的资源返回给客户端,服务器不处理任何逻辑,只是将预先准备好的资源返回给客户端。
服务器会根据用户的请求动态生成内容,将数据返回到客户端显示页面内容。
由服务器从数据库中获取数据,并进行相应的逻辑处理后将处理结果返回客户端
指请求的页面由服务器上预先准备好的静态web资源组成,如HTML、CSS、JS、IMG等,返回给客户端的信息内容是不变的。
由服务器直接将请求的资源返回给客户端,服务器不处理任何逻辑,只是将预先准备好的资源返回给客户端。
通过在?controller?中定义对应的类及方法实现动态请求的业务逻辑处理。
添加在类上;
表示该类是一个控制器,负责处理用户的请求,并将处理结果生成响应返回给客户端。
添加在控制器类或控制器方法上;
将HTTP请求映射到控制器中的方法,指定处理请求的路径
控制器类上:为整个控制器指定一个基础路径
控制器方法上:指定相对于基础路径的具体路径
添加在控制器方法上;
可以使控制器方法通过返回值的方式将响应返回给客户端
超文本传输协议(没有状态的协议,客户端服务端之前的每次对话都是一次请求响应就结束)
HTTP协议是浏览器与服务器通讯的应用层协议,规定了浏览器与服务器之间的交互规则以及交互数据的格式信息等。
HTTPS协议
浏览器给服务端发送的内容称为请求Request,一个请求包含三部分:请求行,请求头,请求体
请求类别 GET
抽象路径 /
协议版本 HTTP/1.1
请求类别:每个请求类别表示向服务器端发请求做不同的操作
GET : 获取服务器资源
POST :新增服务器资源
PUT : 更新服务器资源
DELETE : 删除服务器资源
关于抽象路径
请求URL地址为:http://localhost:8080/
请求行为: GET / HTTP/1.1
请求URL地址为:http://localhost:8080/v1/users/login
请求行为: GET /v1/users/login HTTP/1.1
请求URL地址为:http://localhost:8080/v1/users/reg
请求行为: GET /v1/users/reg HTTP/1.1
请求头是浏览器可以给服务端发送的一些附加信息,有的用来说明浏览器自身内容,有的用来告知服务端交互细节,有的告知服务端消息正文详情等。
Host: localhost:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
请求体通常是用户上传的信息,比如:在页面输入的注册信息,上传的附件等内容。
服务端给浏览器发送的内容称为响应Response,一个响应包含三部分:响应行,响应头,响应体。
HTTP/1.1 200 OK 版本信息 响应码 附加信息 响应码 : 1xx:保留 2xx:成功,表示处理成功,并正常响应 3xx:重定向,表示处理成功,但是需要浏览器进一步请求 4xx:客户端错误,表示客户端请求错误导致服务端无法处理 5xx:服务端错误,表示服务端处理请求过程出现了错误
响应头与请求中的消息头格式一致,表示的是服务端发送给客户端的附加信息。
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 3546 //Content-Length是用来告知浏览器响应正文的长度,单位是字节。
Content-Type?是用来告知浏览器响应正文中的内容是什么类型的数据(图片,页面等等)不同的类型对应的值是不同,浏览器接收正文前会根据上述两个响应头来得知长度和类型从而读取出来做对应的处理以渲染给用户看。
文件类型 | Content-Type对应的值 |
html | text/html |
css | text/css |
js | application/javascript |
png | image/png |
gif | image/gif |
jpg | image/jpeg |
响应的主体内容信息
定义
? URL(Uniform Resource Locator)是互联网上?统一资源定位符?的简称,用于标识和定位互联网上资源的地址。在Web浏览器中,URL是用于访问网页的地址。
查询参数和路径Path之间使用 ?分隔,多个查询参数之间使用 & 分隔。
GET请求
? GET请求是HTTP协议中最常见的请求方式之一,它用于从服务器获取数据。
? GET请求将查询参数附加在URL之后,通过“?”符号进行分隔。
GET请求的参数通常以键值对的形式附加在URL之后。例如,以下URL中,“?name=John&age=25”是两个查询参数,分别表示名字和年龄
http://example.com/users?name=John&age=25
在浏览器中输入URL地址确认即可向服务端发送GET请求。
http://localhost:8080/v1/users/login?username=xxx&password=xxx
POST请求
?POST请求是另一种常见的HTTP请求方式,它用于向服务器提交数据。与GET请求不同,POST请求将数据放在请求体(Request Body)中,而不是URL中。
? POST请求的请求体中包含要提交的数据。这些数据可以是JSON、XML或其他格式。请求体中的数据通过Content-Type头部指定。
POST /users HTTP/1.1
Host: example.com
Content-Type: application/json
Request Body: {"username": "john", "password": "secret"}
-
经常在?form?表单中发送。
<form action="请求地址" method="post">
<input type="text" name="username">
<input type="text" name="password">
<input type="submit" name="登录">
</form>
/**方式1:使用HttpServletRequest接收数据*/
@RequestMapping("/v1/users/login")
@ResponseBody
public String login(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
return username + ":" + password;
}
可以在处理请求的方法中通过?声明参数的方式?来接收客户端传递过来的数据。
/**方式2:通过声明参数的方式接收*/
@RequestMapping("/v1/users/login")
@ResponseBody
// 好处:代码简洁,并且可以自动根据声明的类型进行转换
public String login(String username, String password){
return "username = " + username + ", password = " + password;
}
如果客户端传递数据过多,通过?HttpServletRequest?方式接收复用性较差,通过?声明参数接收?又很繁琐;所以可以将数据封装到?POJO类?中来接收
/**方式3:通过声明Pojo类接收*/
@RequestMapping("/v1/users/login")
@ResponseBody
public String login(User user){
return user.toString();
}
public class User {
// 客户端传递几个参数,此处就有几个属性
private String username;
private String password;
// 省略 setter() getter() 和 toString() 方法
}
使用json数据格式来传输数据
web前端==>服务端 通过@RuquestBody注解完成json数据格式的转换
服务端==>web前端 通过@ResponseBody注解完成json数据格式的转换
@RestController
等价于@Controller 加 @Response
前面我们已经能够使用GET或POST来发送请求和数据,所携带的数据都是比较简单的数据,接下来在这个基础上,我们来研究一些比较复杂的参数传递,常见的参数种类有:
问题分析
团队多人开发,每人设置不同的请求路径,冲突问题该如何解决?
解决思路:为不同模块设置模块名作为请求路径前置
对于Book模块的save,将其访问路径设置http://localhost/book/save
对于User模块的save,将其访问路径设置http://localhost/user/save
这样在同一个模块中出现命名冲突的情况就比较少了。
JSON(JavaScript Object Notation)是一种数据交换格式,常用于不同系统间的数据传输。
在使用SpringMVC框架时,常常需要将Java对象转换成JSON格式,然后返回给前端。
JSON格式的数据通常由一对花括号 {} 括起来,在花括号中包含键值对,键值对之间使用逗号分隔。
JSON数据传输参数
SpringMVC接收JSON数据的实现步骤为:
(1)导入jackson包
(2)使用PostMan发送JSON数据
(3)开启SpringMVC注解驱动,在配置类上添加@EnableWebMvc注解
(4)Controller方法的参数前添加@RequestBody注解
一种资源描述方式
查看REST风格的描述,你会发现请求地址变的简单了,并且光看请求URL并不是很能猜出来该URL的具体功能
所以REST的优点有:
查询全部用户信息 GET(查询)
查询指定用户信息 GET(查询)
添加用户信息 POST(新增/保存)
修改用户信息 PUT(修改/更新)
删除用户信息 DELETE(删除)
请求的方式比较多,但是比较常用的就4种,分别是GET,POST,PUT,DELETE。
按照不同的请求方式代表不同的操作类型。
清楚了什么是REST风格后,我们后期会经常提到一个概念叫RESTful,那什么又是RESTful呢?
域名中体现出api字样,如
路径中避免使用动词,资源用名词表示,案例如下
https://api.example.com/v1/users https://api.example.com/v1/animals
2.1 说明
请求动词 | 说明 |
GET(SELECT) | 从服务器取出资源(一项或多项) |
POST(CREATE) | 在服务器新建一个资源 |
PUT(UPDATE) | 在服务器更新资源 |
DELETE(DELETE) | 从服务器删除资源 |
具体案例如下:
请求动作 | 请求资源 | 说明 |
GET | /zoos | 列出所有动物园 |
POST | /zoos | 新建一个动物园 |
GET | /zoos/ID | 获取某个指定动物园的信息 |
PUT | /zoos/ID | 更新某个指定动物园的信息 |
DELETE | /zoos/ID | 删除某个动物园 |
GET | /zoos/ID/animals | 列出某个指定动物园的所有动物 |
DELETE | /zoos/ID/animals/ID | 删除某个指定动物园的指定动物 |
巧用查询参数
?type_id=1:指定筛选条件 ?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。
用HTTP响应码表达 此次请求结果,例如
响应码 | 说明 |
200 OK - [GET] | 服务器成功返回用户请求的数据 |
404 NOT FOUND - [*] | 用户发出的请求针对的是不存在的记录 |
500 INTERNAL SERVER ERROR - [*] | 服务器发生错误 |
400 | 请求参数错误 |
405 | 请求方法错误 |
@RequestMapping?用于指定处理请求的 URL,它可以标注在类和方法上;
可以通过?method?参数限定处理 GET、POST、PUT、DELETE 等HTTP的请求方法,比如:
?GET?请求
@RequestMapping(value = "/v1/users",?method = RequestMethod.GET)
?POST?请求
@RequestMapping(value = "/v1/users",?method = RequestMethod.POST)
处理 PUT DELETE 方式的HTTP请求同理。
@GetMapping?只会处理 HTTP GET 请求;
是?@RequestMapping(method = RequestMethod.GET)?的缩写。
如果限定为处理GET请求,则发送其他方式请求时HTTP状态码为 405
@PostMapping?只会处理 HTTP POST 请求;
是?@RequestMapping(method = RequestMethod.POST)?的缩写。
如果限定为处理POST请求,则发送其他方式请求时HTTP状态码为 405
@PutMapping?只会处理 HTTP PUT 请求;
是?@RequestMapping(method = RequestMethod.PUT)?的缩写。
如果限定为处理PUT请求,则发送其他方式请求时HTTP状态码为 405
@DeleteMapping?只会处理 HTTP DELETE 请求;
是?@RequestMapping(method = RequestMethod.DELETE)?的缩写。
如果限定为处理DELETE请求,则发送其他方式请求时HTTP状态码为 405
@PathVariable?注解用于接受 RESTful API 中的 URL 中的变量;
通常与请求注解一起使用,可以将 URL 中的变量映射到 Controller 中的方法参数上。
假如有一个 RESTful API:/v1/users/{id},
其中?{id}?是一个变量,表示用户 ID。
可以这样定义一个处理该请求的控制器方法
@GetMapping("/v1/users/{id}") public String getUserById(@PathVariable Integer id) { // 根据 ID 查询用户,并返回用户信息 }
根据?id?查询用户,并返回该用户信息
实现
?id?查询用户,并返回该用户信息(GET请求:/v1/users/{id})
@GetMapping("{id}") public User selectById(@PathVariable int id){ // 自己定义相对应的接口方法 return userMapper.selectById(id); }
想要完成文件上传这个功能需要涉及到两个部分:
我们先来看看在前端程序中要完成哪些代码:
<form action="/upload" method="post" enctype="multipart/form-data"> 姓名: <input type="text" name="username"><br> 年龄: <input type="text" name="age"><br> 头像: <input type="file" name="image"><br> <input type="submit" value="提交"> </form>
上传文件的原始form表单,要求表单必须具备以下三点(上传文件页面三要素):
type="file" name="image"/>
通常上传的文件会比较大,所以需要使用 POST 提交方式
普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格式必须设置为multipart/form-data
前面我们已分析了文件上传功能前端和后端的基础代码实现,文件上传时在服务端会产生一个临时文件,请求响应完成之后,这个临时文件被自动删除,并没有进行保存。下面呢,我们就需要完成将上传的文件保存在服务器的本地磁盘上。
代码实现:
MultipartFile 常见方法: String getOriginalFilename(); //获取原始文件名void transferTo(File dest); //将接收的文件转存到磁盘文件中long getSize(); //获取文件的大小,单位:字节byte[] getBytes(); //获取文件内容的字节数组InputStream getInputStream(); //获取接收到的文件内容的输入流
如果直接存储在服务器的磁盘目录中,存在以下缺点:
为了解决上述问题呢,通常有两种解决方案:
首先我们在宏观上先有一个认知:
前面在讲解HTTP协议的时候,我们提到HTTP协议是无状态协议。什么又是无状态的协议?
所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互,基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。
那应该怎么来实现登录校验的操作呢?具体的实现思路可以分为两部分:
我们要完成以上操作,会涉及到web开发中的两个技术:
而统一拦截技术现实方案也有两种:
cookie 是客户端会话跟踪技术,它是存储在客户端浏览器的,我们使用 cookie 来跟踪会话,我们就可以在浏览器第一次发起请求来请求服务器的时候,我们在服务器端来设置一个cookie。
比如第一次请求了登录接口,登录接口执行完成之后,我们就可以设置一个cookie,在 cookie 当中我们就可以来存储用户相关的一些数据信息。比如我可以在 cookie 当中来存储当前登录用户的用户名,用户的ID。
服务器端在给客户端在响应数据的时候,会自动的将 cookie 响应给浏览器,浏览器接收到响应回来的 cookie 之后,会自动的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中,都会将浏览器本地所存储的 cookie 自动地携带到服务端。
接下来在服务端我们就可以获取到 cookie 的值。我们可以去判断一下这个 cookie 的值是否存在,如果不存在这个cookie,就说明客户端之前是没有访问登录接口的;如果存在 cookie 的值,就说明客户端之前已经登录完成了。这样我们就可以基于 cookie 在同一次会话的不同请求之间来共享数据。
我刚才在介绍流程的时候,用了 3 个自动:
为什么这一切都是自动化进行的?
是因为 cookie 它是 HTP 协议当中所支持的技术,而各大浏览器厂商都支持了这一标准。在 HTTP 协议官方给我们提供了一个响应头和请求头:
优缺点
区分跨域的维度:
只要上述的三个维度有任何一个维度不同,那就是跨域操作
举例:
http://192.168.150.200/login.html ----------> https://192.168.150.200/login [协议不同,跨域]
http://192.168.150.200/login.html ----------> http://192.168.150.100/login [IP不同,跨域]
http://192.168.150.200/login.html ----------> http://192.168.150.200:8080/login [端口不同,跨域]
http://192.168.150.200/login.html ----------> http://192.168.150.200/login [不跨域]
前面介绍的时候,我们提到Session,它是服务器端会话跟踪技术,所以它是存储在服务器端的。而 Session 的底层其实就是基于我们刚才所介绍的 Cookie 来实现的。
设置Httpsession参数
常用方法
这里我们所提到的令牌,其实它就是一个用户身份的标识,看似很高大上,很神秘,其实本质就是一个字符串
是一种用于在网络应用间传递信息的编码规范。在会话校验中,JWT可以用于进行身份验证和权限控制。
JWT校验一般包括以下步骤:
JWT校验提供了一种无状态的会话验证机制,通过在客户端存储 JWT,服务器可以根据 JWT 进行身份验证和权限控制,而无需在服务器端存储会话信息,从而简化了服务器的复杂性和开销。
自动创建get set toString
Lombok 支持自动生成 getter、setter、toString等方法,减少了重复性的开发工作。
日志级别:TRACE
用于跟踪代码执行的详细信息。通常用于调试阶段,用于输出一些详细的调试信息,对性能影响较大;
用于输出调试信息,帮助开发人员诊断问题。通常用于开发和测试阶段,例如输出方法的输入参数和返回值;
普通信息级别;用于输出程序的一般运行信息。通常用于生产环境,记录程序运行的关键信息,如系统启动、关键操作完成等;
用于输出警告信息。通常用于发现一些可能的问题或不正常的情况,但不会影响程序的正常运行;
用于输出错误信息。通常用于记录程序的错误信息、异常信息,表示程序出现了严重的问题,无法正常运行
注意:在?@Slf4j?注解中,应根据不同的应用场景和需求,可以选择适当的日志级别。
在开发和测试阶段,可以将日志级别设置为DEBUG,以便获取详细的调试信息。
而在生产环境中,一般将日志级别设置为INFO或更高级别。
# 设置日志级别为WARN
logging.level.root=WARN
# 将cn.tedu包及其包中的所有类的日志级别设置为DEBUG级别
logging.level.cn.tedu=DEBUG
使用?@Slf4j?注解相比?System.out.println("xxx")?的好处
使用?@Slf4j?注解输出日志,可以避免产生大量的无用日志信息,减少对内存和磁盘等资源的消耗。
而使用?System.out.println()?会产生大量冗余的输出信息,不仅对调试造成困扰,而且会对应用程序的性能产生影响。
使用?@Slf4j?注解,可以根据需要输出不同级别的日志,例如,警告、错误等。通过灵活控制日志输出的级别,可以及时发现并解决问题。
而使用?System.out.println()?输出的日志级别是不可控的,并且无法选择性地过滤日志。
Knife4j是基于SpringBoot构建的一个文档生成工具,它可以让开发者为我们的应用生成API文档;
目的是可以更加方便的基于API文档进行测试。
生成的文档还可以导出,然后给到前端开发团队,前端开发团队可以基于API接口写具体的调用。
在你的SpringBoot项目的pom.xml文件中,添加如下依赖:
<!--添加Knife4j依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>
<dependency>
添加在控制器类上的注解;
通过此注解的tags属性可以修改原本显示控制器类名称的位置的文本;
通常建议在配置的tags属性值上添加序号,例如:“01. 用户模块”、“02. 微博模块”,则框架会根据值进行排序。
// 1. UserController
@Api(tags = "01.用户管理模块")
public class UserController {...}
添加在控制器类中处理请求的方法上的注解;
用于配置此方法处理的请求在API文档中显示的文本。
@ApiOperation(value = "注册功能")
@PostMapping("reg")
public int reg(@RequestBody UserRegDTO userRegDTO){...}
添加在处理请求的方法的参数上;
用于表示API文档框架应该忽略此参数。
// 参数中添加@ApiIgnore注解
public int insert(@RequestBody WeiboDTO weiboDTO, @ApiIgnore HttpSession session){...}
是添加在POJO类的属性上的注解;
用于对请求参数或响应结果中的某个属性进行说明;
主要通过其value属性配置描述文本,并可通过example属性配置示例值。
注意:如果配置了 required=true,只是一种显示效果,Knife4j框架并不具备检查功能
@Data
public class UserRegDTO {
@ApiModelProperty(value = "用户名", required = true, example = "赵丽颖")
private String username;
@ApiModelProperty(value = "密码", required = true)
private String password;
@ApiModelProperty(value = "昵称", required = true)
private String nickname;
}
添加在控制器类中处理请求的方法上的注解;
主要用于配置非封装的参数
注意:一旦使用此注解,各个参数的数据类型默认都会显示String,可以通过dataType指定数据类型
@ApiImplicitParam(name = "id", value = "微博", required=true, dataType = "int")
public WeiboDetailVO selectById(int id){...}
添加在控制器类中处理请求的方法上的注解;
当方法有多个非封装的参数时,在方法上添加此注解,并在注解内部通过@ApiImplicitParam数组配置多个参数。
此处以微博详情功能为例
/**微博详情页功能*/
@GetMapping("selectById")
@ApiOperation(value = "微博详情功能")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "id", value = "微博", required=true, dataType = "int"),
@ApiImplicitParam(name = "username", value = "用户名", required=true)
})
// 额外增加username参数,仅仅用于测试
public WeiboDetailVO selectById(int id, String username){
return weiboMapper.selectById(id);
}
为什么需要统一响应结果处理
在实际开发中,我们往往需要在多个控制器方法中返回相同的响应结构;
例如,统一返回接口调用成功的状态码、提示信息、以及请求结果数据。但是,如果对于每个接口都单独进行处理的话,不仅逻辑复杂,而且容易出现疏漏,进而增加前端调用的难度。
所以为了更好定义服务端返回值的格式,统一客户端对服务端响应结果的处理,我们需要将服务端返回到客户端的数据再次进行封装。
异常处理器我们已经能够使用了,那么在咱们的项目中该如何来处理异常呢?
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
将异常分类以后,针对不同类型的异常,要提供具体的解决方案
名称 | @RestControllerAdvice |
类型 | ==类注解== |
位置 | Rest风格开发的控制器增强类定义上方 |
作用 | 为Rest风格开发的控制器类做增强 |
说明:此注解自带@ResponseBody注解与@Component注解,具备对应的功能
名称 | @ExceptionHandler |
类型 | ==方法注解== |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
当某个Controller方法中出现了异常,系统底层就会查找有没有定义全局异常处理对象。 这个全局异常处理对象中有没有定义对应的异常处理方法,假如有就调用此方法处理异常。假如没有,会查找对应异常的父类异常处理方法。
全局异常处理器是Spring MVC框架中的一种异常处理机制,用于统一?处理由控制器抛出的异常。
全局异常处理器可以帮助我们捕获和处理控制器中的异常,并且可以根据不同的异常类型进行不同的处理操作,从而保障应用的健壮性和稳定性。
当然,Spring MVC中有内置的异常处理对象,但是呈现的结果对于用户端不友好,所以实际项目我们一般会使用全局异常处理器处理异常。
Spring MVC中的全局异常处理器可以通过以下方式进行配置:
1)创建全局异常处理器类
工程目录下创建?exception.GlobalExceptionHandler
定义全局异常处理器,处理Controller中抛出的异常。
复合注解,是@ControllerAdvice注解和@ResponseBody注解的组合;
用于捕获Controller中抛出的异常并对异常进行统一的处理,还可以对返回的数据进行处理。
2)创建异常处理方法
在异常处理方法上添加?@ExceptionHandler?注解
用于捕获Controller处理请求时抛出的异常,并进行统一的处理。
关于Throwable
在开发实践中,通常会添加一个处理Throwable的方法,它将可以处理所有类型的异常,则不会再出现500错误!
GlobalExceptionHandler中添加处理 Throwable 的方法
在实际项目我们需要对客户端传递到服务端的参数进行校验,用于判定请求参数的合法性,假如请求参数不合法则不可以再去执行后续的业务了。那如何校验呢?
第一种方式是我们在控制层方法中每次都自己进行参数有效值的判断,不合法可以抛出异常,但是工作量和代码复杂度会比较高;
第二种方式就是采用市场上主流的?Spring Validation?框架去实现校验,所以?Spring Validation?框架的主要作用是 检查参数的基本有效性。
客户端和服务器端都要做。
流程
@ExceptionHandler
public JsonResult doHandleXxxx(MethodArgumentNotValidException ex){
String message = ex.getFieldError().getDefaultMessage();
return new JsonResult(StatusCode.VALIDATED_ERROR, message);
}
可以添加在类上,也可以添加在参数上,参数验证注解。
要求不能为null
要求不能为空字符串,同时不能为null
要求不能为空白串,同时不能为空字符串,也不能为null
作用于字符串类型;
限定字符串长度范围:@Size(min=x, max=x, message=x)
作用在数值类型;
限定数值类型的范围:@Range(min=x, max=x, message=x)
使用正则表达式进行验证:@Pattern(regexp='正则表达式', message='提示消息')
在 Spring Validation 中,除了对 POJO(Plain Old Java Object)进行校验的功能外,还支持对非 POJO 进行校验,比如 String、Integer、Double 等类型的参数。
使用流程
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行, 他是是?SpringMVC?提供的一个组件,它允许我们在请求到达处理方法之前或之后,对请求拦截并进行预处理或后处理。拦截器可以帮助我们实现许多功能,如用户权限验证、记录日志、处理异常等。
?
(1)浏览器发送一个请求会先到Tomcat的web服务器
(2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
(3)如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问
(4)如果是动态资源,就需要交给项目的后台代码进行处理
(5)在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行
(6)然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截
(7)如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果
(8)如果不满足规则,则不进行处理
(9)这个时候,如果我们需要在每个Controller方法执行的前后添加业务,具体该如何来实现?
这个就是拦截器要做的事。
?
拦截器和过滤器之间的区别是什么?
@Override
//原始方法调用前执行的内容
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}
@Override
//原始方法调用后执行的内容
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
//原始方法调用完成后执行的内容
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
拦截器中的preHandler方法,如果返回true,则代表放行,会执行原始Controller类中要请求的方法,如果返回false,则代表拦截,后面的就不会再执行了
拦截器执行的顺序是和配置顺序有关。就和前面所提到的运维人员进入机房的案例,先进后出。
?
preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行。
这个顺序不太好记,最终只需要把握住一个原则即可:==以最终的运行结果为准==
?
/**
* 注册浏览器添加规则
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/** 拦截所有请求 */
//registry.addInterceptor(new MyIntercepter());
/** 拦截指定请求 */
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/users/login");
/** 拦截指定路径下所有请求 */
//registry.addInterceptor(new MyInterceptor())
// .addPathPatterns("/users/**");
/** 排除指定的请求 [拦截指定路径下除了指定请求之外的所有请求] */
// registry.addInterceptor(new MyInterceptor())
// .addPathPatterns("/users/**")
// .excludePathPatterns("login");
}
}