前言
这篇文章我们来谈谈拦截器,统一结果返回以及统一异常处理以及在Spring 的应用
拦截器
拦截器是Spring的核心功能之一,主要就是用来拦截用户的请求,比如说很多操作都是需要登录之后才能操作的,而这里就可以设置,对Session带有登录信息的就放行,没有就进行拦截,让其重新登录
拦截器的使用主要分两步
1.定义拦截器
2.注册拦截器
1.拦截器的注册
自定义拦截器:实现HandlerInterceptor接口,重写所有方法
这里主要就是三个方法
@Slf4j @Component public class interceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //true 表示放行 false表示拦截 log.info("LoginInterceptor preHandle...."); //获取session, 并且判断session中存储的userinfo信息是否为空 HttpSession session = request.getSession(); UserInfo userInfo = (UserInfo) session.getAttribute(Constant.USER_SESSION); if (userInfo==null || userInfo.getId()<=0){ //用户未登录 response.setStatus(401); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
见名知意,这三个方法一个是在目标方法执行前执行,一个是在目标方法执行后操作
还有afterCompletion方法就是在视图渲染完之后再执行(现在前后端分离,几乎不用)
2.第二步是注册拦截器
实现WebMvcConfigurer接口 重写他的addInterceptor接口
@Configuration public class webConfig implements WebMvcConfigurer { @Autowired private interceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor) .addPathPatterns("/**") .excludePathPatterns("/user/login","/captcha/get") .excludePathPatterns("/css/**") .excludePathPatterns("/js/**") .excludePathPatterns("/pic/**") .excludePathPatterns("/**/*.html"); } }
这里注册主要是两个方法addPath和exclude,第一个是需要拦截的接口,其面向url,第二个是需要排除的接口,比如登录接口需要排除,假设我登录也需要先登录就陷入死循环了
注:返回true表示不拦截,返回false表示拦截
拦截路径含义
拦截路径
含义
/* ⼀级路径
能匹配/user,/book,/login,不能匹配 /user/login
/** 任意级路径
能匹配/user,/user/login,/user/reg
/book/* /book下的⼀级路径
能匹配/book/addBook,不能匹配/book/addBook/1,/book
/book/** /book下的任意级路径
能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login
适配器模式
HandlerAdapter在SpringMVC中饭使用了适配器模式
下面我们介绍一下适配器模式
使用背景:假设用户需要的接口和我提供的接口不同,但是我们不希望修改原来的接口
我们这个时候就可以引入一个中间类进行包装一下,这里这个中间类就称之为适配器
使用适配器模式之后
实际上就是创建了一个实现类,实现了需要被调用的接口,底层持有了一个目标接口(符合我的需求),实际上就是假设我想实现一个功能,但是我觉得第三方的更好,但是不适配我的调用,我就可以使用一个适配器类,实现我的接口持有他的引用,最后实现目标方法的时候使用第三方的即可. 下面我们使用代码验证一下
这里我们有一个logFactory 专门负责打印日志
但是我们嫌弃自己的日志打印有点low
此时我们发现了一个NB的日志框架
我们将对应的类称之为NBlog
但是我们发现NBlog提供的方法和我需要的不一样,我得加个转接器
//适配器 public class LogAdaptor implements LogFactory{ private NBLog nbLog; public LogAdaptor(NBLog nbLog) { this.nbLog = new NBLog(); } @Override public void log(String msg) { nbLog.getLog(1, msg); } } //日志工厂 public interface LogFactory { void log(String msg); } //NB日志 public class NBLog { int i; String msg; public void getLog(int i, String msg) { System.out.println("瞬间打印一条日志"+msg); } } //测试类 public class ClientTest { public static void main(String[] args) { LogFactory logFactory = new LogAdaptor(new NBLog()); logFactory.log("aaa"); } }
打印结果就是调用了底层的nb框架来实现我们的日志功能
优点是程序的可扩展性增强了很多,不需要改动原有的接口就可以引入很多的优秀实现框架
统一数据返回
我们知道,如果我们之前返回的类型不同,而如果我们后面想给每个返回值都加上状态码等信息的时候就会显得异常麻烦,前端获取到的数据得重新修改,后端传入的数据也得重新包装,这无疑加重了程序员的负担,Spring就提供了统一结果返回的方式
主要使用时基于@ControllerAdvice和实现ResponseBodyAdvice的接口来完成任务
我们将统一返回通知类加上其注解修饰并实现接口即可
@ControllerAdvice public class Advice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { return false; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return null; } }
这里的support方法的返回值就是进行判断处理的,如果是false表示不统一返回类型
ture表示统一,我们可以通过returnType来获取其类信息来过滤掉一些不需要返回的接口
beforeBodyWrite就表示在返回前需要封装的操作,我们可以将原来的返回值body加上一层Result.success的封装
@ControllerAdvice public class Advice 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) { return Result.success(body); } }
然后我们会发现有些返回值类型就会出现一些问题,比如原始值就是一个Result就无需再次封装一层了
如果封装了一层就会出现以下类似情况
这不是我们想要的,所以我们对这种情况进行去除
在返回之前对这种情况进行判断即可
这里就可以使用instanceOf来进行判断,是的话就直接返回一个body即可,不是再进行返回
还有一个特殊的问题就是如果body返回值是一个String类型我们就得进行一个序列化
使用objectMapper进行操作即可,使用writeValueAsString方法进行序列化
注:解决方法得在源码中查看,暂时不做讲解,稍后再聊
统一异常处理
有时候我们出的一些问题可能不会被catch捕获
这里我们假设一个sql语句写错了,可以只给前端返回一个错误码等等,不将错误暴露出去
我们就可以使用统一异常处理
下面是一个简单的例子
我故意将这个接口修改错误
使用统一异常处理之前
使用统一异常处理之后
统一异常代码
注意这里如果不加上@ResponseBody会默认返回一个视图
注:优先子类来捕获,子类捕获不住就让父类异常来捕获