Spring拦截器与DispatcherServlet

目录

一:拦截器

1:拦截器作用

2:拦截器使用

(1):定义拦截器(给保安定义任务)

(2):注册配置拦截器(给保安分配拦截表)

二:DispatcherServlet

1:DispatcherServlet初始化

2:DispatcherServlet处理请求

 三:总结


一:拦截器

1:拦截器作用

兄弟们今天讲一讲拦截器啊,也是好久没有讲过拦截器了,将军说过一句话啊,飞机一定要会飞,船一定要在水上开,握手一定要伸手

\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/

那顾名思义啊,拦截器就是要拦截东西的呀,那我们听拦截的最多场景那肯定就是导弹拦截啊

当然哈,导弹拦截我们可能接触不到,但是下面的这个拦截,你肯定是见过的

咱想想,假如有一个人姓王,他想找隔壁小区里面的寡妇翠花交流一下学习经验,如果这个小区没有保安,还不用进出刷卡,那让老王天天想学习就学习,真让老王考上了怎么办。

真让老王考上了那肯定不行,那我转头一想,在小区门口给你安一个保安他不就炸了吗

保安(拦截器):你停下干什么的(拦截请求)

老王(请求):我想找翠花交流下学习经验(进行的业务)

保安(拦截器):你等一下啊,让我查一下拦截表(配置的拦截路径),翠花是不能随便见的(访问路劲/“翠花”被拦截),但如果你有令牌的话还是能见的我查查如果正确(需要经过拦截器校验才能访问)

接下来会有两种情况

(1)老王拿出了令牌,保安检查没问题放行(校验通过),老王进小区和翠花交流经验

(2)老王没拿出令牌,被保安一拳打飞

总结一下:拦截器主要用于在请求处理中的拦截操作,在对请求处理之前,需要先用拦截器进行一次预处理,判断一下你有没有权限进行这个处理请求,就比如说,有人想往你的卡的取一个亿,结果人家没有登录就直接把你的钱全取走了,那肯定是不行的,必须用拦截器校验一下用户有没有登录,进行身份认证一下,才能够取钱

2:拦截器使用

(1):定义拦截器(给保安定义任务)

定义一个拦截器很简单,就只用实现HandlerInterceptor接口就行了

然后重写它里面的三个方法preHandle(),postHandle(),afterCompletion()

preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true: 继续执⾏后续操作; 返回false: 中断后续操作.
postHandle()⽅法:⽬标⽅法执⾏后执⾏
afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏,但由于现在很少涉及视图了,一般用不上

 

如下代码我只给prehandler方法重写了一下,加入了一些逻辑,大概意思是从请求头中获取一下令牌,然后对令牌解析一下,如果解析成功了放行,如果不成功那就直接拦截(一拳打飞),

就是上面保安查看老王令牌正确不正确的过程

至于postHandle(),我就没定义,因为人家都进小区了,再出来的时候再排查的意义就不是很大了,当然如果你想搞些什么事,可以根据业务场景自己写一些逻辑来处理。

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

/*
* 业务请求之前调用
* @param request  请求报文
* @param response  响应报文
* @param handler
* */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头
       String token =  request.getHeader("user_token");
        log.info("请求头:{}",request.getHeader("user_token"));
        log.info("获取路径:{}",request.getRequestURI());
        //令牌解析
      Claims claims =  JWTUtil.parseJWT(token);
      if (null == claims){
          log.error("解析JWT令牌失败");
          response.setStatus(401);
          return false;
      }
      log.info("解析令牌成功,放行!");
      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);
    }
}

(2):注册配置拦截器(给保安分配拦截表)

给保安已经安排好干什么了,现在保安就得明白,你干什么该查,干什么不该查了

如下事注册配置拦截器的代码,只需要实现WebMvcConfigurer这个接口,然后重写addInterceptors()这个方法就可以了

@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
private final List<String> excludes = Arrays.asList(//不用拦截哪些路径
        "/**/*.html",
        "/css/**",
        "/js/**",
        "/pic/**",
        "/*.jpg",
        "/*.png",
        "/favicon.ico",
        "/**/login",
        "/user/register",
        "/user/verification-code/send",
        "/winning-records/show"
);
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//加入哪个拦截器
                .addPathPatterns("/**").//拦截的路径
                excludePathPatterns(excludes);//不拦截的路径
    }
}

方法解释

registry.addInterceptor(loginInterceptor)//加入哪个配置好的拦截器,就是咱刚刚配好的那个拦截器
                .addPathPatterns("/**").//拦截的路径,给保安发的拦截表,请求的哪些路径应该拦截
                excludePathPatterns(excludes);不拦截的路径,有哪些路径是不用拦截的上面代码中配置的excludes链表就是说哪些请求是不需要拦截的

拦截路径的写法

