Web 过滤器与拦截器:Spring Boot 请求处理增强

好的,没问题!咱们今天就来聊聊 Spring Boot 里两个好玩又实用的家伙:Web 过滤器 (Filter) 和拦截器 (Interceptor)。它们就像是 Spring Boot 这辆跑车上的安全带和导航仪,帮你规范请求,保驾护航。

文章大纲

  1. 开场白:别让你的 Spring Boot 应用裸奔!
  2. Web 过滤器 (Filter):请求的“守门员”
    • 2.1 什么是 Web 过滤器?
    • 2.2 过滤器的生命周期
    • 2.3 如何自定义一个过滤器?
    • 2.4 过滤器的典型应用场景
    • 2.5 过滤器链:层层把关
    • 2.6 过滤器配置:让 Spring Boot 找到你
    • 2.7 示例:实现一个简单的日志过滤器
  3. 拦截器 (Interceptor):请求的“导航员”
    • 3.1 什么是拦截器?
    • 3.2 拦截器的生命周期(三大方法)
    • 3.3 如何自定义一个拦截器?
    • 3.4 拦截器的典型应用场景
    • 3.5 拦截器配置:告诉 Spring Boot 往哪儿拦
    • 3.6 示例:实现一个权限验证拦截器
  4. 过滤器 vs. 拦截器:傻傻分不清楚?
    • 4.1 相同点
    • 4.2 不同点
    • 4.3 如何选择?
  5. 高级用法:玩转过滤器和拦截器
    • 5.1 过滤器中的异步处理
    • 5.2 拦截器中的异常处理
    • 5.3 使用 HandlerMethodArgumentResolver 自定义参数解析
    • 5.4 使用 HandlerMethodReturnValueHandler 自定义返回值处理
  6. 最佳实践:让你的代码更优雅
    • 6.1 职责单一原则
    • 6.2 避免过度设计
    • 6.3 善用配置
  7. 总结:过滤器和拦截器,Spring Boot 的左膀右臂

1. 开场白:别让你的 Spring Boot 应用裸奔!

想象一下,你辛辛苦苦写了一个 Spring Boot 应用,对外提供各种 API 接口。如果没有任何保护措施,就像让它在大街上裸奔一样,随便什么人都能访问,想想都觉得可怕。

  • 安全风险:恶意用户可以随意调用你的接口,窃取数据、破坏系统。
  • 性能问题:大量无效请求会消耗你的服务器资源,拖慢系统速度。
  • 业务逻辑混乱:很多通用的逻辑(比如日志记录、权限验证)散落在各个 Controller 中,代码冗余,难以维护。

这个时候,Web 过滤器和拦截器就派上用场了。它们就像是你的应用的“保安”和“向导”,帮你过滤掉恶意请求,规范用户行为,让你的 Spring Boot 应用更加安全、高效、易于维护。

2. Web 过滤器 (Filter):请求的“守门员”

2.1 什么是 Web 过滤器?

Web 过滤器是 Servlet 规范中的一个组件,它可以在请求到达 Servlet (比如 Spring MVC 的 DispatcherServlet) 之前或之后,对请求进行预处理或后处理。你可以把过滤器想象成一个“守门员”,它会检查每一个进来的请求,看看是否符合规则,如果不符合,就直接拒绝或者进行一些处理。

2.2 过滤器的生命周期

过滤器的生命周期由 Servlet 容器(比如 Tomcat)管理,它有三个关键方法:

  • init(FilterConfig filterConfig):在过滤器创建时被调用,用于初始化过滤器。
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain):这是过滤器的核心方法,每个请求都会经过这里。你可以在这里编写你的过滤逻辑。
  • destroy():在过滤器销毁时被调用,用于释放资源。

2.3 如何自定义一个过滤器?

要自定义一个过滤器,你需要:

  1. 创建一个类,实现 javax.servlet.Filter 接口。
  2. 重写 init()doFilter()destroy() 方法。
  3. 使用 @Component 注解将你的过滤器注册为 Spring Bean,或者使用 FilterRegistrationBean 进行更细粒度的控制。

