本来这一节想讲接口的统一返回值的,发现用到拦截器,所以这一节先做个铺垫,讲一下拦截器,后面再讲“统一的返回格式”,“统一返回值的全局处理”和“异常的全局处理”。

为什么要用拦截器

考虑这样的场景:

1、在后台的返回值中,每个接口返回的数据都不一样,有可能是字符串,也有可能是List,也有可能是Map,如果直接返回这样的数据,相信前端会疯掉的。

2、所有的管理平台都需要登录之后才能操作,每个接口收到请求后再处理肯定不合适,有没有统一的处理办法?

解决的方法就是通过拦截器,在接口处理之前或者之后一次性统一处理。

实现步骤

定义拦截器

在SpringBoot中定义拦截器,只需要实现HandlerInterceptor接口,重写其中的3个方法即可。

preHandle(……) 方法 该方法的执行时机是,当某个 URL 已经匹配到对应的 Controller 中的某个方法,且在这个方法执行之前。所以 preHandle(……) 方法可以决定是否将请求放行,这是通过返回值来决定的,返回 true 则放行,返回 false 则不会向后执行。

postHandle(……) 方法 该方法的执行时机是,当某个 URL 已经匹配到对应的 Controller 中的某个方法,且在执行完了该方法,但是在 DispatcherServlet 视图渲染之前。所以在这个方法中有个 ModelAndView 参数,可以在此做一些修改动作。

afterCompletion(……) 方法

顾名思义,该方法是在整个请求处理完成后(包括视图渲染)执行,这时做一些资源的清理工作,这个方法只有在 preHandle(……) 被成功执行后并且返回 true 才会被执行。

 

配置拦截器

在Spring Boot 2.0 之后,有两种配置的方式:

  • 继承WebMvcConfigurationSupport类
  • 实现WebMvcConfigurer接口

继承WebMvcConfigurationSupport

@Configuration
 public class MyInterceptorConfig extends WebMvcConfigurationSupport {
 
     @Override
     protected void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
         super.addInterceptors(registry);
     }
 }

在该配置中重写 addInterceptors 方法,将我们上面自定义的拦截器添加进去,addPathPatterns 方法用来添加要拦截的请求,这里我们拦截所有的请求。现在我们配置好了拦截器,接下来写一个 Controller 测试一下:

@Controller
 @RequestMapping("/interceptor")
 public class InterceptorController {
     @RequestMapping("/test")
     public String test() {
         return "hello";
     }
 }

让其跳转到 hello.html 页面,直接在 hello.html 中输出 hello interceptor 即可。启动项目,在浏览器中输入:localhost:8080/interceptor/test,看一下控制台的日志:

preHandle,尚未处理接口业务
 postHandle,尚未渲染
 afterCompletion,渲染完成

可以看出拦截器已经生效,并能看出其执行顺序。

使用上面这种配置的话,会有一个缺陷,那就是静态资源被拦截了。可以在 resources/static/ 目录下放置一个图片资源或者 HTML 文件,之后启动项目直接访问,即可看到无法访问的现象。

如何解决静态资源访问的问题呢?除了在 MyInterceptorConfig 配置类中重写 addInterceptors 方法,还需要再重写一个方法 addResourceHandlers,用来将静态资源放开:

/**
  * 用来指定静态资源不被拦截,否则继承 WebMvcConfigurationSupport 这种方式会导致静态资源无法直接访问
  */
 @Override
 protected void addResourceHandlers(ResourceHandlerRegistry registry) {
     registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
     super.addResourceHandlers(registry);
 }

如上配置好之后,重启项目,静态资源也可以正常访问了。

实现WebMvcConfigurer接口

直接实现 WebMvcConfigurer 接口,然后重写 addInterceptors 方法,将自定义的拦截器添加进去即可,如下:

@Configuration
 public class MyInterceptorConfig implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         // 实现 WebMvcConfigurer 不会导致静态资源被拦截
         registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
     }
 }

通过实现 WebMvcConfigure 接口,使 Spring Boot 默认的静态资源不会拦截,推荐使用这种方式

 

实例简介

一般用户的登录功能,我们可以这么实现,要么在 Session 中写一个 user,要么针对每个 user 生成一个 Token,相比之下,第二种要更好。

第二种方式中,如果用户登录成功,每次请求时都会带上该用户的 Token,如果未登录,则没有该 Token,服务端可以检测这个 Token 参数的有无来判断用户有没有登录,从而实现拦截功能。我们改造一下 preHandle 方法,如下:

@Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 
     HandlerMethod handlerMethod = (HandlerMethod) handler;
     Method method = handlerMethod.getMethod();
     String methodName = method.getName();
 
     // 判断用户有没有登陆,一般登陆之后的用户都有一个对应的 token
     String token = request.getParameter("token");
     if (null == token || "".equals(token)) {
         logger.info("用户未登录,没有权限执行……请登录");
         return false;
     }
 
     // 返回 true 才会继续执行,返回 false 则取消当前请求
     return true;
 }

重启项目,在浏览器中输入:localhost:8080/api/testInterceptor, 之后查看控制台日志,发现被拦截,如果在浏览器中输入:localhost:8080/api/testInterceptor?token=123 即可正常往下访问。

总结

知道使用拦截器分两步走,知道拦截器中有3个方法可根据请求的不同阶段进行处理,这就算学会了拦截器,具体的使用还要结合的需求,下一节就会使用的实例。

代码已同步更新:https://github.com/316620938/shop-springboot.git