目录
一:拦截器
1:拦截器作用
兄弟们今天讲一讲拦截器啊,也是好久没有讲过拦截器了,将军说过一句话啊,飞机一定要会飞,船一定要在水上开,握手一定要伸手
\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/\😭/
那顾名思义啊,拦截器就是要拦截东西的呀,那我们听拦截的最多场景那肯定就是导弹拦截啊
当然哈,导弹拦截我们可能接触不到,但是下面的这个拦截,你肯定是见过的
咱想想,假如有一个人姓王,他想找隔壁小区里面的寡妇翠花交流一下学习经验,如果这个小区没有保安,还不用进出刷卡,那让老王天天想学习就学习,真让老王考上了怎么办。
真让老王考上了那肯定不行,那我转头一想,在小区门口给你安一个保安他不就炸了吗
保安(拦截器):你停下干什么的(拦截请求)
老王(请求):我想找翠花交流下学习经验(进行的业务)
保安(拦截器):你等一下啊,让我查一下拦截表(配置的拦截路径),翠花是不能随便见的(访问路劲/“翠花”被拦截),但如果你有令牌的话还是能见的我查查如果正确(需要经过拦截器校验才能访问)
接下来会有两种情况
(1)老王拿出了令牌,保安检查没问题放行(校验通过),老王进小区和翠花交流经验
(2)老王没拿出令牌,被保安一拳打飞
总结一下:拦截器主要用于在请求处理中的拦截操作,在对请求处理之前,需要先用拦截器进行一次预处理,判断一下你有没有权限进行这个处理请求,就比如说,有人想往你的卡的取一个亿,结果人家没有登录就直接把你的钱全取走了,那肯定是不行的,必须用拦截器校验一下用户有没有登录,进行身份认证一下,才能够取钱
2:拦截器使用
(1):定义拦截器(给保安定义任务)
定义一个拦截器很简单,就只用实现HandlerInterceptor接口就行了
然后重写它里面的三个方法preHandle(),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
其他的我们不做过多的讨论,感兴趣的兄弟们可以自己去查一下, 到此为止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是什么,
以及初始化流程,
以及它在接收请求的时候会干什么