过滤器Filter和拦截器Interceptor

一、过滤器Filter

我们搭建一个简单demo:

1、自定义一个Filter类

public class LoginAuthFilter implements Filter {

    Logger logger = LoggerFactory.getLogger(LoginAuthFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //在Filter生命周期只会调用一次,可以读取配置文件
        logger.info("【过滤器】初始化");

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.info("【过滤器】开始执行");
        filterChain.doFilter(servletRequest, servletResponse);
        logger.info("【过滤器】执行结束");
    }
}

2、注册过滤器


@Configuration
public class LoginAuthConfig {
    /**
     * 注册过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean getLoginAuthFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        LoginAuthFilter filter = new LoginAuthFilter();
        registrationBean.setFilter(filter);
        //urlPatterns配置哪些路径的请求进入过滤器,/*表示所有
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

3、项目演示

@RestController
public class SysUserController {
    Logger logger = LoggerFactory.getLogger(SysUserController.class);
    @GetMapping(value = "/sysUser/getSysUserById")
    public String getSysUserById(String userId) {
        logger.info("Controller:进入方法getSysUserById()");
        return "嘻嘻嘻";
    }
 }

启动项目,SpringBoot初始化的时候,我们可以看到LoginAuthFilter类中的init()方法执行了一次

过滤器Filter和拦截器Interceptor_第1张图片

接着我们使用postman请求接口,在控制台看到:

图片

在接口请求进入controller层前后,可以在过滤器Filter中处理一些前置后置逻辑。

二、拦截器Interceptor

1、自定义一个Interceptor类

@Component
public class MyInterceptor implements HandlerInterceptor {

    Logger logger = LoggerFactory.getLogger(MyInterceptor.class);

   @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("【拦截器】控制器方法调用前");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("【拦截器】控制器方法调用后,视图渲染前");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("【拦截器】整个请求完成后调用");
    }
}

2、注册拦截器


@Configuration
public class InterceptorConfig implements WebMvcConfigurer{
    @Autowired
    private MyInterceptor myInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor);
    }
}

3、项目演示

@RestController
public class SysUserController {
    Logger logger = LoggerFactory.getLogger(SysUserController.class);
    @GetMapping(value = "/sysUser/getSysUserById")
    public String getSysUserById(String userId) {
        logger.info("Controller:进入方法getSysUserById()");
        return "嘻嘻嘻";
    }
 }

启动项目,使用postman请求接口,控制台输出如下:

图片

在接口请求进入controler层的前后,拦截器Interceptor中,都有对应的方法,可以处理二开逻辑。

三、过滤器Filter和拦截器Interceptor的区别

  • 1、触发时机不同

  • 2、过滤器Filter中无法注入Bean,但是拦截器Interceptor中可以

当我们的项目中,既有过滤器Filter,又有拦截器Interceptor的时候,又出现什么的现象呢?

过滤器Filter和拦截器Interceptor_第2张图片

过滤器Filter和拦截器Interceptor_第3张图片

通过断点和日志输出,引出过滤器Filter和拦截器Interceptor一个区别:

1、触发时机不同

过滤器Filter和拦截器Interceptor_第4张图片

  • 过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

  • 拦截器 Interceptor 是在请求进入DispathServlet后,在进入Controller之前进行预处理的,视图渲染之后请求结束。我们在自定义类Interceptor的方法中打上断点,就可以看到都是在什么时机进入。

2、过滤器Filter中无法注入Bean,但是拦截器Interceptor中可以

我们写一个Service类,分别在过滤器Filter和拦截器Interceptor中注入,看下结果:

@Service
public class TestServiceImp {
    public void test(){
        System.out.println("测试方法");
    }
}

过滤器Filter和拦截器Interceptor_第5张图片

过滤器Filter和拦截器Interceptor_第6张图片

可以看到拦截器Interceptor中,是可以成功注入的,过滤器Filter中,testServiceImpl的值为null,无法注入。

这是因为我们在注册过滤器Filter的时候,使用的是new LoginAuthFilter的方式,过滤器是Servlet规范的一部分,而不是Spring框架的组件,需要通过new 关键字创建实例。Servlet容器并不了解Spring容器的上下文,因此它无法自动注入Spring的Bean到过滤器中。

而实例化拦截器Interceptor的时候,使用的@Autowired注解的方式。拦截器是Spring MVC框架的一部分,由Spring容器管理,因此可以通过@Autowired注解直接注入。

那如果我在LoginAuthFilter上,同样加上注解@Component,在配置类中使用@Autowired注入过滤器类,那LoginAuthFilter中的bean 可以成功注入吗?

答案是肯定的,可以成功注入bean。

这是为什么呢?不是说过滤器Filter不能注入bean吗?

    在一些特定情况下,你确实可以使用@Autowired注解来将过滤器(Filter)注入到Spring中,类似于拦截器(HandlerInterceptor)。   

     这是因为在Spring Boot等现代Web应用中,Spring容器和Servlet容器通常是集成的,而且Spring Boot提供了一些方便的工具来简化这个集成过程。

    尽管在某些情况下使用@Autowired注解是可能的,但需要注意的是,这种方式可能导致在一些纯Servlet容器环境下无法正常工作,因为过滤器的生命周期是由Servlet容器管理的。

这是我在网上查询到的答案。同时我还发现一个有趣的现象。

如果我们在LoginAuthFilter上,加上注解@Component,那么按理说testServiceImp 的bean应该可以成功注入,并且在后续逻辑中,可以正常使用才对。

但是如果你的注册过滤器的类LoginAuthConfig中,还是通过new LoginAuthFilter方法而不是@Autowired注入LoginAuthFilter,你会发现在拦截请求进入LoginAuthFilter中的时候,testServiceImp的bean还是为null 。


@Configuration
public class LoginAuthConfig {
    /**
     * 注册过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean getLoginAuthFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        //exclusionsList 可以根据配置设置白名单
        LoginAuthFilter filter = new LoginAuthFilter();
        registrationBean.setFilter(filter);
        //urlPatterns配置哪些路径的请求进入过滤器,/*表示所有
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

过滤器Filter和拦截器Interceptor_第7张图片

百思不得其解吧。不过好在springBoot启动的时候,我还发现一个情况:LoginAuthFilter中的init()方法,打印了两次日志,那也就意味着创建了两次实例。

初步猜测,是不是请求拦截的时候,使用的LoginAuthFilter是注册过滤器的时候new LoginAuthFilter的,而不是通过注解扫描创建出来的loginAuthFilter呢?

为了验证猜测,我们在init中打上断点,看下两个实例的情况。

过滤器Filter和拦截器Interceptor_第8张图片

过滤器Filter和拦截器Interceptor_第9张图片

springBoot启动完成后,请求接口:

过滤器Filter和拦截器Interceptor_第10张图片

果然,此时请求进入的是:LoginAuthFilter@6189,对应的是new LoginAuthFilter创建的实例。

为什么拦截请求的时候,选择了使用new LoginAuthFilter的实例,而不是Spring注入的实例呢?这是我产生的第二个疑问。

通过debug查看源码,发现事情并不是我想象的这样必须是2选择1的,实际上注册的所有Filter实例,都会链式调用执行的。

过滤器Filter和拦截器Interceptor_第11张图片

而我们在代码中所写的filterChain.doFilter(servletRequest, servletResponse),则是触发下一个Filter执行的逻辑。

new 方法创建的实例,只是在通过注解扫描创建的bean之前执行了,异常则中断了后续Filter的执行。

四、过滤器Filter和拦截器Interceptor的适用场景

适用场景:

  • 过滤器:广泛用于对请求和响应进行预处理和后处理的场景。例如,可以在过滤器中进行日志记录、字符编码转换、权限验证等操作。过滤器在请求进入Servlet容器或在响应返回客户端之前起作用。

  • 拦截器:主要用于在请求处理的不同阶段进行拦截和处理。在Spring等框架中,拦截器常用于对控制器的方法进行前后处理,如日志记录、权限检查、性能监控等。

你可能感兴趣的:(java,spring,spring,boot)