拦截器,请求的接口被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略,拦截器主要用来按照指定规则拒绝请求。
使用场景:
1、Token令牌验证、2、请求数据校验、3、用户权限校验、4、放行指定接口
1. 拦截器的使用
使用拦截器需要进行两步操作,首先需要定义拦截器、然后配置拦截器即可。
1.1 定义拦截器
定义拦截器,需要实现 HandlerInterceptor 接口,该接口中有三个方法: preHandle()、postHandle() 和 afterCompletion()
preHandle() 方法:当某个 url 已经匹配到对应的 Controller 中的某个方法,且在这个方法执行之前。所以 preHandle() 方法可以决定是否将请求放行,这是通过返回值来决定的,返回 true 则放行,返回 false 则不会向后执行。postHandle() 方法:当某个 url 已经匹配到对应的 Controller 中的某个方法,且在执行完了该方法,但是在 DispatcherServlet 视图渲染之前。所以在这个方法中有个 ModelAndView 参数,可以在此做一些修改动作。afterCompletion() 该方法是在整个请求处理完成后(包括视图渲染)执行,这时做一些资源的清理工作,这个方法只有在 preHandle() 被成功执行后并且返回 true 才会被执行。
以上就是对HandlerInterceptor 接口的主要方法的讲解,接下来我们需要自定义拦截器。
package com.bowen.interceptor;import com.bowen.annotation.UnInterception;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;/** *
springboot-study
*
自定义拦截器
* @author : zhang.bw * @date : 2020-07-17 21:37 **/public class MyInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); String methodName = method.getName(); logger.info("====拦截到了方法:{},在该方法执行之前执行====", methodName); // 通过方法,可以获取该方法上的自定义注解,然后通过注解来判断该方法是否要被拦截 // @UnInterception 是我们自定义的注解 UnInterception unInterception = method.getAnnotation(UnInterception.class); if (null != unInterception) { return true; } // 判断用户有没有登陆,一般登陆之后的用户都有一个对应的token String token = request.getParameter("token"); if (null == token || "".equals(token)) { logger.info("用户未登录,没有权限执行……请登录"); return false; } } // 返回true才会继续执行,返回false则取消当前请求 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { logger.info("执行完方法之后执行(Controller方法调用之后),但是此时还没进行视图渲染"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.info("整个请求都处理完成,DispatcherServlet也渲染了对应的视图,在这里可以做清理事项"); }}
经过上述步骤,我们已完成拦截器的定义。
1.2 配置拦截器
在 Spring Boot 2.0 之前,我们都是直接继承 WebMvcConfigurerAdapter 类,然后重写 addInterceptors 方法来实现拦截器的配置。但是在 Spring Boot 2.0 之后,该方法已经很少用了,取而代之的是 WebMvcConfigurationSupport 类,如下:
@Configurationpublic class MyInterceptorConfig extends WebMvcConfigurationSupport { @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); }}
我们配置了一个拦截器,在MyInterceptorConfig中我们重写了addInterceptors方法,将定义的拦截器MyInterceptor加进去,addPathPatterns 方法是添加要拦截的请求,这里我们拦截所有的请求
新建一个InterceptorController测试一下,启动项目,在浏览器中输入 http://localhost:8080/interceptor/test1?token=2020
@Controller@RequestMapping("/interceptor")public class InterceptorController { @RequestMapping("/test1") public String test1() { return "hello"; }}
测试结果:可以看出拦截器已经生效,并能看出其执行顺序。
拦截器测试
1.3 静态资源被拦截问题处理
上述配置有一个问题,那就是静态资源被拦截了。
我们可以在 resources/static/ 目录下放置一个图片ymtd.png,然后请求静态资源,发现被拦截了。
所以对于 WebMvcConfigurationSupport 而言,它会导致默认的静态资源被拦截,这就需要我们手动将静态资源放开。
怎么放开?需要再重写一个方法:addResourceHandlers,将静态资源放开:
/** * 用来指定静态资源不被拦截,否则继承WebMvcConfigurationSupport这种方式会导致静态资源无法直接访问 * @param registry */@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); super.addResourceHandlers(registry);}
访问静态资源,浏览器输入:http://localhost:8080/ymtd.png
拦截器测试
配置完成后,重启项目,静态资源也可以正常访问了。
配置拦截器还有一种方式,那就是直接实现 WebMvcConfigurer 接口,然后重写 addInterceptors 方法,将自定义的拦截器添加进去即可,如下:
@Configurationpublic class MyInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 实现WebMvcConfigurer不会导致静态资源被拦截 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); }}
实现 WebMvcConfigure 接口的话,不会拦截 Spring Boot 默认的静态资源。
继承 WebMvcConfigurationSupport 类的方式可以用在前后端分离的项目中,后台不需要访问静态资源(就不需要放开静态资源了);实现 WebMvcConfigure 接口的方式可以用在非前后端分离的项目中,因为需要读取一些图片、css、js文件等。
2. 拦截器使用例子
2.1 判断用户是否登录
一般用户登录功能我们可以这么做,要么往 session 中写一个 user,要么针对每个 user 生成一个 token,第二种要更好一点,那么针对第二种方式,如果用户登录成功了,每次请求的时候都会带上该用户的 token,如果未登录,则没有该 token,服务端可以检测这个 token 参数的有无来判断用户有没有登录,从而实现拦截功能。我们改造一下 preHandle 方法,如下:
在 MyInterceptor 的 preHandle方法中加入以下判断// 判断用户有没有登陆,一般登陆之后的用户都有一个对应的tokenString token = request.getParameter("token");if (null == token || "".equals(token)) { logger.info("用户未登录,没有权限执行……请登录"); return false;}
重启项目,在浏览器中输入 localhost:8080/interceptor/test1 后查看控制台日志,发现被拦截,如果在浏览器中输入 localhost:8080/interceptor/test1?token=2020 即可正常访问。
2.2 取消拦截操作
如果要拦截所有 /user 开头的 url 请求的话,需要在拦截器配置中添加这个前缀,但是在实际项目中,可能会有这种场景出现:某个请求也是 /user 开头的,但是不能拦截,比如 /user/login 等等,这样的话又需要去配置。那么,可不可以做成一个类似于开关的东西,哪里不需要拦截,我就在哪里弄个开关上去,做成这种灵活的可插拔的效果呢?
是可以的,我们可以定义一个注解,该注解专门用来取消拦截操作,如果某个 Controller 中的方法我们不需要拦截掉,即可在该方法上加上我们自定义的注解即可,下面先定义一个注解:
/** *
springboot-study
*
该注解用来指定某个方法不用拦截
* @author : zhang.bw * @date : 2020-07-17 21:37 **/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UnInterception {}
然后在 Controller 中的某个方法上添加该注解,在拦截器处理方法中添加该注解取消拦截的逻辑,如下:
在 MyInterceptor 的 preHandle方法中加入以下判断// 通过方法,可以获取该方法上的自定义注解,然后通过注解来判断该方法是否要被拦截// @UnInterception 是我们自定义的注解UnInterception unInterception = method.getAnnotation(UnInterception.class);if (null != unInterception) { return true;}
Controller 中的方法代码可以参见源码,重启项目在浏览器中输入 http://localhost:8080/interceptor/test2?token=123 测试一下,可以看出,加了该注解的方法不会被拦截。
3. 总结
本节课程主要介绍了 Spring Boot 中拦截器的使用,从拦截器的创建、配置,到拦截器对静态资源的影响,都做了详细的分析。Spring Boot 2.0 之后拦截器的配置支持两种方式,可以根据实际情况选择不同的配置方式。通过本节课程的学习,相信对Spring Boot拦截器的使用有了比较深刻的印象。
4. 源码获取
!