过滤器和拦截器的使用和原理

1. 过滤器和拦截器

过滤器(Filter)
拦截器(Interceptor)
在这里插入图片描述

1.1 区别

过滤器和拦截器触发时机不一样

  • 过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。
  • 请求结束返回也是,是在servlet处理完后,返回给前端之前

拦截器可以获取IOC容器中的各个bean,而过滤器就不行

  • 因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。
  • 过滤器是JavaEE标准只需依赖servlet api ,不需要依赖spring

过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射

Filter依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用

Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行。

Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此拦截器可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

最简单明了的区别就是:

  • 过滤器可以修改request,而拦截器不能
  • 过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境
  • 拦截器可以调用IOC容器中的各种依赖,而过滤器不能
  • 过滤器只能在请求的前后使用,而拦截器可以详细到每个方法

本质区别
   从灵活性上说拦截器功能更强大些,Filter能做的事情它都能做,而且可以在请求前,请求后执行,比较灵活。
   Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验,其他的还是建议用interceptor。

1.2 执行顺序

过滤前-----拦截前-----Action处理-----拦截后-----过滤后。

【过滤器】开始执行
【拦截器】处理前
com.boot.filter.controller.HelloController
Controller:Hello World!
【拦截器】处理中
【拦截器】耗时:20ms
【拦截器】处理后
【拦截器】耗时:20ms
【过滤器】耗时:27ms
【过滤器】结束执行

过滤器(Filter) :可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息
拦截器(Interceptor):可以拿到你请求的控制器和方法,却拿不到请求方法的参数
切片(Aspect): 可以拿到方法的参数,但是却拿不到http请求和响应的对象
在这里插入图片描述
在这里插入图片描述

2. 过滤器

过滤器是处于客户端与服务器资源文件之间的一道过滤网

在访问资源文件之前,通过一系列的过滤器对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应

两种实现方式
1、使用spring boot提供的FilterRegistrationBean注册Filter
2、使用原生servlet注解定义Filter

两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter

2.1 Filter

2.1.1 示例

2.1.1.1 自定义一个TimeFilter类
@Component
@WebFilter(urlPatterns = {
   "/*"})
public class TimeFilter implements Filter{
   
    Logger log = LoggerFactory.getLogger(Logger.class);
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
   
        log.info("【过滤器】初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
   
        log.info("【过滤器】开始执行");
        Long startTime = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        log.info("【过滤器】耗时:" + (System.currentTimeMillis() - startTime)+"ms");
        log.info("【过滤器】结束执行");
    }

    @Override
    public void destroy() {
   
        log.info("【过滤器】销毁");
    }
}
2.1.1.2 配置方式
  • @WebFilter注解的urlPatterns属性配置了哪些请求可以进入该过滤器,/*表示所有请求
  • 除了在过滤器类上加注解外,我们也可以通过FilterRegistrationBean来注册过滤器。定义一个TimeFilterConfig类,加上@Configuration注解表明其为配置类,然后通过FilterRegistrationBean来注册过滤器。
    @Configuration
    public class TimeFilterConfig {
         
        @Bean
        public FilterRegistrationBean timeFilter() {
         
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            TimeFilter timeFilter = new TimeFilter();
            filterRegistrationBean.setFilter(timeFilter);
    
            List<String> urlList = new ArrayList<>();
            urlList.add("/*");
    
            filterRegistrationBean.setUrlPatterns(urlList);
            return filterRegistrationBean;
        }
    }
    
2.1.1.3 测试
@RestController
public class HelloController {
   
    Logger log = LoggerFactory.getLogger(Logger.class);

    @RequestMapping("hello")
    public void hello(){
   
        log.info("Controller:Hello World!");
    }
}

运行结果:

【过滤器】开始执行
ControllerHello World!
【过滤器】耗时:26ms
【过滤器】结束执行

2.2 OncePerRequestFilter

2.2.1 概念

在Spring中,Filter默认继承OncePerRequestFilter

Filter 可以在 Servlet 执行之前或之后调用。当请求被调度给一个 Servlet 时,RequestDispatcher 可能会将其转发给另一个 Servlet。另一个 Servlet 也有可能使用相同的 Filter。在这种情况下,同一个 Filter 会被调用多次。

OncePerRequestFilter是 Spring 框架中的一个抽象类,

  • 用于确保在每个请求中只执行一次过滤操作
  • 它通常用于实现一些需要在每个请求中执行一次的逻辑

    比如日志记录、身份验证等

可以继承 OncePerRequestFilter。

Spring 保证 OncePerRequestFilter 只对指定请求执行一次

2.2.1.1 一般只会请求一次,那为什么还要使用该注解

此方法是**为了兼容不同的web container**,也就是说并不是所有的container都是我们期望的只过滤一次,servlet版本不同,执行过程也不同

  • servlet2.3中Filter会经过一切请求,包括服务器内部使用的forward转发请求<%@ include file=”/login.jsp”%>的情况
  • servlet2.4中Filter默认情况下只过滤外部提交的请求forward和include这些内部转发都不会被过滤
  • 所以,若是在Spring环境下使用Filter的话,还是继承OncePerRequestFilter吧,而不是直接实现Filter接口

2.2.2 OncePerRequestFilter源码解读

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.web.filter;

import java.io.IOException;
import javax.servlet.DispatcherType;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.async.WebAsyncUtils;

public abstract class OncePerRequestFilter extends GenericFilterBean {
   
    // 常量,表示已经过滤的请求属性后缀
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    // 构造函数
    public OncePerRequestFilter() {
   
    }

    // 核心过滤方法,确保每个请求只执行一次过滤操作
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
   
        // 检查请求和响应是否为 HTTP 类型
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
   
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            // 获取已经过滤的请求属性名称
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            // 检查请求是否已经被过滤
            boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
            // 检查是否需要跳过过滤
            if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
   
                if (hasAlreadyFilteredAttribute) {
   
                    // 如果是错误调度类型,执行嵌套错误调度过滤
                    if (DispatcherType.ERROR.equals(request.getDispatcherType())) 
  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

?abc!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值