
🎬 那我掉的头发算什么 :个人主页
🔥 个人专栏 : 《javaSE》《数据结构》《数据库》《javaEE》
⛺️待到苦尽甘来日

文章目录
拦截器
快速入门
拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法执行的前后,根据业务需要执行预先设定的代码。
也就是说,拦截器允许开发人员提前定义一些通用逻辑,在用户的请求被处理前、响应返回后执行;也可以在用户请求处理前直接阻止其执行。
在拦截器中,开发人员可以实现应用程序中的一些通用性操作。比如通过拦截器拦截前端发来的所有请求,判断 Session 中是否存在登录用户的信息:如果存在则放行请求,继续处理业务;如果不存在则拦截请求,拒绝后续处理 。

就好比上学时进校门出校门需要带出入证,保安就是拦截器,他会检查你是否带了出入证,带了放行,没带不放行。
定义拦截器
java
package com.hbu.book.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
preHandle () 方法:在目标方法执行前执行。返回 true 时,继续执行后续操作(如目标方法、其他拦截器逻辑);返回 false 时,直接中断后续所有操作。
postHandle () 方法:在目标方法执行完成后执行。
afterCompletion () 方法:在视图渲染完毕后执行,是拦截器中最后执行的方法(后端开发中现在几乎不涉及视图相关开发,暂时无需深入了解)。
注册配置拦截器
java
package com.hbu.book.config;
import com.hbu.book.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自己定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");//拦截器拦截的请求路径
}
}
注册完成之后拦截器就算真正配置到了项目中了。下面我们来把拦截器代码简化一下,先看看他是怎么使用的:


当我们产生请求时,也就是访问一次Controller层时,在进入控制层之前会先执行preHandle逻辑,业务逻辑执行完了会执行postHandle逻辑。
因此,如果我们把preHandle的返回结果改成false,那么所有的请求根本就不会进入到Controller。就算只是访问的前端页面,但是页面的返回还是需要经过后端的,最终页面也就不会被加载出来:


拦截器详解
拦截路径

我们在设置拦截路径的时候还可以使用excludePathPatterns()来指定不拦截哪些请求。
比如,我们可以在拦截器中定义检查Session来确定用户是不是登录了来防止别人恶意进入我们网站,此时就可以对除了登录路径/User/Login以外的路径进行拦截:
java
package com.hbu.book.config;
import com.hbu.book.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自己定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//拦截器拦截的请求路径
.excludePathPatterns("/user/login");
}
}
拦截器路径设置:

拦截器执行流程
正常流程:

有了拦截器之后:

添加拦截器后,执行 Controller 的方法之前,请求会先被拦截器拦截,执行 preHandle() 方法。该方法需要返回一个布尔类型的值:
返回 true:表示放行本次请求,继续执行 Controller 中的目标方法;
返回 false:表示拒绝放行,Controller 中的方法不会被执行。
当 Controller 中的方法执行完毕后,会依次执行 postHandle() 方法和 afterCompletion() 方法,待这些方法执行完毕后,最终才会向浏览器响应数据。
登录校验
学会了拦截之后,我们就可以对图书管理系统的登录检验功能进行拦截啦。
项目结构:

定义注册拦截器
java
package com.hbu.book.interceptor;
import com.hbu.book.constant.Constants;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.jspecify.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession = request.getSession(false);
if(httpSession != null && httpSession.getAttribute(Constants.SESSION_USER_KEY)!=null){
return true;
}
response.setStatus(401);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("方法执行之后执行-postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
java
package com.hbu.book.config;
import com.hbu.book.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自己定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//拦截器拦截的请求路径
.excludePathPatterns("/user/login")
.excludePathPatterns("/**/*.js") //排除前端静态资源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.html");
}
}
此时我们就完成了对不通过登录接口想直接访问我们的页面的行为的检验。
java
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest, HttpSession session){
//参数校验
//返回数据
if (session.getAttribute(Constants.SESSION_USER_KEY)==null){
return Result.unlogin();
}
UserInfo userInfo = (UserInfo)session.getAttribute(Constants.SESSION_USER_KEY);
if (userInfo==null || userInfo.getId()<=0){
//用户未登录
return Result.unlogin();
}
PageResp<BookInfo> listByPage = bookService.getListByPage(pageRequest);
log.info("此页码的数据:{}",listByPage);
return Result.success(listByPage);
}
因此,这里的登录检验就可以删除了。
DispatcherServlet 源码分析

Tomcat 启动后,Spring MVC 核心类 DispatcherServlet 负责控制请求的整体执行顺序,具体流程如下:
1.所有客户端请求都会首先进入 DispatcherServlet,并执行其核心调度方法 doDispatch();
2.若当前请求配置了拦截器(Interceptor),则优先执行拦截器的 preHandle() 方法;
3.若 preHandle() 方法返回 true,请求会继续向后执行,进入对应的 Controller 层方法并完成业务逻辑处理;
4.Controller 方法执行完毕后,会回溯执行拦截器的 postHandle() 方法和 afterCompletion() 方法;
5.上述流程全部完成后,结果返回至 DispatcherServlet,最终由 DispatcherServlet 将响应数据返回给浏览器。

Tomcat 启动后,DispatcherServlet作为前端控制器,是所有 HTTP 请求的统一入口。它不负责具体业务逻辑,只做请求分发、组件协调、流程控制,相当于整个 Spring MVC 的「指挥中心」。
而它所有的核心调度逻辑,都封装在doDispatch()方法中 ------ 这也是我们分析源码的核心入口。
源码:(仅保留主干代码,关键步骤标注注释版)
java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1. 预处理:判断是否为文件上传请求,做包装处理
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 2. 根据当前请求,匹配对应的Handler(Controller中的目标方法)
mappedHandler = getHandler(processedRequest);
// 没有找到Handler,直接返回404异常
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3. 核心:根据Handler,找到对应的HandlerAdapter(适配器)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. 执行拦截器的preHandle()方法
// 返回true则继续,返回false直接中断请求
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 5. 通过HandlerAdapter执行Handler(真正调用Controller方法)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 异步请求直接返回,不执行后续同步逻辑
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 给ModelAndView设置默认视图名
applyDefaultViewName(processedRequest, mv);
// 6. 执行拦截器的postHandle()方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 7. 统一处理结果:渲染视图、执行拦截器afterCompletion()
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} finally {
// 异步请求&文件上传请求的资源清理
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
1.请求预处理:判断是否是文件上传请求,做包装适配;
2.查找 Handler:通过HandlerMapping根据 URL 匹配到目标 Controller 方法(即 Handler);
3.查找 HandlerAdapter:根据 Handler 类型,找到能执行它的适配器;
4.前置拦截:执行所有拦截器的preHandle(),校验通过才继续;
5.执行 Handler:通过 HandlerAdapter 调用 Controller 方法,得到 ModelAndView;
6.后置拦截:执行拦截器的postHandle(),可对结果做二次处理;
7.结果处理:渲染视图、执行拦截器afterCompletion(),最终响应浏览器。
适配器模式
HandlerAdapter在Spring MVC中使用了适配器模式。
适配器模式定义
适配器模式,也叫包装器模式。将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
简单来说就是目标类不能直接使用,通过一个新类进行包装一下,适配调用方使用。把两个不兼容的接口通过一定的方式使之兼容。
比如下面两个接口,本身是不兼容的(参数类型不一样,参数个数不一样等等)。

通过适配器可以让他们兼容


比如生活中插头的转换器也是这样。
适配器模式角色
・Target: 目标接口 (可以是抽象类或接口), 客户希望直接用的接口
・Adaptee: 适配者,但是与 Target 不兼容
・Adapter: 适配器类,此模式的核心。通过继承或者引用适配者的对象,把适配者转为目标接口
・client: 需要使用适配器的对象
咱们前面提到的slf4j门面模式其实也是通过适配器模式兼容的
java
package com.hbu.book.adapter;
public class logback {
public void print(String log){
System.out.println("logback: " + log);
}
}
java
package com.hbu.book.adapter;
public class logbackAdapter implements Slf4j{
private logback logback;
public void log(String log){
logback.print(log);
}
}
java
package com.hbu.book.adapter;
public class Log4j {
public void print(String log){
System.out.println("log4j: " + log);
}
}
java
package com.hbu.book.adapter;
public class Log4jAdapter implements Slf4j{
private Log4j log4j;
public void log(String log){
log4j.print(log);
}
}
java
package com.hbu.book.adapter;
public interface Slf4j {
void log(String log);
}
java
package com.hbu.book.adapter;
public class Client {
public static void main(String[] args) {
Slf4j slf4j = new Log4jAdapter();
Slf4j slf4j1 = new logbackAdapter();
slf4j.log("111");
slf4j1.log("222");
}
}
咱们的Slf4j对用户提供了一个log接口打印日志,但是底层调用的Logback和Log4j提供的方法却是print,不适配。此时就可以通过适配器来兼容。
适配器模式应用场景
一般来说,适配器模式可以看作一种 "补偿模式" ,用来补救设计上的缺陷。应用这种模式算是 "无奈之举", 如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使用适配器模式了所以适配器模式更多的应用场景主要是对正在运行的代码进行改造,并且希望可以复用原有代码实现新的功能。比如版本升级等.
以上就是本篇博客全部内容!