拦截路径
含义
举例
/*
⼀级路径
能匹配/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

以上只要有某一个请求访问服务器的时候,就会经过拦截器校验一下了,不会出现什么请求都能进的情况了 ,那么你就不好奇它是怎么实现的吗?

二:DispatcherServlet

当我们配置好拦截器后,当我们在访问页面的时候,控制台中会出现下面三段日志,它们都是有关于DispatcherServlet的,那么它是什么呢?

先说结论:DispatcherServlet 是 ​Spring MVC 框架的核心组件,它充当了前端控制器(Front Controller)的角色,负责接收所有 HTTP 请求,并根据配置将请求分发给对应的处理组件(如 Controller)

它的核心作用有以下三种

统一入口所有 HTTP 请求首先由 DispatcherServlet 接收,避免每个请求单独处理。

请求分发:根据请求的 URL、参数等信息,将请求路由到对应的 Controller 方法。

协调组件:整合 Spring MVC 的其他组件(如处理器映射、视图解析器等),完成请求处理的全流程

在HTTP请求被我们自己定义的方法处理之前,DispatcherServlet会统一的接收所有的HTTP请求,这不就给拦截器的实现提供了天然的支持了吗?

1:DispatcherServlet初始化

上面控制台中三条关于DispatcherServlet的信息,其实都是它初始化的信息,那么DispatcherServlet是怎么进行初始化的呢?

我们先捋清一下调用关系,

DispatcherServlet继承了FramworkServlet,

FramwtorkServlet继承了HttpServletBean,

HttpServletBean继承了一个HttpServlet(这个继承了HttpServlet就证明了Spring是基于Servlet开发的)

HttpServletBean中有一个init()方法,init方法中调用了一个initServletBean()方法,这个initServletBean()方法由HttpServletBean的子类FramworkServlet来实现,initServletBean()源代码如下图

这里打印的日志就是我们上面图中,初始化DispatcherServlet 时打印的日志,然后会在这个方法中调用initWebApplicationContext()方法,创建ApplicationContext容器。

在 initWebApplicationContext()方法中,又会调用onRefresh方法来初始化SpringMVC容器

 

onRefresh方法由FramwtorkServlet的子类DispatcherServlet来实现,在onRefresh()方法调用的initStrategies方法中,对九大组件进行初始化

这九大组件我们只说一下4和5

4.初始化处理器映射器HandlerMappings:
处理器映射器的作⽤
1:通过处理器映射器找到对应的 处理器适配器,将请求交给适配器处理;
2:缓存每个请求地址URL对应的位置(Controller.xxx ⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取 到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射 器,则默认BeanNameUrlHandlerMapping作为处理器映射器
5.  初始化处理器适配器HandlerAdapter:
处理器适配器的作⽤
通过调⽤具体的⽅法来处理具体的请求;如果在 ApplicationContext发现handlerAdapter,则从ApplicationContext中获取到所有的 HandlerAdapter,并进⾏排序;
如果在ApplicationContext中没有发现处理器适配器,则默认SimpleControllerHandlerAdapter作为处理器适配器

其他的我们不做过多的讨论,感兴趣的兄弟们可以自己去查一下, 到此为止DispatcherServlet的初始化完毕。

大致调用关系如下图所示,如果没看明白,可以按照这个图自己点点源码看一遍

2:DispatcherServlet处理请求

DispatcherServlet是交给doDispatch处理请求,doDispatch的源代码如下,处理请求就这一个方法,比较长,在关键的地方我加了注释方便理解大概流程如下

请求包装​ → 2. ​处理器匹配​ → 3. ​缓存验证​ → 4. ​拦截器前置处理​ → 5. ​执行处理器​ → 6. ​视图渲染​ → 7. ​拦截器后置处理​ → 8. ​异常/结果处理​ → 9. ​资源清理

/**
 * DispatcherServlet 的核心方法,负责处理所有HTTP请求的分发逻辑。
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request; // 实际处理的请求对象(可能是multipart请求的包装)
    HandlerExecutionChain mappedHandler = null;     // 请求对应的处理器执行链(包含Handler和Interceptors)
    boolean multipartRequestParsed = false;         // 标记是否为multipart请求
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); // 获取异步管理器

    try {
        try {
            ModelAndView mv = null;                 // 处理器返回的ModelAndView结果
            Exception dispatchException = null;     // 处理过程中抛出的异常

            try {
                // 1. 检查是否为multipart请求(如文件上传),如果是则包装为MultipartHttpServletRequest
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = (processedRequest != request); // 标记是否已处理multipart

                // 2. 根据当前请求找到对应的处理器执行链(HandlerExecutionChain)
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    // 找不到处理器,返回404错误
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                // 3. 获取支持该处理器的适配器(HandlerAdapter)
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                // 4. 处理HTTP缓存(Last-Modified头)
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    // 检查资源是否未修改,如果是则直接返回304状态码
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                // 5. 执行拦截器的preHandle()方法(若任一拦截器返回false,终止请求)
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 6. 实际调用处理器(Controller方法),返回ModelAndView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // 7. 如果已开始异步处理,直接返回(响应将由异步线程处理)
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                // 8. 处理默认视图名(当返回的ModelAndView未设置视图名时,根据请求路径生成)
                this.applyDefaultViewName(processedRequest, mv);
                
                // 9. 执行拦截器的postHandle()方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception ex) {
                // 捕获处理器或拦截器抛出的异常
                dispatchException = ex;
            } catch (Throwable err) {
                // 将其他Throwable转换为ServletException
                dispatchException = new ServletException("Handler dispatch failed: " + err, err);
            }

            // 10. 处理分发结果(渲染视图或处理异常)
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            // 处理过程中发生异常,触发拦截器的afterCompletion()
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Throwable err) {
            // 转换为ServletException后触发afterCompletion()
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new ServletException("Handler processing failed: " + err, err));
        }
    } finally {
        // 最终清理逻辑
        if (asyncManager.isConcurrentHandlingStarted()) {
            // 异步请求处理中
            if (mappedHandler != null) {
                // 调用拦截器的afterConcurrentHandlingStarted()
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
            // 标记multipart请求已处理
            asyncManager.setMultipartRequestParsed(multipartRequestParsed);
        } else if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
            // 同步请求且处理过multipart:清理临时资源(如上传的临时文件)
            this.cleanupMultipart(processedRequest);
        }
    }
}

其中在applyPreHandle方法我们点进去就会看到,HandlerInterceptor,这不正是我们在实现拦截器时要实现的接口吗。 

 三:总结

这篇文章啥也没说,就是说了一些

拦截器的概念,

以及怎么实现和注册一个拦截器

然后就是说一下DispatcherServlet是什么,

以及初始化流程,

以及它在接收请求的时候会干什么

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值