2.4 过滤器的典型应用场景

  • 日志记录:记录每个请求的 URL、IP 地址、请求参数等信息。
  • 权限验证:检查用户是否登录,是否有权限访问某个资源。
  • 字符编码转换:将请求的字符编码统一转换为 UTF-8。
  • XSS 攻击防御:过滤掉请求中的恶意脚本。
  • GZIP 压缩:对响应进行 GZIP 压缩,减少网络传输量。

2.5 过滤器链:层层把关

你可以配置多个过滤器,它们会按照一定的顺序组成一个过滤器链 (FilterChain)。每个过滤器都会对请求进行处理,然后将请求传递给下一个过滤器,直到最后一个过滤器处理完成,请求才会到达 Servlet。

2.6 过滤器配置:让 Spring Boot 找到你

在 Spring Boot 中,你可以使用以下两种方式配置过滤器:

  • 使用 @Component 注解:这是最简单的方式,直接将你的过滤器类标记为 @Component,Spring Boot 会自动注册它。
  • 使用 FilterRegistrationBean:这种方式可以让你更灵活地控制过滤器的注册,比如指定过滤器的 URL 匹配规则、执行顺序等。

2.7 示例:实现一个简单的日志过滤器

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class LogFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("LogFilter 初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String url = httpRequest.getRequestURL().toString();
        String method = httpRequest.getMethod();
        logger.info("请求URL: {}, 请求方法: {}", url, method);

        // 继续执行过滤器链,将请求传递给下一个过滤器或 Servlet
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        logger.info("LogFilter 销毁");
    }
}

这个例子中,LogFilter 会记录每个请求的 URL 和请求方法。

3. 拦截器 (Interceptor):请求的“导航员”

3.1 什么是拦截器?

拦截器是 Spring MVC 框架中的一个组件,它可以在请求到达 Controller 之前、Controller 处理请求之后、视图渲染之后,对请求进行拦截和处理。你可以把拦截器想象成一个“导航员”,它会引导请求按照正确的路线到达目的地,并在途中进行一些必要的处理。

3.2 拦截器的生命周期(三大方法)

拦截器有三个关键方法:

  • preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在 Controller 处理请求之前被调用。你可以在这里进行权限验证、参数校验等操作。如果返回 false,则请求不会被继续处理。
  • postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):在 Controller 处理请求之后,但在视图渲染之前被调用。你可以在这里修改 ModelAndView,添加一些额外的参数。
  • afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):在视图渲染之后被调用。你可以在这里进行资源清理、异常处理等操作。

3.3 如何自定义一个拦截器?

要自定义一个拦截器,你需要:

  1. 创建一个类,实现 org.springframework.web.servlet.HandlerInterceptor 接口。
  2. 重写 preHandle()postHandle()afterCompletion() 方法。
  3. 注册你的拦截器到 Spring MVC 的配置中。

3.4 拦截器的典型应用场景

  • 权限验证:检查用户是否登录,是否有权限访问某个资源。
  • 日志记录:记录请求的执行时间、用户信息等。
  • 国际化处理:根据用户的语言设置,设置相应的 Locale。
  • 防止重复提交:防止用户重复提交表单。
  • Open API 签名验证:验证请求的签名是否合法。

3.5 拦截器配置:告诉 Spring Boot 往哪儿拦

你需要创建一个配置类,实现 org.springframework.web.servlet.config.annotation.WebMvcConfigurer 接口,并重写 addInterceptors() 方法,将你的拦截器注册到 Spring MVC 中。

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 WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()) // 添加拦截器
                .addPathPatterns("/api/**") // 拦截的 URL
                .excludePathPatterns("/api/login"); // 排除的 URL
    }
}

3.6 示例:实现一个权限验证拦截器

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Component
public class AuthInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object user = session.getAttribute("user");

        if (user == null) {
            logger.warn("用户未登录,禁止访问: {}", request.getRequestURI());
            response.sendRedirect("/login"); // 重定向到登录页面
            return false; // 阻止请求继续执行
        }

        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("视图渲染完毕");
    }
}

