Java Spring Boot 过滤器的使用与拦截器对比

发布时间 2023-12-14 17:03:36作者: 进击的davis

在 web 应用中,早期在 servlet 中使用 filter过滤器,随着 spring 的发展,不同于依托 servlet容器,拦截器依托 Spring框架 应用也很广泛。

今天主要内容分两部分:

  • 1.filter的使用
  • 2.filter和interceptors对比

filter的使用

导入依赖

<!-- Lombok 工具 -->
<!-- @Data && slf4j -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!-- 导入配置文件处理器,在配置springboot相关文件时候会有提示 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

编写filter

package com.example.springbootfilterdemo.boot.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/*")
public class MyFilter  implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        log.info("过滤器开始过滤...");
        log.info("requestID: " + request.getRequestId());
        filterChain.doFilter(request, response);
    }
}

上面过滤器逻辑简单,只是简单将请求的顺序号打印了。

可以看到,我们的 MyFilter 只是实现了 Filter 接口,我们看看 FIlter接口:

package jakarta.servlet;

import java.io.IOException;

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

其中主要的方法 void doFilter() 有三个参数:

  • 请求
  • 响应
  • 过滤链

当该过滤器处理结束后,就会将请求沿着过滤链传递。

为了效果直观,我们可以添加个 监听器。

编写监听器 MyListener

package com.example.springbootfilterdemo.boot.listener;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@WebListener
public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("监听器开始初始化...");
        ServletContextListener.super.contextInitialized(sce);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("监听器开始销毁");
        ServletContextListener.super.contextDestroyed(sce);
    }
}

监听器也是实现 ServletContextListener接口 ,可以看看这个接口:

package jakarta.servlet;

import java.util.EventListener;

public interface ServletContextListener extends EventListener {
    default void contextInitialized(ServletContextEvent sce) {
    }

    default void contextDestroyed(ServletContextEvent sce) {
    }
}

主要就是声明环境初始化和环境销毁两个方法,我们实现时加入打印信息,便于在日志中看到。

注册过滤器

package com.example.springbootfilterdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class SpringBootFilterDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootFilterDemoApplication.class, args);
    }

}

我们只需要在项目启动程序中加入 @ServletComponentScan注解,过滤器就可以使用了。

然后我们再添加几个简单的接口,分别是:

  • index
  • 注册用户
  • 获取所有用户信息
  • 删除用户

控制器类

index控制器

package com.example.springbootfilterdemo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    @GetMapping("/index")
    public Object index() {
        return "Hello, this is Spring Boot index api";
    }
}

用户类控制器

package com.example.springbootfilterdemo.controller;

import com.example.springbootfilterdemo.model.User;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequestMapping("/user")
public class UserController {

    private static Map<String, Object> users = new ConcurrentHashMap<>();

    @PostMapping("/add")
    public Object add(@RequestBody User user) {
        users.put(user.getName(), user);
        return "add user ok!";
    }

    @GetMapping("/allUsers")
    public Object getAllUsers() {
        return users;
    }

    @DeleteMapping("/delete")
    public Object delete(@RequestBody User user) {
        String name = user.getName();
        users.remove(name);

        return "delete user ok!";
    }

}

这里的 User 就是个 Bean:

package com.example.springbootfilterdemo.model;

import lombok.Data;

@Data
public class User {

    private String name;

    private Integer age;

    private String password;
}

测试结果:

image.png

image.png

image.png

image.png

这样看过滤器的使用是不是很简单呢。

过滤器与拦截器的对比

开头一张图,直接看看不同[2]:

image.png

从图中也可以看出,通常 过滤器 的处理先于 拦截器 的处理。

过滤器:

过滤器是Java Web中的一种技术,它在请求到达Servlet之前或之后进行处理。因此,过滤器是最先执行的拦截器。在过滤器中可以进行一些通用的业务处理,例如鉴权、数据校验、请求日志记录等等。Spring Boot应用程序中注册的过滤器按照注册的顺序依次执行。

拦截器:

拦截器是Spring MVC框架提供的一种技术,它在请求到达Controller之前或之后进行处理。因此,拦截器在过滤器之后执行,但在请求到达Controller之前。在拦截器中可以进行请求的修改或者一些通用的业务逻辑处理。Spring Boot应用程序中注册的拦截器按照注册的顺序依次执行。

二者的区别[3]

Spring的拦截器与Servlet的Filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。

不同的是:

  • 使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。
  • 规范不同: Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
  • 使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过loC注入到拦截器即可:而Filter则不能。
  • 深度不同:Filter在只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。

注意:过滤器的触发时机是容器后,servlet之前,所以过滤器的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)的入参是ServletRequest,而不是HttpServletRequest,因为过滤器是在HttpServlet之前。下面这个图,可以让你对Filter和Interceptor的执行时机有更加直观的认识。

只有经过DispatcherServlet 的请求,才会走拦截器链,自定义的Servlet请求是不会被拦截的,比如我们自定义的Servlet地址。

过滤器依赖于Servlet容器,而Interceptor则为SpringMVC的一部分。过滤器能够拦截所有请求,而Interceptor只能拦截Controller的请求,所以从覆盖范围来看,Filter应用更广一些。但是在Spring逐渐一统Java框架、前后端分离越演越烈,实际上大部分的应用场景,拦截器都可以满足了。

参考: