开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。
对于异常的处理,一般分为两种方式:
@Throws
或 @ExceptionHandler
),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。站在宏观角度来看待声明式事务处理:
整个项目从架构这个层面设计的异常处理的统一机制和规范。
一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。
使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!
处理流程:
发生异常 --> 去往ControllerAdvice注解的类 --> 寻找@ExceptionHandler(绑定的类名.class)注解的异常处理类
声明异常处理控制器类
异常处理控制类,统一定义异常处理handler方法!
/**
* projectName: com.sunsplanter.execptionhandler
*
* description: 全局异常处理器,内部可以定义异常处理Handler!
*/
/**
* @RestControllerAdvice = @ControllerAdvice + @ResponseBody
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
}
异常处理handler方法和普通的handler方法参数接收和响应都一致!
只不过异常处理handler方法要映射异常,发生对应的异常会调用!
/**
* 当发生空指针异常会触发此方法!
* 这是具体异常处理Handler,如果所有具体异常处理handler都不匹配,就走@ExceptionHandler(Exception.class)
*/
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){
return null;
}
/**
* 所有异常都会触发此方法!但是如果有具体的异常处理Handler, 具体异常处理Handler优先级更高!
* 例如: 发生NullPointerException异常!
* 会触发handlerNullException方法,不会触发handlerException方法!
*/
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){
return null;
}
记得将异常处理控制类所在的包加入包扫描.
什么是拦截器?
在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测
什么时候拦截器会生效?
调用handler之前(preHandler)和之后(postHandler),DS返回结果给前端之前(afterCompletion)
拦截器图1
Springmvc中的拦截器 VS javaWeb中的过滤器:
总结 : 拦截器是对过滤器的进一步升级 , 能用拦截器就没必要用过滤器
拦截器图2
拦截器使用
package com.sunsplanter.interceptors
public class MyInterceptor implements HandlerInterceptor {
// if( ! preHandler()){return;}
//preHandle在处理请求的目标 handler 方法前执行
//根据返回值true/false决定是否放行
/**
*request 请求
*response响应
*handler 处理之前要判断是否拦截的handler方法
*modelAndView 返回的视图和共享域数据对象
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
System.out.println("Process01Interceptor.preHandle");
return true;
}
// postHandle在目标 handler 方法后执行,若handler报错则该postHandler不执行!
@Override
public 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");
}
// afterCompletion在渲染视图之后执行(最后),一定执行!
@Override
public 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("MyInterceptor.afterCompletion");
}
}
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.sunsplanter.controller","com.sunsplanter.interceptors"})
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法!
public class SpringMvcConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}
//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将MyInterceptor中的所有拦截器都添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new MyInterceptor());
}
}
a. 默认拦截全部(即上述)
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new MyInterceptor());
}
b . 精准配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
//addPathPatterns("/common/request/one") 添加拦截路径
//也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
//拦截user下的所有handler
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/**");
}
c. 排除配置
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//排除匹配,排除已在匹配的范围的handler
//addPathPatterns("/user/**") 添加拦截路径
//excludePathPatterns("/user/data"); 排除路径,排除应该在拦截的范围内
registry.addInterceptor(new Process01Interceptor())
.addPathPatterns("/user/**")
.excludePathPatterns("/user/data");
}
SpringMVC 会把所有拦截器收集到一起 , 然后:
a. 按照配置顺序调用各个 preHandle() 方法。
b. 按照配置逆序调用各个 postHandle() 方法。
c. 按照配置逆序调用各个 afterCompletion() 方法。
什么是参数校验?
在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
一切的开始是导入依赖:
使用参数校验的前提是同时开启@EnableWebMvc和添加依赖
<!-- 校验注解 -->
<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>
步骤是 :
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;
/**
* projectName: com.sunsplanter.pojo
*/
public class User {
//age 1 <= age < = 150
@Min(1)
private int age;
//name 3 <= name.length <= 10
@Length(min = 3,max = 10)
private String name;
//email 邮箱格式
@Email
private String email;
public int getAge() {
return age;
}
}
@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;
}
}
常用的校验注解就是非空和长度限定 , 其中存在三种非空 :