上篇博客我们讲解了使用AOP来进行统一的用户登录判断,其实像这种功能统一且使用较多的地方,都可以用AOP来处理,除了统⼀的⽤户登录判断之外,AOP 还可以实现:
- 统⼀⽇志记录
- 统⼀⽅法执⾏时间统计(在性能优化阶段,监控流量,接口的响应时间等甚至每个方法的响应时间,为整个项目的性能进行优化)
- 统⼀的返回格式设置 (对于接口的返回格式,基本上都是code,message,data)
- 统⼀的异常处理
- 事务的开启和提交等
1.统一异常处理
我们写一个类来测试一下:
package com.example.springaop.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
//@RequestMapping("/ex")
public class ExceptionController {
@RequestMapping("/test1")
public boolean test1() {
int a = 10/0;
return true;
}
@RequestMapping("/test2")
public boolean test2() {
String str = null;
System.out.println(str.length());
return true;
}
@RequestMapping("/test3")
public boolean test3() {
throw new RuntimeException("测试运行时异常");
}
}
运行后去浏览器访问对于的url测试:
可以在控制台看见对应的异常信息,如果不进行统一异常处理的话,那么有些浏览器可能会将异常清楚的告知用户
统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件,具体实现代码如下:
package com.example.springaop.config;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashMap;
@ControllerAdvice// 控制器的通知,交给Spring管理
public class ErrorHandler {
@ResponseBody// 表示返回的数据不是视图
@ExceptionHandler
public Object error (Exception e) {// 根据我的方法参数来捕获异常,这里捕获的就是Exception异常
HashMap<String, Object> result = new HashMap<>();
result.put("success",0);
result.put("code",-1);
result.put("msg","内部异常");
return result;
}
}
- ⭐⽅法名和返回值可以⾃定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解,括号里面的内容可以不写
- ⭐当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配
可以看到这些异常返回了相应的结果
我们再来写一个捕获算数异常和空指针异常统一处理的方法
@ResponseBody// 表示返回的数据不是视图
@ExceptionHandler
public Object error (ArithmeticException e) {// 根据我的方法参数来捕获异常,这里捕获的就是算数ArithmeticException异常
HashMap<String, Object> result = new HashMap<>();
result.put("success",0);
result.put("code",-2);
result.put("msg","ArithmeticException。。。");
return result;
}
@ResponseBody// 表示返回的数据不是视图
@ExceptionHandler
public Object error (NullPointerException e) {// 根据我的方法参数来捕获异常,这里捕获的就是空指针异常
HashMap<String, Object> result = new HashMap<>();
result.put("success",0);
result.put("code",-3);
result.put("msg","NullPointerException。。。");
return result;
}
此时我们再来访问算数异常的url为test1,空指针异常为test2
此时就返回了我们设置的算数异常和空指针异常应该返回的数据
这里有一个需要注意的点就是,如果我们给ExceptionController加上了拦截器
这样我们再去访问的时候发现:
我们并未修改Exception Controller类中的任何代码,只是给他加了一个拦截器而已,那么这又是什么原因呢?
我们查看环绕通知的代码即可发现,不论是什么异常,经过环绕通知最后都变成了运行时异常
解决办法就是把这里修改一下,把连接点里面的异常直接抛出,就像下面这样:
此时我们再去浏览器测试:
算数异常和空指针异常又正常显示了
代码之间经常会互相影响,出现错误后,一定要有耐心的寻找错误,相信自己一定可以找到的
2.统⼀的返回格式
1. 为什么需要统⼀数据返回格式
- ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回的。
- 有利于项⽬统⼀数据的维护和修改。
- 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容
2. 统⼀数据返回格式的实现
@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {
/**
* 内容是否需要重写(通过此⽅法可以选择性部分控制器和⽅法进⾏重写)
* 返回 true 表示重写
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/**
* ⽅法返回之前调⽤此⽅法
*/
@Override
public Object beforeBodyWrite(Object body, // 响应的正文内容
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 封装
HashMap<String, Object> result = new HashMap<>();
result.put("success",1);
result.put("data",body);
result.put("errorMsg","");
return result;
}
}
此时我们在访问user类中的url:
/**
* 用户需要调用的一些方法
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
// 获取用户信息
@RequestMapping("/get")
public String getInfo() {
log.info("获取信息...");
return "获取信息";
}
// 注册
@RequestMapping("/reg")
public Boolean reg() {
log.info("注册...");
return true;
}
// 登录
@RequestMapping("/log")
public Boolean log(HttpServletRequest request, String username, String password) {
log.info("login...");
// 判断用户名和密R码,如果有任意一个为空,那么就不能登陆成功
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
return false;
}
// 此时判断用户名和密码是否正确
// 假装判断一下
if (!"admin".equals(username) || !"admin".equals(password)) {
return false;
}
// 此时用户名和密码校验通过
// 参数true表示,如果没有存放Session,那么需要创建一个Session来存放当前登录的用户
HttpSession session = request.getSession(true);
session.setAttribute("username",username);
return true;
}
}
可以看到,我们访问get的url时出错了,因为它返回的数据是String类型
原因在这里可以看到:SpringBoot 使用 beforeBodyWrite 实现统一的接口返回类型_一梦喂马.的博客-CSDN博客
那么该如何解决呢?
再加一个判断方法,当它返回的数据为String类型时,手动按照Json的数据格式返回
异常使用@SneakyThrows注解,重新去浏览器访问:
可以看到访问成功了,不论访问的是那个页面,成功与否,返回的数据都是我们刚才设置的统一格式
3.总结
- 自定义拦截器使⽤ WebMvcConfigurer+ HandlerInterceptor来实现,
- 统⼀异常处理使⽤ @ControllerAdvice + @ExceptionHandler 来实现,
- 统⼀返回值处理使⽤@ControllerAdvice + ResponseBodyAdvice 来处理