目录
- 1.SpringMVC介绍
- 1.1SpringMVC涉及组件
- 2.SpringMVC接收数据
- 2.1 访问路径设置
- 地址的表示方式
- 添加@RequestMapping的位置
- 请求方式指定
- 2.2接收参数
- json和param参数
- param参数接收
- json参数接收
- 接收cookie数据
- 原生对象获取
- 共享域对象操作
- 3.SpringMVC响应数据
- 3.1 handler方法分析
- handler方法的作用
- 3.2 页面跳转控制
- 快速返回模板视图
- 3.3 请求转发和响应重定向实现
- 3.4 返回json数据
- 3.5 静态资源处理
- 4.RESTful
- 4.1 介绍
- 4.2 restful特点
- 5.全局异常处理机制
- 5.1 关于异常处理
- 5.2 声明式异常的步骤
- 6.拦截器
- 6.1 拦截器的使用
- 6.2 指定地址拦截
- 6.3 排除拦截
- 6.4 多个拦截器执行顺序
- 7.参数校验
- 7.1 校验的步骤
1.SpringMVC介绍
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc
),但它通常被称为“Spring MVC”。
1.1SpringMVC涉及组件
- DispatcherServlet : SpringMVC提供,需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发
- HandlerMapping : SpringMVC提供,需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler
- HandlerAdapter : SpringMVC提供,需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器
- Handler : handler又称处理器,它是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果
- ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器。所以,视图解析器,相对其他的组件不是必须的
SpringMVC案例:
控制层:
@Controller
public class HelloController {
@RequestMapping("spring/hello") // 对外访问的地址 此注解也会进行在handlerMapping注册的操作
@ResponseBody // 不用找视图解析器
//每个方法就是一个handler方法,最后注册到handlerMapping中并且指定访问地址
public String hello() {
System.out.println("hello,springmvc");
return "hello springmvc";
}
}
配置类:
/**
* 要配置的操作:
* 1.将controller配置到ioc容器
* 2.将handlerMapping和handlerAdapter加入到ioc容器
*/
@Configuration
@ComponentScan("com.ergou.controller")
public class MvcConfig {
@Bean
public RequestMappingHandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
}
初始化的类:
/**
* 可以被web项目加载,会初始化容器,会设置dispatcherServlet的地址
*/
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
// 设置我们项目对应的配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { MvcConfig.class };
}
// 配置springmvc内部自带的servlet访问地址
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
2.SpringMVC接收数据
2.1 访问路径设置
@RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。
将地址写在RequestMapping注解的value属性中即可,同样只有value属性时,value=可以省略
@RequestMapping注解中的地址不是必须斜杆开头,可有可无
地址的表示方式
-
精准地址
- 例:/user/login
- 如果有多个地址,就用{}括号括起来,多个地址之间用逗号隔开,例如:@RequestMapping({”地址一”,”地址二”,……}
-
模糊地址
- *表示一层的任意字符串,**表示任意层的任意字符串
- 例:/user/* ,*处的一层的字符串可以是任何字符串
- 例:/user/** ,**处可以有多层字符串,每层的字符串任意
添加@RequestMapping的位置
类上和方法上添加@RequestMapping的区别:
类上提取公共的访问地址,方法上是具体的handler地址
访问:类地址 + 方法地址即可
请求方式指定
handler最终是被客户端访问,客户端先访问到是DispatcherServlet最后访问到handler。
客户端 -> http(get / post / put / delete) -> dispatcherServlet -> handler
默认情况:@RequestMapping(“login”) 主要地址正确,任何请求方式都可以访问
指定请求方式如下:
不符合请求方式会出现405异常!
例:
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping(value = "login", method = RequestMethod.POST) // 只允许使用post方式的请求
public String login() {
return null;
}
@RequestMapping(value = "register", method = {RequestMethod.GET, RequestMethod.POST}) // 可以使用get方式的请求,也可以使用post方式的请求
public String register() {
return null;
}
}
注解进阶:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
作用是这些注解比起RequestMapping,直接指定了请求的方式
只能使用在方法上
以@GetMapping为例:
@GetMapping(value = "exit")
等同于
@RequestMapping(value = "exit",method = RequestMethod.GET)
2.2接收参数
json和param参数
param参数:由key-value形式传递参数,key和value之间用=连接,多个key-value数据之间用&连接,只支持简单数据类型的数据,不支持数据的嵌套,形式:key=value&key=value&……
json参数:也是由key-value形式传递参数,key和value之间用 : 连接,多个key-value数据之间使用逗号隔开,整体是由一个{}括起来,如果一个value中又包括其他key-value数据,可以使用{}表示此value,{}中写其中的key-value数据,即json参数支持数据的嵌套,json支持复杂数据类型,比如数组、集合、对象等。形式:
{key:value,key:{key:value,key,value},key:……}
param参数接收
- 直接接收
在handler方法的形参列表中,形参的命名和请求的参数名一致,相应的请求参数即会直接传给其同名的形参
@Controller
@RequestMapping("param")
public class ParamController {
@RequestMapping("data")
@ResponseBody
public String data(String name, int age) {
System.out.println("name = " + name + ", age = " + age);
return "name = " + name + ", age = " + age;
}
}
- 注解指定
@RequestParam注解加在形参前,可以指定任意的请求参数名,可以要求参数必须传递或非必须传递,还可以指定不必须传递时设置形参的默认值
@RequestParam注解的属性:
- value:指定形参要接收的参数的参数名
- required:指定形参是否必须传递,true为必须传递,false为非必须传递,默认为true
- defaultValue:非必须传递时设置,当没有请求参数传递过来时,形参的值默认为该属性值
非必须传递时要用defaultValue属性设置默认值,如果形参没有接收到请求参数的数据,会报400异常
例:
@RequestMapping(value = "data1", method = RequestMethod.GET)
@ResponseBody
public String data1(
@RequestParam(value = "account") String username,
@RequestParam(required = false, defaultValue = "1") int age
) {
System.out.println("username = " + username + ", age = " + age);
return "username = " + username + ", age = " + age;
}
-
特殊参数值
-
一个key值有多个value
- 若param参数中有两个key值相同value值不相同的参数,可以直接使用集合来接收其参数值
- 设置集合类型的形参时,要在此形参前加上@RequestParam注解,如果不加,会将字符串直接赋值给集合,加了此注解后,才会将多个参数依次加入集合中
-
@RequestMapping(value = "data2", method = RequestMethod.GET)
@ResponseBody
// 若要传递的参数为:hbs=singing&hbs=dancing&hbs=rap&hbs=basketball
public String data2(@RequestParam List<String> hbs) {
System.out.println(hbs);
return hbs.toString();
}
// 客户端收到的结果为[singing, dancing, rap, basketball]
使用实体对象接收参数值
准备一个用来接收请求参数的有对应属性和其get、set方法的实体类,要保证请求参数名和实体类的属性名对应一致,如果要默认值,直接在实体类中给要设默认值的属性赋值即可
形参列表中写一个此实体类类型的形参即可
@RequestMapping(value = "data3", method = RequestMethod.GET)
@ResponseBody
public String data3(User user) {
System.out.println("user = " + user);
return user.toString();
}
-
路径参数接收
-
路径传参指的是直接将要传递的值写在路径中,即直接将value写在路径中,例如,要将用户名为ergou,密码为1234的数据传递给handler,可以写作:user/login/ergou/1234
-
路径参数接收步骤:
- 设置动态路径:
在@RequestMapping注解的value属性中,在要用来接收参数的路径处写上{},在{}中写上该参数的参数名(以方便handler方法调用)即可,例:
@GetMapping("login/{username}/{password}")
- 接收动态路径参数
在handler方法的形参列表中写上相应的形参,形参名要和动态路径的{}中的参数名对应一致,且要在形参前加上@PathVariable注解
- @PathVariable,如果形参名和动态路径中的参数名不一致,可以用value的属性值指定形参要接收的动态路径中的参数(写入要接收的动态参数的参数名)
-
-
例:
@Controller
@RequestMapping("param")
public class ParamController {
/**
* 前端请求: http://localhost:8080/param/value?name=xx&age=18
*
* 可以利用形参列表,直接接收前端传递的param参数!
* 要求: 参数名 = 形参名
* 类型相同
* 出现乱码正常,json接收具体解决!!
* @return 返回前端数据
*/
@GetMapping(value="/value")
@ResponseBody
public String setupForm(String name,int age){
System.out.println("name = " + name + ", age = " + age);
return name + age;
}
}
json参数接收
接收json参数时,首先要准备一个接收json参数的实体类,其中的属性和参数一一对应。在形参列表中,写一个此实体类类型的形参,并在此形参前加上@RequestBody注解(加上此注解后就可以接收json参数)
@RequestBody 注解可以将 JSON 数据转换为 Java 对象。
@Controller
@RequestMapping("json")
@ResponseBody
public class JsonController {
@PostMapping("data")
public String data(@RequestBody Person person) {
System.out.println("person = " + person);
return person.toString();
}
}
除此之外,还要项目导入json处理的依赖,以及给handlerAdapter配置json转化器
导入json处理依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
给handlerAdapter配置json转化器:
在配置类上加上@EnableWebMvc注解即可
@EnableWebMvc // 此注解会给handlerAdapter配置json转化器
@Configuration
@ComponentScan({"com.ergou.param", "com.ergou.path", "com.ergou.json"})
public class MvcConfig {
@Bean
public RequestMappingHandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
}
@EnableWebMvc注解的作用:
此注解会自动将RequestMappingHandlerMapping和RequestMappingHandlerAdapter加入ioc容器中,除此之外还会配置json转化器。
所以在配置类上写好@EnableWebMvc注解后,可以省略RequestMappingHandlerMapping和RequestMappingHandlerAdapter的使用@Bean注解进行ioc配置的操作。
/**
* 要配置的操作:
* 1.将controller配置到ioc容器
* 2.将handlerMapping和handlerAdapter加入到ioc容器
*/
@EnableWebMvc //此注解会给handlerAdapter配置json转化器
@Configuration
@ComponentScan("com.yhd.json")
public class MvcConfig{
/*
@Bean
public HandlerMapping handlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public HandlerAdapter handlerAdapter(){
return new RequestMappingHandlerAdapter();
}
*/
}
接收cookie数据
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它可以在浏览器下次向同一服务器再发起请求时被携带上并发送到服务器上。这些数据可以包含用户的偏好设置、登录状态等信息,使得服务器能够识别不同的用户,为用户提供个性化的服务。
在handler方法参数列表中的要接收cookie数据的形参前加上@CookieValue注解,可以在其中的value属性中指定要接收的cookie数据的名称
@Controller
@RequestMapping("cookie")
@ResponseBody
public class CookieController {
@RequestMapping(value = "data", method = RequestMethod.GET)
public String data(@CookieValue("cookie") String value) {
System.out.println("value = " + value);
return value;
}
@GetMapping("save")
public String save(HttpServletResponse response) {
Cookie cookie = new Cookie("cookie", "root");
response.addCookie(cookie);
return cookie.toString();
}
}
原生对象获取
在 Spring MVC 中,原生对象指的是与底层 Servlet API 直接相关的对象,这些对象是 Servlet 规范定义的,Spring MVC 作为基于 Servlet 的 Web 框架,允许开发者在控制器方法中直接使用这些对象来处理 HTTP 请求和响应
在形参中加上相应的注解或直接使用某些实体类的形参,可以获取对应的一些原生对象
Controller method argument 控制器方法参数 | Description |
---|---|
jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse | 请求/响应对象 |
jakarta.servlet.http.HttpSession | 强制存在会话。因此,这样的参数永远不会为 null 。 |
java.io.InputStream, java.io.Reader | 用于访问由 Servlet API 公开的原始请求正文。 |
java.io.OutputStream, java.io.Writer | 用于访问由 Servlet API 公开的原始响应正文。 |
@PathVariable | 接收路径参数注解 |
@RequestParam | 用于访问 Servlet 请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。 |
@RequestHeader | 用于访问请求标头。标头值将转换为声明的方法参数类型。 |
@CookieValue | 用于访问Cookie。Cookie 值将转换为声明的方法参数类型。 |
@RequestBody | 用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。 |
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap | 共享域对象,并在视图呈现过程中向模板公开。 |
Errors, BindingResult | 验证和数据绑定中的错误信息获取对象! |
共享域对象操作
获取共享域对象:
获取request共享域对象和session共享域对象:在形参列表处声明HttpServletRequest类型的对象和HttpSession类型的对象即可
获取ServletContext共享域对象:在Controller层的类中声明ServletContext类型的属性,在其上方加上@Autowired注解即可
3.SpringMVC响应数据
3.1 handler方法分析
一个controller层的方法是控制层的一个处理器,称之为handler
handler方法需要使用@RequestMapping或@GetMapping等一系列注解,声明路径,以在HandlerMapping中注册,供DS查找
handler方法的作用
- 接收请求参数(param、json、pathvariable、共享域等)
- 调用业务逻辑
- 响应前端数据(页面、json、转发和重定向等)
3.2 页面跳转控制
快速返回模板视图
jsp视图解析器的配置
依赖导入
<!-- jsp需要依赖! jstl -->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
准备jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%-- ${data}用来获取共享域中名为data的共享数据 --%>
<font color="red">${data}</font>
</body>
</html>
配置类:
/**
* mvc组件的配置类
*/
@Configuration
@ComponentScan("com.ergou")
@EnableWebMvc
// WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// registry可以快速添加前后缀
registry.jsp("/WEB-INF/views/", ".jsp");
}
}
handler方法:
@Controller
@RequestMapping("jsp")
public class JspController {
/**
* 快速查找视图
* 1.方法的返回值是字符串类型
* 2.不能添加@ResponseBody注解,这个注解直接返回视图给浏览器,不找视图,不走视图解析器
* 3.返回值为对应的中间视图名称
* @return
*/
@GetMapping("index")
public String index(HttpServletRequest request) {
request.setAttribute("data", "hello,jsp");
return "index";
}
}
3.3 请求转发和响应重定向实现
-
请求转发的步骤
- handler方法的返回值类型为String类型
- 不添加@ResponseBody
- 返回的字符串写要转发的地址,并在地址前加上
forward:
例:
// 请求转发
@GetMapping("forward")
public String forward() {
System.out.println("请求转发");
return "forward:/jsp/index";
}
-
响应重定向的步骤
- handler方法的返回值类型为String类型
- 不添加@ResponseBody
- 返回的字符串写要响应重定向的地址,并在地址前加上
redirect:
例:
// 响应重定向
@GetMapping("redirect")
public String redirect() {
System.out.println("响应重定向");
// 重定向项目下的路径
return "redirect:/jsp/index";
}
// 响应重定向
@GetMapping("redirect2")
public String redirect2() {
System.out.println("响应重定向");
// 重定向项目外的路径
return "redirect:http://www.baidu.com";
}
路径细节:
- 请求转发是项目下的资源跳转。路径要写为项目下的地址,忽略项目根路径
- 响应重定向是可以是项目下也可以是项目外的地址,在访问项目下的地址时不能忽略项目根路径
- 但是在springmvc下,响应重定向到项目下的地址时,springmvc会自动为其项目下的地址补上项目根路径
转发显示的是第一个资源的地址不会跳,重定向显示的是最后一个资源的地址
3.4 返回json数据
准备:导入json的依赖,并使用@EnableWebMvc注解写在配置类上
步骤:
- 将返回值类型设置为目标类型
- 在handler方法上方加上@ResponseBody注解,表示不找视图解析器,直接放入请求体,可以加在类上,表示该类下的所有handler方法都加上@ResponseBody注解
- 直接返回目标类型的对象即可,handlerAdapter会自动将其对象转化为json字符串。如果返回的是集合,同样会将其集合转化为json字符串
例:
@Controller
@RequestMapping("json")
public class JsonController {
@GetMapping("data")
@ResponseBody
public User data() {
User user = new User();
user.setName("ergou");
user.setAge(19);
return user;
}
@GetMapping("data2")
@ResponseBody
public List<User> data2() {
User user1 = new User();
user1.setName("Tom");
user1.setAge(20);
User user2 = new User();
user2.setName("Tony");
user2.setAge(17);
User user3 = new User();
user3.setName("Rose");
user3.setAge(18);
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
return users;
}
}
拓展:@RestController=@Controller+@ResponseBody
3.5 静态资源处理
准备:配置类要实现WebMvcConfigurer接口
在配置类中实现configureDefalutServletHandling方法,调用参数configurer的enable方法即可
这样在dispatcherServlet通过一个路径去handlerMapping中找对应的handler方法没找到时,就会继续通过此路径去找静态资源
/**
* mvc组件的配置类
*/
@Configuration
@ComponentScan("com.ergou")
@EnableWebMvc
// WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// registry可以快速添加前后缀
registry.jsp("/WEB-INF/views/", ".jsp");
}
// 开启动态资源处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
4.RESTful
4.1 介绍
RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量级的通信协议,广泛应用于现代的Web服务开发。
通过遵循 RESTful 架构的设计原则,可以构建出易于理解、可扩展、松耦合和可重用的 Web 服务。RESTful API 的特点是简单、清晰,并且易于使用和理解,它们使用标准的 HTTP 方法和状态码进行通信,不需要额外的协议和中间件。
总而言之,RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格,用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序
restful–>更好地设计路径、设计参数传递、选择请求方式
4.2 restful特点
-
每一个URI代表1种资源(URI 是名词);
-
客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;222
-
资源的表现形式是XML或者JSON;
-
客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
-
HTTP协议请求方式要求
REST 风格主张在项目设计、开发过程中,具体的操作符合HTTP协议定义的请求方式的语义。
操作 | 请求方式 |
---|---|
查询操作 | GET |
保存操作 | POST |
删除操作 | DELETE |
更新操作 | PUT |
-
URL路径风格要求
REST风格下每个资源都应该有一个唯一的标识符,例如一个 URI(统一资源标识符)或者一个 URL(统一资源定位符)。资源的标识符应该能明确地说明该资源的信息,同时也应该是可被理解和解释的
使用URL+请求方式确定具体的动作,也是一种标准的HTTP协议请求
操作 | 传统风格 | REST 风格 |
---|---|---|
保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST |
删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE |
更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT |
查询 | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET |
-
总结
根据接口的具体动作,选择具体的HTTP协议请求方式
路径设计从原来携带动标识,改成名词,对应资源的唯一标识即可
注:
在 RESTful API 的设计中,路径和请求参数和请求体都是用来向服务器传递信息的方式。
- 对于查询用户详情,使用路径传递参数是因为这是一个单一资源的查询,即查询一条用户记录。使用路径参数可以明确指定所请求的资源,便于服务器定位并返回对应的资源,也符合 RESTful 风格的要求。
- 而对于多条件模糊查询,使用请求参数传递参数是因为这是一个资源集合的查询,即查询多条用户记录。使用请求参数可以通过组合不同参数来限制查询结果,路径参数的组合和排列可能会很多,不如使用请求参数更加灵活和简洁。
此外,还有一些通用的原则可以遵循:
- 路径参数应该用于指定资源的唯一标识或者 ID,而请求参数应该用于指定查询条件或者操作参数。
- 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。
- 对于敏感信息,最好使用 POST 和请求体来传递参数。
例:
@Controller
@ResponseBody
@RequestMapping("user")
public class UserController {
@GetMapping
//@RequestParam设置默认值
public List<User> page(
@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false, defaultValue = "10") int size
) {
System.out.println("page = " + page + ", size = " + size);
return null;
}
@PostMapping
// 接收json数据使用@RequestBody注解在形参前
public User save(@RequestBody User user) {
return user;
}
@GetMapping("{id}")
//想接受路径参数必须使用@PathVariable注解,默认写的变量都是接param
public User query(@PathVariable Integer id) {
return null;
}
@PutMapping
public User update(@RequestBody User user) {
return user;
}
@GetMapping("search")
public List<User> search(
String keywork,
@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false, defaultValue = "10") int size
) {
return null;
}
}
5.全局异常处理机制
5.1 关于异常处理
对于异常的处理,一般分为两种方式:
- 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
- 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如
@Throws
或@ExceptionHandler
),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。
5.2 声明式异常的步骤
- 声明异常处理控制器类
- 在此类上加上指定注解,@RestControllerAdvice,@RestControllerAdvice等同于@ControllerAdvice注解加上@ResponseBody注解。全局异常发生时,会走此类写的handler方法。@ControllerAdvice 代表当前类的异常处理controller,@ResponseBody注解代表当前类的handler方法直接返回异常的json字符串。
- 声明异常处理handler方法
- 在类中创建handler方法,返回值类型为Object,在此方法上方加上@ExceptionHandler注解,在此注解中指定此方法处理的异常类型(写入异常类型的class实例,如果有多个,就用{}括号括起来),获取异常对象只要在形参列表中写一个对应异常类型的形参,使用其形参即可。
- 如果找异常handler方法时,没找到处理对应异常类型的handler方法,就会找处理其父类异常类型的handler方法。
注:要把异常处理控制器的类的包也加入ioc的包扫描下,才能生效
例:
//@ControllerAdvice//可以返回逻辑视图 转发和重定向
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ArithmeticException.class, IOException.class})
public Object arithmeticExceptionHandler(ArithmeticException e) {
//自定义处理异常即可handler
String message = e.getMessage();
System.out.println(message);
return message;
}
@ExceptionHandler(Exception.class)
public Object exceptionHandler(Exception e) {
//自定义处理异常即可handler
String message = e.getMessage();
System.out.println(message);
return message;
}
}
6.拦截器
拦截器比起过滤器,过滤器能够拦截到的最大范围是整个 Web 应用,拦截器能够拦截到的最大范围是整个 SpringMVC 负责的请求。过滤器能处理的没有拦截器能处理的细致,通常能用拦截器就使用拦截器。
6.1 拦截器的使用
- 写拦截类
创建一个类,这个类要实现HandlerInterceptor接口
在拦截类中重写拦截方法,可重写的拦截方法分为三种:
- preHandler方法:在目标handler方法之前执行
- postHandler方法:目标handler方法之后执行,若目标handler方法报错,则不会执行postHandler方法
- afterCompletion方法:最后执行的方法,即dispatcherServlet要将响应返回给客户端之前执行
preHandler拦截方法的参数和返回值:
- 参数:request,代表请求对象
- 参数:response,代表响应对象
- 参数:handler,代表要调用的handler方法的对象
- 返回值:返回的结果为true则放行,返回的结果为false则拦截不放行
postHandler拦截方法没有返回值,为void返回值类型的方法。相比preHandler方法的参数,参数多了一个modelAndView,代表着返回的视图和共享域对象。
afterCompletion方法也没有返回值,为void返回值类型的方法。相比preHandle方法的参数,参数多了一个ex对象,代表异常信息的异常对象
在拦截方法中写具体的拦截的逻辑代码即可
public class MyInterceptor implements HandlerInterceptor {
//在执行handler之前,调用拦截方法
//编码格式设置,登录保护,权限处理...
/**
*
* @param request 请求对象
* @param response 响应对象
* @param handler handler就是我们要调用的方法对象
* @return true放行 false拦截
* @throws Exception
*/
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
return true;
}
/**
* 当handler执行完毕后,触发的方法。没有拦截机制
* 此方法只有 preHandle return ture 触发
*
* 对结果处理 敏感词汇检查
*
* @param request 请求对象
* @param response 响应对象
* @param handler handler方法
* @param modelAndView 返回的视图和共享域数据对象
* @throws Exception
*/
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
// 渲染视图之后执行(最后),一定执行!
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
- 配置拦截类
在配置类中重写addInterceptors方法(配置类实现了WebMvcConfigurer接口前提下),再调用registry参数的addInterceptor方法,在参数中传入拦截类的实例对象即可(可以直接在参数列表new一个拦截器的实例对象)
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置方案一:拦截全部请求
registry.addInterceptor(new MyInterceptor());
}
6.2 指定地址拦截
在调用完addInterceptor方法后,再接着在后面调用addPathPattern方法,在addPathPattern方法参数列表中写要指定拦截的路径的字符串即可,可以使用模糊路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new MyInterceptor());
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/user/data");
}
6.3 排除拦截
在全部拦截或指定地址拦截后,再接着再后面调用excludePathPatterns方法,在excludePathPattern方法参数列表中写入要排除拦截的路径的字符串即可,可以使用模糊路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new MyInterceptor());
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/user/**")
.excludePathPatterns("/user/data1");
}
6.4 多个拦截器执行顺序
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
7.参数校验
与参数校验有关的注解:(来自jsr303)
将其注解写在接收前端的数据的相关的实体类的属性上即可
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
注:
1.@NotNull (包装类型不为null)
@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。
3.@NotEmpty (集合类型长度大于0)
@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。
3.@NotBlank (字符串,不为null,且不为" "字符串)
@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
7.1 校验的步骤
- 在相应的注解上加上指定的校验注解
例:
@Data
public class User {
@NotBlank
private String name;
@Length(min = 6)
private String password;
@Min(1)
private int age;
@Email
private String email;
@Past
private Date birthday;
}
- 在控制层的handler方法使用其实体类接收请求的数据时,在参数列表的其实体类类型的参数前,要加上@Validated注解(使校验注解生效)
@PostMapping("register")
public User register(@Validated @RequestBody User user) {
System.out.println("user = " + user);
return user;
}
- 设置错误绑定信息的捕捉,在handler方法的参数列表中,在要校验的对象后加上一个BindingResult类型的对象(BindingResult类型的对象必须跟在要校验的对象后面),在handler方法中加上一个if(result.hasErrors()){ }的条件语句,在条件语句中写发生错误绑定信息后的逻辑处理的代码
例:
@PostMapping("register")
public Object register(@Validated @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
Map<String, Object> data = new HashMap<>();
data.put("code", 400);
data.put("msg", "参数校验异常");
return data;
}
System.out.println("user = " + user);
return user;
}