这个例子中,AuthInterceptor 会检查用户是否登录,如果没有登录,就重定向到登录页面。

4. 过滤器 vs. 拦截器:傻傻分不清楚?

Web 过滤器和拦截器都是用于处理请求的组件,但它们之间有一些重要的区别。

4.1 相同点

  • 都可以对请求进行预处理和后处理。
  • 都可以实现权限验证、日志记录等功能。

4.2 不同点

特性 Web 过滤器 (Filter) 拦截器 (Interceptor)
实现方式 Servlet 规范 Spring MVC 框架
作用范围 作用于所有进入 Servlet 容器的请求 作用于 Spring MVC 的 Controller 请求
执行顺序 在 DispatcherServlet 之前或之后执行 在 Controller 的方法执行之前、之后、视图渲染之后执行
依赖关系 不依赖 Spring 容器 依赖 Spring 容器
功能 更多的是通用的、底层的处理,比如字符编码转换、XSS 防御 更多的是业务相关的处理,比如权限验证、日志记录、参数校验
获取 Spring Bean 需要手动从 Spring 容器中获取,比较麻烦 可以直接注入 Spring Bean

4.3 如何选择?

  • 如果你的处理逻辑是通用的、底层的,不依赖 Spring 容器,比如字符编码转换、XSS 防御,那么选择过滤器。
  • 如果你的处理逻辑是业务相关的,需要访问 Spring Bean,比如权限验证、日志记录、参数校验,那么选择拦截器。

5. 高级用法:玩转过滤器和拦截器

5.1 过滤器中的异步处理

Servlet 3.0 引入了异步处理的功能,你可以在过滤器中使用异步处理来提高性能。

@Component
public class AsyncFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        AsyncContext asyncContext = request.startAsync();
        asyncContext.start(() -> {
            try {
                // 模拟耗时操作
                Thread.sleep(2000);
                ServletResponse asyncResponse = asyncContext.getResponse();
                asyncResponse.getWriter().write("Async Filter 处理完成");
                asyncContext.complete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

5.2 拦截器中的异常处理

你可以在 afterCompletion() 方法中处理 Controller 抛出的异常。

@Component
public class ExceptionInterceptor implements HandlerInterceptor {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (ex != null) {
            // 记录异常信息
            logger.error("发生异常: {}", ex.getMessage());
            // 可以根据不同的异常类型进行不同的处理
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.getWriter().write("服务器内部错误");
        }
    }
}

5.3 使用 HandlerMethodArgumentResolver 自定义参数解析

HandlerMethodArgumentResolver 允许你自定义 Controller 方法的参数解析逻辑。比如,你可以从请求头中获取用户信息,并将其注入到 Controller 方法的参数中。

5.4 使用 HandlerMethodReturnValueHandler 自定义返回值处理

HandlerMethodReturnValueHandler 允许你自定义 Controller 方法的返回值处理逻辑。比如,你可以将 Controller 方法的返回值统一包装成 JSON 格式。

6. 最佳实践:让你的代码更优雅

6.1 职责单一原则

每个过滤器和拦截器都应该只负责一个特定的功能,避免将多个功能耦合在一起。

6.2 避免过度设计

不要为了追求“完美”而过度设计你的过滤器和拦截器,保持简单易懂。

6.3 善用配置

使用 Spring Boot 的配置功能,将一些可配置的参数提取到配置文件中,方便修改和维护。

7. 总结:过滤器和拦截器,Spring Boot 的左膀右臂

Web 过滤器和拦截器是 Spring Boot 应用中非常重要的两个组件。它们可以帮助你实现各种通用的、业务相关的处理逻辑,提高应用的安全性、性能和可维护性。掌握它们的使用方法,可以让你的 Spring Boot 应用更加健壮、高效。

希望这篇文章能帮助你更好地理解和使用 Web 过滤器和拦截器。祝你编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注