SpringMVC学习记录(五)之SpringMVC其他扩展
SpringMVC学习记录(五)之SpringMVC其他扩展
- 一、全局异常处理机制
- 1、异常处理的两种方式
- 2、基于注解的异常声明和处理
- 1)声明异常处理控制器类
- 2)声明异常处理handler方法
- 3)配置文件扫描控制器类配置
- 二、拦截器
- 1、拦截器的概念
- 2、拦截器的使用
- 1)创建拦截器
- 2)添加拦截器到配置类
- 3)精准拦截配置
- 4)排除法拦截
- 5) 多个拦截器执行顺序
- 三、参数校验
- 1、校验概述
- 2、校验实现
- 3、“参数不能为空”的区分
一、全局异常处理机制
1、异常处理的两种方式
对于异常的处理,一般分为两种方式:
- 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
- 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如
@Throws
或@ExceptionHandler
),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。
站在宏观角度来看待声明式事务处理:
-
整个项目从架构这个层面设计的异常处理的统一机制和规范。
-
一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。
使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!
2、基于注解的异常声明和处理
1)声明异常处理控制器类
异常处理控制类,统一定义异常处理handler方法。需要在异常处理控制器类上加入@ControllerAdvice
注解,并且由于这也是一个Controller类,如果内部的handler方法都不需要进行视图解析的话,需要像普通Controller类一样加入@ResponseBody
注解。
这两个注解可以用一个@RestControllerAdvice
代替。
@RestControllerAdvice
= @ControllerAdvice
+ @ResponseBody
/*** projectName: com.atguigu.execptionhandler* * description: 全局异常处理器,内部可以定义异常处理Handler!*//*** @RestControllerAdvice = @ControllerAdvice + @ResponseBody* @ControllerAdvice 代表当前类的异常处理controller! */
@RestControllerAdvice
public class GlobalExceptionHandler {}
2)声明异常处理handler方法
异常处理handler方法和普通的handler方法参数接收和响应都一致!
只不过异常处理handler方法要映射异常,发生对应的异常会调用!
普通的handler方法要使用@RequestMapping注解映射路径,发生对应的路径调用!
异常处理的handler方法上要添加@ExceptionHandler
注解,并且括号内部对应异常的class类。
当捕获到异常时,会去找对应的异常处理handler,如果没有定义相应的异常,会去找这个异常的父类,或者说是比这个异常更大的异常,比如抛出了NullPointerException空指针异常,但是并没有定义,这时候会去找比它更大的异常,比如Exception。
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 异常处理handler * @ExceptionHandler(HttpMessageNotReadableException.class) * 该注解标记异常处理Handler,并且指定发生异常调用该方法!* * @param e 获取异常对象!* @return 返回handler处理结果!*/@ExceptionHandler(HttpMessageNotReadableException.class)public Object handlerJsonDateException(HttpMessageNotReadableException e){return null;}/*** 当发生空指针异常会触发此方法!* @param e* @return*/@ExceptionHandler(NullPointerException.class)public Object handlerNullException(NullPointerException e){return null;}/*** 所有异常都会触发此方法!但是如果有具体的异常处理Handler! * 具体异常处理Handler优先级更高!* 例如: 发生NullPointerException异常!* 会触发handlerNullException方法,不会触发handlerException方法!* @param e* @return*/@ExceptionHandler(Exception.class)public Object handlerException(Exception e){return null;}}
3)配置文件扫描控制器类配置
确保异常处理控制类被扫描
<!-- 扫描controller对应的包,将handler加入到ioc-->@ComponentScan(basePackages = {"com.atguigu.controller", "com.atguigu.exceptionhandler"})
二、拦截器
1、拦截器的概念
拦截器 Springmvc VS 过滤器 javaWeb:
- 相似点
- 拦截:必须先把请求拦住,才能执行后续操作
- 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
- 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
- 不同点
- 工作平台不同
- 过滤器工作在 Servlet 容器中
- 拦截器工作在 SpringMVC 的基础上
- 拦截的范围
- 过滤器:能够拦截到的最大范围是整个 Web 应用
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
- 执行顺序不同
- 过滤器:过滤器是servlet容器接收到请求但还未调用servlet之前执行的
- 拦截器:拦截器是servlet被调用但是响应还未返回给客户端之前执行的
- IOC 容器支持
- 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持
- 工作平台不同
功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。
拦截器拦截的位置:handler方法执行之前、handler方法执行之后、DispatcherServlet响应之后。
2、拦截器的使用
1)创建拦截器
public class Process01Interceptor implements HandlerInterceptor {// if( ! preHandler()){return;}// 在处理请求的目标 handler 方法前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);System.out.println("Process01Interceptor.preHandle");// 返回true:放行// 返回false:不放行return true;}// 在目标 handler 方法之后,handler报错不执行!@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);System.out.println("Process01Interceptor.postHandle");}// 渲染视图之后执行(最后),一定执行!@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);System.out.println("Process01Interceptor.afterCompletion");}
}
2)添加拦截器到配置类
添加拦截器(拦截全部):
public void addInterceptors(InterceptorRegistry registry) { //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求registry.addInterceptor(new Process01Interceptor());}
配置类的代码:
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.atguigu.controller","com.atguigu.exceptionhandler"}) //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {//配置jsp对应的视图解析器@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {//快速配置jsp模板语言对应的registry.jsp("/WEB-INF/views/",".jsp");}//开启静态资源处理 <mvc:default-servlet-handler/>@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}//添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) { //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求registry.addInterceptor(new Process01Interceptor());}
}
3)精准拦截配置
@Override
public void addInterceptors(InterceptorRegistry registry) {//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求registry.addInterceptor(new Process01Interceptor());//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可//addPathPatterns("/common/request/one") 添加拦截路径//也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
}
4)排除法拦截
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {//排除匹配,排除应该在匹配的范围内排除//addPathPatterns("/common/request/one") 添加拦截路径//excludePathPatterns("/common/request/tow"); 排除路径,排除应该在拦截的范围内registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow").excludePathPatterns("/common/request/tow");
}
5) 多个拦截器执行顺序
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个preHandle() 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
三、参数校验
在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
1、校验概述
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
注解 | 规则 |
---|---|
@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) | 标注值必须符合指定的正则表达式 |
JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
注解 | 规则 |
---|---|
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 @EnableWebMvc
的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。
配置 @EnableWebMvc
后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated
注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。
2、校验实现
- 1)导入依赖
<!-- 校验注解 -->
<dependency><groupId>jakarta.platform</groupId><artifactId>jakarta.jakartaee-web-api</artifactId><version>9.1.0</version><scope>provided</scope>
</dependency><!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator-annotation-processor</artifactId><version>8.0.0.Final</version>
</dependency>
- 2)应用校验注解
/*** projectName: com.atguigu.pojo*/
public class User {//age 1 <= age < = 150@Min(10)private int age;//name 3 <= name.length <= 6@Length(min = 3,max = 10)private String name;//email 邮箱格式@Emailprivate String email;}
- 3)handler标记和绑定错误收集
在handler接收参数的时候,添加 @Validated
注解进行校验,如果校验通过,则正常处理,若不通过,则进行错误处理。
@RestController
@RequestMapping("user")
public class UserController {/*** @Validated 代表应用校验注解! 必须添加!*/@PostMapping("save")public Object save(@Validated @RequestBody User user,//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!BindingResult result){//判断是否有信息绑定错误! 有可以自行处理!if (result.hasErrors()){System.out.println("错误");String errorMsg = result.getFieldError().toString();return errorMsg;}//没有,正常处理业务即可System.out.println("正常");return user;}
}
3、“参数不能为空”的区分
@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。
-
@NotNull (包装类型不为null)
@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。
-
@NotEmpty (集合类型长度大于0)
@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。
-
@NotBlank (字符串,不为null,切不为" "字符串)
@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。