首先你得有web环境,这个就不说了,springboot下很简单。
一、拦截器使用
我们先来使用一下拦截器。
步骤1、先创建一个Controller
@RestController
@RequestMapping("/test")
public class MyController {
@GetMapping("/test/{name}")
public String test(@PathVariable(name = "name") String name) {
System.out.println("MyController.test invoke:" + name);
return name;
}
}
逻辑很简单,就是一个get请求,携带一个name参数,并且把这个name返回给前端。
步骤2、创建拦截器
// 拦截器,只需要实现 HandlerInterceptor 接口即可
public class MyInterceptor implements HandlerInterceptor {
// 请求处理之前触发,如果返回 true,则继续执行往下走,否则就直接返回
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle invoke");
String requestURI = request.getRequestURI();
if (requestURI.contains("/test")) {
return true;
}
return false;
}
// 请求处理之后触发,如果 preHandle 返回 true,则执行 postHandle
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle invoke");
}
// 请求处理之后触发,如果 preHandle 返回 true,则执行 afterCompletion
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion invoke");
}
}
步骤3、配置类,加入拦截器
以前我们在Spring Mvc的时候要配置一大堆映射。这里在boot环境我们只需要配置在配置类里面即可。
// 开启一个配置类,实现WebMvcConfigurer即可
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 添加拦截器 这里我是用的new 但是实际不对,最好是使用注入进来的单例,不然开销太大了
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry
// 添加第一组拦截器和他的规则,我们就添加我们的MyInterceptor,并且他的拦截规则是/**,也就是拦截所有的请求
// 而且我们放行css和js等静态资源,因为这些资源是不需要拦截的
.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/js/**");
}
}
步骤四、启动测试
我们看到页面返回没问题。我们再来看看后端控制台。
MyInterceptor.preHandle invoke
MyController.test invoke:levi
MyInterceptor.postHandle invoke
MyInterceptor.afterCompletion invoke
不出所料,我们看到方法执行前触发Interceptor.preHandle
然后是方法执行,然后是Interceptor.postHandle
最后是Interceptor.afterCompletion
所以我们这就基本可以说使用起来了。具体能干啥,我们下文再说。我们先来看源码逻辑。知道他的底层实现。
二、拦截器底层源码原理
1、源码分析
我们知道在MVC里面对于拦截器的处理是在DispatcherServlet这个类中处理的,他处理了请求的映射和派发等逻辑。
我们就来看他的核心逻辑。
DispatcherServlet本质还是Servlet,既然是Servlet那他的核心就在doService()方法里面,我们先来看这个方法。
org.springframework.web.servlet.DispatcherServlet#doService
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
***省略上面无用***
this.doDispatch(request, response);
***省略下面无用***
}
按照spring的毛病,所有的核心业务都在以do开头的方法实现中,那我们就进入这个doDispatch看看。
org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
省略没用的
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 校验文件上传,这里不看
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 获取处理器,这里其实就是获取你前端的接口路径交给哪个controller的
// 哪个方法去执行的派发器,这里不是重点也不说
mappedHandler = this.getHandler(processedRequest);
// 获取适配器,这个就是找到了执行的方法
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
// 下面就是执行方法,上面就是找到执行器,这个处于中间,所以他必然是处理拦截器的preHandle的地方,
// 所以我们就知道这里是拦截器生效的地方,于是我们来到这里。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 这里是执行方法的地方,也就是执行你controller的里面的方法的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
this.applyDefaultViewName(processedRequest, mv);
// 当上面的方法被放行通过之后,就来这里执行applyPostHandle方法。
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 在这里最后执行afterCompletion方法
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}
}
省略没用的
}
上面我们看到我们推出拦截器执行的机会是mappedHandler.applyPreHandle这个方法,所以我们就来看这个方法。
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
OK,也不出所料,我们在代码上来就看到了interceptors 获取了所有的拦截器。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取所有的拦截器,封装在这里
HandlerInterceptor[] interceptors = this.getInterceptors();
// null校验
if (!ObjectUtils.isEmpty(interceptors)) {
// 遍历所有拦截器,并且给当前遍历到的哪个拦截器保存一个下标进度interceptorIndex
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
/**
挨个开始执行每个拦截器的preHandle方法,并且返回布尔值,我们看到返回为false的时候取反为true成立,此时就会执行triggerAfterCompletion方法,这个方法的内容就是执行拦截器的interceptor.afterCompletion方法。当preHandle执行结果为true的时候,也就是我们在拦截器里面返回了true的时候,这里就直接跳过去了,等于只执行了preHandle。
所以我们可以知道,一旦有一个拦截器返回false,后面的就都不走了。
*/
if (!interceptor.preHandle(request, response, this.handler)) {
/**
我们这里看一下triggerAfterCompletion,其实就是拿到之前执行过的所有的拦截器,
就是之前执行成功的,
也就是返回为true的.此时通过interceptorIndex这个下标反向遍历这些拦截器,
因为是反向遍历的,所以我们知道afterCompletion是和preHandle倒序执行的
把之前执行成功的拦截器的afterCompletion方法执行一遍。这里我们说一下,
因为afterCompletion这个方法是一定会被执行的,
所以以前成功的就要执行afterCompletion,以后失败的也就不说了,直接就返回了
啥都不执行了。
*/
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
我们这里看一下triggerAfterCompletion,其实就是拿到之前执行过的所有的拦截器,就是之前执行成功的,也就是返回为true的
此时通过interceptorIndex这个下标反向遍历这些拦截器,把之前执行成功的拦截器的afterCompletion方法执行一遍。因为是反向遍历的,所以我们知道afterCompletion是和preHandle倒序执行的
这里我们说一下,因为afterCompletion这个方法是一定会被执行的,所以以前成功的就要执行afterCompletion,以后失败的也就不说了。
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = this.interceptorIndex; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.afterCompletion(request, response, this.handler, ex);
}
}
}
以上就是拦截器在boot中的生效能力。
2、总结
我们这里总结一下他的这个原理,首先他会获取你容器中的所有拦截器,包括你自己实现的拦截器,组成一个集合。然后再一个方法被拦截的时候,他会遍历这个拦截器,然后挨个执行拦截器的preHandle方法,在每个拦截器执行自己的preHandle方法拿到返回值,返回值如果是true表示通过放行,然后就往下走,执行后面的拦截器的preHandle。
如果执行结果为false,表明没通过拦截器的拦截方法。此时整个拦截器链条就直接断了,他会去执行一个叫做triggerAfterCompletion的方法,在这个方法里面,会把以前执行过preHandle的方法执行一遍他的afterCompletion方法。
如果每个拦截器都顺利执行了preHandle(都返回true),那么此时他就往下走执行方法本身。
然后执行完方法之后,在执行每个拦截器的PostHandle方法。
最后执行每个拦截器的afterCompletion方法。
也就是说我们看到其实preHandle和afterCompletion是必然会执行的,但是PostHandle不一定。
三、拦截器能做什么
以我们这里这个拦截器为例。
// 拦截器,只需要实现 HandlerInterceptor 接口即可
public class MyInterceptor implements HandlerInterceptor {
// 请求处理之前触发,如果返回 true,则继续执行往下走,否则就直接返回
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle invoke");
String requestURI = request.getRequestURI();
if (requestURI.contains("/test")) {
return true;
}
return false;
}
// 请求处理之后触发,如果 preHandle 返回 true,则执行 postHandle
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle invoke");
}
// 请求处理之后触发,如果 preHandle 返回 true,则执行 afterCompletion
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion invoke");
}
}
我们可以在方法执行之前的preHandle这里拦截到哪些请求,对于这些请求我们判断是不是要先登录才能访问。那么我们这里就可以判断他是不是已经登录了,要是登录了,我们就返回true放行,否则就返回false,并且在返回false之前跳转去登录页面,或者返给前端一个标识,让前端去跳都可以。
当然也可以放行一些不需要登录就能访问的,反正你灵活使用。