关于springboot的拦截器能力源码分析

首先你得有web环境,这个就不说了,springboot下很简单。

一、拦截器使用

我们先来使用一下拦截器。

步骤1、先创建一个Controller

java 复制代码
@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、创建拦截器

java 复制代码
// 拦截器,只需要实现 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环境我们只需要配置在配置类里面即可。

java 复制代码
// 开启一个配置类,实现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/**");
    }
}

步骤四、启动测试

我们看到页面返回没问题。我们再来看看后端控制台。

shell 复制代码
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

java 复制代码
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        
	***省略上面无用***
	this.doDispatch(request, response);
     ***省略下面无用***  
}

按照spring的毛病,所有的核心业务都在以do开头的方法实现中,那我们就进入这个doDispatch看看。

org.springframework.web.servlet.DispatcherServlet#doDispatch

java 复制代码
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 获取了所有的拦截器。

java 复制代码
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不一定。

三、拦截器能做什么

以我们这里这个拦截器为例。

java 复制代码
// 拦截器,只需要实现 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之前跳转去登录页面,或者返给前端一个标识,让前端去跳都可以。

当然也可以放行一些不需要登录就能访问的,反正你灵活使用。

相关推荐
paopaokaka_luck7 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
以后不吃煲仔饭19 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师20 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
The_Ticker26 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
ExiFengs1 小时前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
paj1234567891 小时前
JDK1.8新增特性
java·开发语言
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse