SpringMVC之拦截器(Interceptor)

一、概述

Spring Boot中,拦截器是一种用于拦截和处理HTTP请求的机制。它是Spring框架提供的一种中间件,用于在请求到达控制器(Controller)之前或之后执行一些共享的逻辑。

Spring Boot的拦截器基于Spring MVC框架中的HandlerInterceptor接口实现。通过创建一个自定义的拦截器类并实现HandlerInterceptor接口,可以定义拦截器要执行的逻辑和行为。

二、定义拦截器

Spring Boot中定义拦截器十分的简单,只需要创建一个拦截器类,并实现HandlerInterceptor接口即可。

HandlerInterceptor接口中定义以下3个方法,如下表:

返回值类型 方法声明 描述
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回true表示继续向下执行,返回false表示中断后续操作。
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。

它接收三个参数:

  • HttpServletRequest request: 表示当前的HTTP请求
  • HttpServletResponse response: 表示当前的HTTP响应
  • Object handler: 表示被拦截的处理器(一般是Controller中的方法)

三、使用拦截器

3.1 自定义拦截器

java 复制代码
@Component
public class LoginInterceptor implements HandlerInterceptor {
    //调用目标方法之前执行的方法
    //如果返回ture表示拦截器验证成功,执行目标方法
    //如果返回false表示拦截器验证失败,不再继续执行后续业务
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        //用户登录判断业务
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("session_userinfo") != null) {
            //用户已登录
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

代码中的preHandle方法是拦截器的主要方法,在目标方法调用之前执行。

preHandle方法中,首先通过request.getSession(false)获取当前请求的HttpSession对象(如果存在的话),然后判断该HttpSession对象是否为null并且是否存在名为"session_userinfo"的属性。如果这个条件成立,说明用户已经登录,可以继续执行后续的业务,于是返回true,否则验证失败,将HTTP响应的状态码设置为401,表示未授权,然后返回false,不再继续执行后续业务。

3.2 注册拦截器

java 复制代码
@Configuration
public class MyConfig implements WebMvcConfigurer {

    //注入
    @Autowired
    private LoginInterceptor loginInterceptor;

    //将拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //拦截所有的 url
                .excludePathPatterns("/user/login")//排除url: /user/login (登录)
                .excludePathPatterns("/user/reg") //排除url: /user/reg   (注册)
                .excludePathPatterns("/image/**")//排除 image(图像) 文件夹下的所有文件
                .excludePathPatterns("/**/*.js")//排除任意深度目录下的所有".js"文件
                .excludePathPatterns("/**/*.css");
    }
}

UserController:

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    @RequestMapping("/reg")
    public String reg(){
        return "reg";
    }
}

在配置类中,重写了addInterceptors方法,该方法用于注册拦截器。在这里,通过调用InterceptorRegistryaddInterceptor方法来添加拦截器,并设置拦截的路径和排除的路径。

具体地,通过调用addInterceptor(loginInterceptor)来添加LoginInterceptor拦截器。然后使用addPathPatterns方法指定需要拦截的URL路径模式,这里使用"/**"表示拦截所有的URL。使用excludePathPatterns方法来排除一些特定的URL路径,这些路径不会被拦截。

"//*.js","":表示零个或多个路径段(目录或文件夹),可以匹配任意深度的目录结构。 "/*.js":表示以".js"结尾的文件名。

常见的拦截配置:

拦截路径 含义 举例
/* 拦截一级路径 能拦截 /user,/login,不能拦截 /user/login
/** 拦截任意路径 拦截所有
/user/* 拦截/user的下一级路径 能拦截 /user/getList,不能拦截 /user/getList/1 和 /user
/user/** 拦截/user下的所有路径 拦截/user下的所有路径,但是不能拦截 /book/getList

此外拦截器不仅可以拦截项目中的URL,还可以拦截静态资源(html,图片等)。

四、执行流程

4.1 内部执行流程

flowchart TD A[请求] --> C(Interceptor#preHandle) C --> D[Controller] D --> E(Interceptor#postHandle) E --> F(Interceptor#afterCompletion) F --> H[响应]
  1. 添加拦截器后,执行Controller的方法之前,请求会先被拦截器拦截住。
  2. 执行preHandle()方法,这个方法需要返回一个布尔类型的值。
  3. 如果返回true,就表示放行本次操作, 继续访问controller中的方法。
  4. 如果返回false,则不会放行(controller中的方法也不会执行)。
  5. controller当中的方法执行完毕后,再回过来执行postHandle()这个方法以及afterCompletion()方法。
  6. 执行完毕之后,最终给浏览器响应数据。

4.2 多个拦截器执行流程

拦截器可以注册多个,可以通过注册顺序的流程执行,也可以通过设置注册的order()方法设置执行顺序。

java 复制代码
//将拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**") //拦截所有的 url
            .excludePathPatterns("/**/user/login")//排除url: /user/login (登录)
            .order(1)

    registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**") //拦截所有的 url
            .excludePathPatterns("/**/user/login")//排除url: /user/login (登录)
            .order(2)
        
    // 更多拦截器规则... 
}

五、实际案例

5.1 统一访问前缀添加

所有请求地址添加test前缀:在WebMvcConfigurer接口中,configurePathMatch方法用于配置路径匹配规则。

java 复制代码
@Configuration
public class MyConfig implements WebMvcConfigurer {
    //统一访问前缀的添加
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("test", new Predicate<Class<?>>() {
            @Override
            public boolean test(Class<?> aClass) {
                return true;
            }
        });
    }
}

在这个例子中,传递给addPathPrefix方法的前缀是"test",而Predicate对象是一个匿名内部类,实现了Predicate<Class<?>>接口。Predicate接口是Java 8中引入的函数式接口,它的test方法用于判断传入的类是否符合条件。

在这个匿名内部类中,test方法被重写为总是返回true,这意味着所有的类都符合条件,都会被添加统一访问前缀。

因此,通过这段代码的配置,所有的请求路径都会在前面添加"test"前缀。例如,原始路径为"/example",添加了前缀后的路径就变为"/test/example"。这样可以实现对请求路径的统一处理。

注意:如果加了前缀,拦截器的排除路径也要跟着改动:

java 复制代码
//将拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**") //拦截所有的 url
            .excludePathPatterns("/**/user/login")//排除url: /user/login (登录)
            .excludePathPatterns("/**/user/reg") //排除url: /user/reg   (注册)
            .excludePathPatterns("/**/image/**")//排除 image(图像) 文件夹下的所有文件
            .excludePathPatterns("/**/*.js")//排除任意深度目录下的所有".js"文件
            .excludePathPatterns("/**/*.css");
}

5.2 配置本地资源映射路径

实现WebMvcConfigurer,重写addResourceHandlers(ResourceHandlerRegistry registry)方法

  • addResourceHandler():添加的是访问路径
  • addResourceLocations(): 添加的是映射后的真实路径,映射的真实路径末尾必须加/,不然映射不到,/适用于windowslinux

代码:

java 复制代码
@Configuration
public class MyWebMVCConfig implements WebMvcConfigurer {

    @Value("${file.location}") //         D:/test/
    String fileLocation;  // 这两个是路径

    @Value("${file.path}") //         /file/**
    String filePath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //匹配到resourceHandler,将URL映射至location,也就是本地文件夹
        registry.addResourceHandler(filePath)
                //这里最后一个/必须写
                .addResourceLocations("file:///" + fileLocation);
    }
}

这段代码意思就配置一个拦截器,如果访问路径是addResourceHandler中的filePath这个路径那么就映射到访问本地的addResourceLocations的参数的这个路径上,这样就可以让别人访问服务器的本地文件了,比如本地图片或者本地音乐视频什么的。

六、应用场景

拦截器在SpringBoot中扮演着多面手的角色,适用于多种场景,有助于增强应用程序的功能和安全性。以下是拦截器的一些典型应用:

  • 身份验证与授权 使用拦截器检查用户的身份验证状态和权限级别,确保只有经过验证且有适当权限的用户能够访问特定资源。这为应用程序的安全性提供了第一道防线。
  • 日志记录与审计: 在请求处理的不同阶段(如请求到达前、响应发送后)插入日志记录逻辑,帮助开发者跟踪系统行为、调试问题,并满足审计需求。
  • 性能分析与监控: 拦截器可用于测量请求处理时间,收集性能指标,识别潜在的性能瓶颈,从而优化系统的响应速度和服务质量。
  • 跨域资源共享(CORS)配置: 当构建RESTful API时,通过拦截器设置适当的HTTP响应头来允许或限制跨域请求,简化前端与后端之间的交互。
  • 异常捕捉与友好错误处理: 捕获请求处理过程中抛出的异常,提供统一的错误处理机制,确保最终用户接收到格式化良好且友好的错误信息,而不是直接暴露底层技术细节
  • 数据预处理与后处理: 对进入的数据进行验证或转换,确保其符合预期格式;同时,在响应生成之后对输出数据进行格式化或加密等操作,以满足业务逻辑要求。
  • 流量控制与限流策略: 实施流量控制措施,例如限制同一IP地址在一定时间内发起的请求数量,或者控制并发请求的数量,以保护系统免受过载影响。

通过合理运用拦截器,开发人员可以在不改变原有业务逻辑的前提下,轻松地向应用程序添加这些额外的功能,提高系统的灵活性、安全性和可维护性。

七、拓展

7.1 过滤器和拦截器的区别

过滤器和拦截器非常相似,但是它们有很大的区别

  1. 过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
  2. 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api,不需要依赖spring
  3. 过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射。
  4. Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
  5. Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行。
  6. Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

最简单明了的区别就是

  1. 过滤器可以修改request,而拦截器不能。过滤器需要在servlet容器中实现,拦截器可以适用于javaEEjavaSE等各种环境
  2. 拦截器可以调用IOC容器中的各种依赖,而过滤器不能
  3. 过滤器只能在请求的前后使用,而拦截器可以详细到每个方法
相关推荐
爱勇宝5 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒9 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码11 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo11 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志11 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..11 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects12 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰12 小时前
一些基础和问题解决
后端
陈随易12 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN12 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp