文章目录
- 一. 拦截器
- 1. 什么是拦截器
- 2. 拦截器的使用
- 自定义拦截器
- 注册配置器
- 拦截路径
- 3. 适配器模式
- 二. 统一数据返回格式
- 异常处理
- 三. 统一异常处理
一. 拦截器
1. 什么是拦截器
例如, 图书管理系统, 在访问图书list页面之前, 需要先进行登录, 如果没登录, 就需要进行强制跳转页面
那么我们就需要在每个接口中都进行判断是否进行了登录, 像这样重复的代码, SpringBoot就封装成了一个框架
2. 拦截器的使用
自定义拦截器
定义一个接口类, 实现HandlerInterceptor接口, 根据需要重写里面的方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("目标方法执行前执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("目标方法执行后执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("视图渲染完成后执行后执行");
}
}
preHandle()方法, 返回true, 表示拦截后, 通过验证, 继续执行目标代码
返回false, 表示拦截后, 没有通过验证, 不能执行目标代码
登录验证:
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
private ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("校验用户是否登录");
HttpSession session = request.getSession();
UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_USER_KEY);
if(userInfo == null){
log.warn("用户未登录");
response.setStatus(401);
return false;
}
log.info("用户登录检验通过");
return true;
}
注册配置器
自定义一个类, 实现WebMvcConfigurer, 重写addInterceptors方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
//1. 注入拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
//2. 重写方法, 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//addPathPatterns方法, 表示添加拦截的路径 /** 表示全部路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");
}
}
通过观察日志发现拦截成功
拦截路径
用户登录是, 我们希望对登录页面不进行拦截
但是发现并不好使…还是会拦截…(后面解决)
(测试返回不是字符串的类型, 会进行排除拦截, 但是String类型并没有拦截)
3. 适配器模式
使用场景:
前⾯学习的slf4j就使⽤了适配器模式,slf4j提供了⼀系列打印⽇志的api,底层调⽤的是log4j或者
logback来打⽇志,我们作为调⽤者,只需要调⽤slf4j的api就⾏了.
/**
* slf4j接⼝
*/
interface Slf4jApi{
void log(String message);
}
/**
* log4j 接⼝
*/
class Log4j{
void log4jLog(String message){
System.out.println("Log4j打印:"+message);
}
}
/**
* slf4j和log4j适配器
*/
class Slf4jLog4JAdapter implements Slf4jApi{
private Log4j log4j;
public Slf4jLog4JAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void log(String message) {
log4j.log4jLog(message);
}
}
/**
* 客⼾端调⽤
*/
public class Slf4jDemo {
public static void main(String[] args) {
Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
slf4jApi.log("使⽤slf4j打印⽇志");
}
}
二. 统一数据返回格式
后端需要对返回的数据进行封装, 告诉前端响应的结果
可以封装成一个Result枚举类进行返回, 里面包含自定义状态码, 错误信息, 和返回的数据
但是如果我们每一个都要修改成Result, 很麻烦, 所以SpringBoot给我们封装成了框架
使用@ControllerAdvice注解, 继承ResponseBodyAdvice接口, 重写接口中的方法
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@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) {
if(body instanceof Result<?>){
return body;
}
return Result.success(body);
}
}
supports⽅法: 判断是否要执⾏beforeBodyWrite⽅法.true为执⾏,false不执⾏. 通过该⽅法可以
选择哪些类或哪些⽅法的response要进⾏处理,其他的不进⾏处理.
beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理
异常处理
我们写一个返回不同参数的测试类
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@RequestMapping("/t2")
public Boolean t2() {
return true;
}
@RequestMapping("/t3")
public Integer t3() {
return 1;
}
@RequestMapping("/t4")
public BookInfo t4() {
return new BookInfo();
}
}
测试t2t3t4都返回正常, 只有返回String会发生异常
通过异常信息, 进行打断点的方式, 阅读源码, 进行简单理解:
SpringMVC提供了很多对于返回结果的处理器, 会根据不同的返回类型, 进行调用不同的处理器
如果返回的是String, 那么进入的接口是String相关的处理器, 在这些方法中, 有一个方法会根据我们的统一返回数据, 对String进行封装, 二后面的方法, 接收参数的时候, 还是使用String进行接收的, 就会发生类型不匹配的情况
如果返回的不是String, 就不会发生类型不匹配的情况
我们进行统一数据返回处理后, 封装成了Result对象
但是如果返回String类型, 会发生类型不匹配异常, 所以我们在返回的时候, 不应该返回Result类型, 而应该是String, 所以使用SpringBoot内置提供的Jackson来实现信息序列化, writeValueAsString方法, 可以将接收到的对象参数, 按照JSON的方式, 就行转换, 然后通过String进行返回
此时, t1测试成功, 返回的就是JSON格式的字符串
上面登录页面, 返回的是String, 并且尽管我们去除拦截login接口, 还是会进行拦截
现在我们加上这个转JSON, 将异常处理了, 来测试一下:
没有进行拦截了, 并且正常返回String
原因在于: String类型, 如果不使用SpringBoot内置提供的Jackson来实现信息序列化, writeValueAsString方法, 就会抛异常, 那么Spring内部就会走到另一个"/error"路径, 而不再是"/user/login", 路径已经变了, 但是"/error"路径, 我们并没有进行拦截排除, 如果error这个路径会被拦截, 此时就会打印日志, 看起来好像是"/user/login"被拦截一样
三. 统一异常处理
上述的统一返回结果中, 返回的都是SUCCESS, 即使发生异常, 这显然是不合理的
那么, 我们就可以进行统一的异常处理, 前端在ajax中使用error接收
添加@ControllerAdvice注解, 并在处理异常的方法前加@ExceptionHandler注解
由于接口返回的是数据, 需要加上@ResponseBody
@ControllerAdvice注解:
所以@ControllerAdvice也可以实现将对象交给Spring处理
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e){
return Result.fail(e.getMessage());
}
}
也可以针对不同的异常, 返回不同的结果:
通过修改方法的参数类型
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e){
return Result.fail(e.getMessage());
}
@ExceptionHandler
public Object handler1(NullPointerException e){
return Result.fail("NullPointerException: " + e.getMessage());
}
@ExceptionHandler
public Object handler2(ArithmeticException e){
return Result.fail("ArithmeticException:" + e.getMessage());
}
}