Filter(过滤器)和 Interceptor(拦截器)详解

在这里插入图片描述

一.Filter(过滤器)

1.简介

过滤器(Filter)是 Java Servlet 技术中的一个重要部分,主要用于在 Servlet 处理请求之前或响应之后对数据进行某些处理。过滤器可以实现诸如日志记录、请求数据修改、响应数据修改、权限控制等功能。在 Java EE 或 Spring 应用中,过滤器的使用非常广泛。

2.基本原理

过滤器工作在 Servlet 容器中,它拦截客户端的请求和服务器的响应。过滤器链(Filter Chain)是多个过滤器按照一定的顺序执行的集合,一个请求可以依次通过多个过滤器,然后到达目标 Servlet,响应也会按相反的顺序经过这些过滤器返回给客户端。

  • 生命周期管理
    • Servlet 容器负责过滤器的生命周期管理。过滤器的生命周期方法包括 init(初始化)、doFilter(执行过滤操作)和 destroy(销毁)。
  • 请求处理流程
    • 当一个请求到达 Servlet 容器时,容器会根据部署描述符(web.xml)或注解配置,决定是否以及如何调用过滤器链。
    • 过滤器链是多个过滤器按照一定的顺序执行的集合。容器按照这个顺序依次调用每个过滤器的 doFilter 方法。
  • doFilter 方法
    • doFilter 方法中,开发者可以实现自定义的处理逻辑,比如修改请求头、记录日志等。
    • doFilter 方法中必须调用 FilterChaindoFilter 方法,这样请求才能继续传递给下一个过滤器或目标资源(如 Servlet)。如果不调用,请求处理流程将会停止。
  • 工作机制
    • 过滤器可以修改请求和响应,但它们通常不会生成响应或结束请求,因为这通常是 Servlet 或其他资源的职责。

3.实现步骤

  • 创建过滤器类

    • 实现 javax.servlet.Filter 接口。
    • 重写 initdoFilterdestroy 方法。
  • 配置过滤器

    • 使用注解 @WebFilter 进行声明和配置。
    • 或者在 web.xml 文件中配置。
  • 编写过滤逻辑

    • doFilter 方法中实现具体的过滤逻辑。

4.示例代码

下面是一个简单的过滤器实现示例:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/example/*") // 过滤器应用于 URL 模式 "/example/*"
public class ExampleFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化代码,例如资源加载
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 在请求处理之前执行的代码
        System.out.println("Before Servlet processing");

        chain.doFilter(request, response); // 将请求传递给下一个过滤器或目标资源

        // 在请求处理之后执行的代码
        System.out.println("After Servlet processing");
    }

    @Override
    public void destroy() {
        // 清理代码,例如释放资源
    }
}

5.过滤器的使用场景

  • 权限检查:在过滤器中检查用户是否已经登录或是否有权限访问某些资源。
  • 日志和审计:记录请求的详细信息,如 IP 地址、访问时间、请求参数等。
  • 请求数据处理:修改或包装请求中的数据。
  • 响应内容处理:修改响应数据,如添加额外的响应头、压缩响应内容等。
  • 资源管理:如实现缓存机制,减少对实际资源的访问次数。

6.注意事项

  • 过滤器执行顺序:如果应用中有多个过滤器,它们的执行顺序可能会影响处理逻辑。可以通过 @WebFilter 注解的 order 属性或在 web.xml 中定义的顺序来控制。
  • 资源释放:确保在 destroy 方法中释放过滤器使用的资源。
  • 异常处理:在过滤器中妥善处理异常,避免因为异常而中断整个请求处理流程。

二.Interceptor(拦截器)

在这里插入图片描述

1.简介

拦截器(Interceptor)是 Spring MVC 框架中的一个核心组件,用于在处理 HTTP 请求的过程中进行拦截和处理。拦截器主要用于实现跨切面(cross-cutting)的逻辑,如日志记录、性能统计、安全控制、事务处理等。

2.基本原理

拦截器工作在 Spring 的 DispatcherServlet 和具体的 Controller 之间。当一个请求被发送到 Spring MVC 应用时,DispatcherServlet 首先接收到这个请求,然后根据配置的拦截器链对请求进行预处理,最后将请求转发到相应的 Controller 进行处理。

  • 实现机制
    • 在Spring框架中,拦截器(Interceptor)的实现机制是基于Java动态代理和AOP(面向切面编程)的。具体来说,Spring框架通过代理模式和AOP技术来实现拦截器的功能。
    • 当一个请求到达被拦截的方法或Controller时,Spring框架会利用动态代理生成一个代理对象,该代理对象包含了目标对象(被拦截的方法或Controller)以及拦截器的逻辑。在代理对象中,拦截器的逻辑会在目标方法执行前、执行后或抛出异常时被调用,从而实现对请求的拦截和处理。
    • 这种基于代理的拦截器实现方式,使得开发者可以通过简单的配置将拦截器应用到需要的方法或Controller上,而无需修改原有的业务逻辑代码。同时,利用AOP技术,可以灵活地定义切点和通知,实现对请求处理过程的精细控制。
    • 总的来说,拦截器的底层实现依赖于Spring框架提供的代理机制和AOP功能,通过代理对象对请求进行拦截和处理,实现了拦截器的功能。这种实现方式保持了代码的清晰性和可维护性,同时提供了灵活的扩展性,使得开发者能够方便地定制和应用拦截器功能。
  • 集成于 Spring MVC
    • 拦截器是与 Spring MVC 的 DispatcherServlet 集成的。当一个请求被 DispatcherServlet 接收后,它会处理和调度这个请求到相应的处理器(Controller)。
  • 处理器映射(Handler Mapping)
    • 在请求到达 Controller 之前,Spring 会根据请求的 URL 查找对应的 Handler Mapping,然后确定相应的 Controller。
  • 拦截器链
    • 在请求达到 Controller 之前,以及响应返回给客户端之前,拦截器链中的拦截器会被依次调用。
    • 开发者可以自定义拦截器,并通过实现 WebMvcConfigurer 接口的 addInterceptors 方法将它们添加到应用中。
  • 方法级别的拦截
    • 拦截器可以更精细地控制处理流程,它们能够在 Controller 方法执行前后以及请求完成后进行拦截处理。
  • 上下文访问
    • 拦截器可以访问请求的上下文,包括请求和响应对象、处理器(Controller)的信息等。这使得拦截器可以执行如权限检查、日志记录等更复杂的任务。

3.实现步骤

  • 创建拦截器类

    • 实现 HandlerInterceptor 接口或继承 HandlerInterceptorAdapter 类。
    • 重写 preHandlepostHandleafterCompletion 方法。
  • 注册拦截器

    • 创建一个配置类,实现 WebMvcConfigurer 接口。
    • 重写 addInterceptors 方法来添加拦截器。
  • 编写拦截逻辑

    • preHandlepostHandleafterCompletion 方法中实现具体的拦截逻辑。

4.示例代码

下面是一个简单的拦截器实现示例:

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 请求处理前的逻辑
        return true; // 返回 true 继续流程,返回 false 中断流程
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // 请求处理后的逻辑,但在视图渲染前
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 请求处理完毕后的逻辑
    }
}

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); // 应用于所有路径
    }
}

4.拦截器的使用场景

  • 日志记录和审计:在请求处理前后记录日志,进行审计。
  • 权限验证:检查用户是否登录或是否有权限访问某些资源。
  • 性能监控:记录请求的处理时间,用于性能监控。
  • 通用行为处理:如字符集设置、内容协商等。
  • 事务管理:在 Spring 环境中管理事务。

5.注意事项

  • 返回值处理preHandle 方法的返回值决定是否继续执行后续的拦截器和请求处理。如果返回 false,则流程中断。
  • 拦截器链:Spring 允许配置多个拦截器,形成一个拦截器链。这些拦截器会按顺序执行。
  • 异常处理:应妥善处理拦截器中可能发生的异常,以免影响整个请求处理流程。

三.过滤器和拦截器的相同点和不同点

过滤器(Filter)和拦截器(Interceptor)虽然都用于处理 HTTP 请求和响应,但它们在 Java Web 应用中有各自的特点和使用场景。让我们来详细对比一下它们的相同点和不同点。

1.相同点

  • 目的相似:都用于在请求到达目标资源(如 Servlet 或 Controller)之前或响应返回给客户端之后进行某些处理。
  • 处理能力:都能够实现请求的预处理、后处理,比如日志记录、权限检查、请求数据修改等。
  • 配置方式:可以通过注解或 XML 配置的方式进行配置。

2.不同点

  • 所属框架/规范

    • 过滤器是基于 Java Servlet 规范的,它是 Servlet 容器(如 Tomcat)的一部分。
    • 拦截器则是 Spring MVC 框架的一部分,依赖于 Spring 的上下文环境。
  • 执行层次

    • 过滤器更接近于底层,它在 Servlet 请求之前和响应之后进行处理。
    • 拦截器则更接近于业务层,它在 DispatcherServlet 处理请求的过程中执行,即在请求到达 Controller 之前和处理完毕之后。
  • 功能范围和依赖

    • 过滤器通常用于对请求和响应的预处理和后处理,依赖于 Servlet API。
    • 拦截器可以利用 Spring MVC 提供的丰富功能,如访问控制器的元数据、处理模型和视图等。
  • 可控制的粒度

    • 过滤器的粒度比较粗,通常对所有请求或特定 URL 模式的请求生效。
    • 拦截器的粒度更细,可以控制到具体的控制器方法。
  • 异常处理

    • 过滤器中的异常处理通常需要自己实现。
    • 拦截器中的异常可以被 Spring 的全局异常处理器捕获和处理。
  • 参数访问

    • 过滤器直接使用 Servlet API,所以只能访问到请求和响应对象。
    • 拦截器可以访问更多的上下文数据,如控制器方法的签名、模型数据等。

3.使用选择

  • 当需要处理所有请求,并且与 Spring 上下文无关时,可以选择过滤器。
  • 当需要在 Spring MVC 的上下文中处理请求,并且需要更细粒度的控制或利用 Spring 提供的其他功能时,拦截器是更好的选择。

四.日志记录案例

1.数据库表

-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
	class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
	method_desc varchar(100) comment '方法用途',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
	operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

2.自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnno {
    /**
     * 用于标记方法用途的注解
     * @return
     */
    String value() default "";
}

3.Controller方法标记注解

@PostMapping
@LogAnno("添加员工")
public Result insert(@RequestBody Emp emp){
    log.info("emp:{}",emp);
    return empService.insert(emp);
}

4.基于AOP实现的日志记录

@Component
@Aspect // 表示这个类是一个切面类
@Slf4j
public class LogAspect {

    private static final String TOKEN = "token";

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

    // 切点
    @Pointcut("execution(* com.itheima.tlias.web.controller.*.*(..)) && 		@annotation(com.itheima.tlias.anno.LogAnno)")
    public void pt() {
    }

    // 切面(环绕通知)
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
        log.info("日志环绕通知开始了:{}", pjp);
        // 声明变量用来存储目标方法的返回值
        Object result = null;
        // 记录开始操作时间
        long start = System.currentTimeMillis();
        OperateLog operateLog = new OperateLog();
        try {
            // 解析token获取ID
            String token = request.getHeader(TOKEN);
            Object empId = JwtUtils.parseJwt(token).get("id");
            // 获取操作的类名
            String className = pjp.getTarget().getClass().getName();
            operateLog.setClassName(className);
            // 获取操作的方法名
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method method = methodSignature.getMethod();
            String methodName = method.getName();
            operateLog.setMethodName(methodName);
            // 获取方法用途
            String methodDesc = method.getAnnotation(LogAnno.class).value();
            operateLog.setMethodDesc(methodDesc);
            // 获取方法参数
            Object[] args = pjp.getArgs();
            operateLog.setMethodParams(Arrays.toString(args));
            // 获取返回值
            result = pjp.proceed();
            operateLog.setReturnValue(JSON.toJSONString(result));
            // 获取操作人ID
            operateLog.setOperateUser(Integer.valueOf(empId.toString()));
            // 获取操作时间
            operateLog.setOperateTime(LocalDateTime.now());
        } catch (Throwable e) {
            e.printStackTrace();
            operateLog.setReturnValue("出异常了,返回值为异常消息:" + e.getMessage());  //返回值
        } finally {
            // 记录结束操作时间
            long end = System.currentTimeMillis();
            operateLog.setCostTime(end - start);
        }
        operateLogMapper.save(operateLog);
        return result;
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(logInterceptor).addPathPatterns("/**"); // 应用于所有路径
    }
}

5.基于Spring MVC 拦截器实现日志记录

@Component
@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    @Autowired
    private OperateLogMapper operateLogMapper;

    // 在请求处理之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 记录开始时间
        long start = System.currentTimeMillis();
        request.setAttribute("startTime", start);

        // 仅对方法调用进行处理
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();

            // 检查是否有 LogAnno 注解
            if (method.isAnnotationPresent(LogAnno.class)) {
                // 初始化操作日志对象
                OperateLog operateLog = new OperateLog();
                // 获取操作的类名和方法名
                operateLog.setClassName(method.getDeclaringClass().getName());
                operateLog.setMethodName(method.getName());

                // 获取方法描述
                LogAnno logAnno = method.getAnnotation(LogAnno.class);
                operateLog.setMethodDesc(logAnno.value());

                // 存储操作日志对象以便在 postHandle 中使用
                request.setAttribute("operateLog", operateLog);
            }
        }
        return true;
    }

    // 在请求处理之后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long endTime = System.currentTimeMillis();
        long startTime = (Long) request.getAttribute("startTime");

        // 检查是否有日志对象
        if (request.getAttribute("operateLog") != null) {
            OperateLog operateLog = (OperateLog) request.getAttribute("operateLog");

            // 设置操作时间和耗时
            operateLog.setOperateTime(LocalDateTime.now());
            operateLog.setCostTime(endTime - startTime);

            // 解析token获取ID
            String token = request.getHeader("token");
            Object empId = JwtUtils.parseJwt(token).get("id");
            operateLog.setOperateUser(Integer.valueOf(empId.toString()));

            // 保存操作日志
            operateLogMapper.save(operateLog);
        }
    }

    // 在请求完成后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 可以处理异常信息,例如记录异常日志
        if (ex != null && request.getAttribute("operateLog") != null) {
            OperateLog operateLog = (OperateLog) request.getAttribute("operateLog");
            operateLog.setReturnValue("出异常了,返回值为异常消息:" + ex.getMessage());
            operateLogMapper.save(operateLog);
        }
    }
}

五.疑问

1.当一个方法上同时定义了拦截器(Interceptor)和 AOP(Aspect-Oriented Programming)切面时,他的执行顺序是什么?

  • 拦截器的 preHandle 方法:在请求进入 Controller 方法之前执行。这是拦截器链的第一个环节,用于预处理操作。

  • AOP 前置通知(Before Advice):在具体的方法执行之前执行,但在拦截器的 preHandle 方法之后。

  • Controller 中的方法执行:执行你的业务逻辑。

  • AOP 后置通知(After Returning Advice):在方法执行之后、返回结果之前执行。

  • 拦截器的 postHandle 方法:在 Controller 方法执行完毕后、DispatcherServlet 进行视图返回渲染之前调用。

  • 拦截器的 afterCompletion 方法:在整个请求完成之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。这个方法的执行时间点是在 AOP 后置通知之后。

  • AOP 环绕通知(Around Advice):这个通知可以在方法执行前后都执行,它的执行顺序取决于其内部调用 proceed() 方法的位置。如果在 proceed() 之前执行,那么它将在上述所有步骤之前执行;如果在 proceed() 之后执行,那么它将在所有步骤之后执行。

  • AOP 异常通知(After Throwing Advice):在方法抛出异常后执行。如果 Controller 方法中发生异常,则这个通知将在 afterCompletion 方法之前执行。

  • AOP 最终通知(After (finally) Advice):无论方法正常执行还是发生异常,这个通知都将执行。在拦截器的 afterCompletion 方法之后执行。

总结

  • 拦截器的 preHandle → AOP Before → 方法执行 → AOP After Returning → 拦截器的 postHandle → 拦截器的 afterCompletion → AOP After。
  • AOP 环绕通知的执行顺序取决于 proceed() 的调用位置。
  • AOP 异常通知执行取决于是否有异常产生,且在拦截器的 afterCompletion 方法之前。
filter过滤器拦截器Web开发中常用的两种组件,它们在请求处理过程中起到了类似于"中间件"的作用,用于对请求进行处理和拦截。 Filter过滤器是Servlet规范中定义的一种组件,它可以对请求进行预处理和后处理。Filter可以拦截特定的URL请求,对请求进行处理,并将请求传递给下一个过滤器或Servlet。Filter可以用于对请求进行身份验证、日志记录、编码转换、资源过滤等等操作。一个应用可以配置多个Filter,它们按照配置的顺序依次执行。 拦截器是在Spring框架中使用的一种组件,它也可以对请求进行预处理和后处理。拦截器的使用更加灵活,可以对请求进行更加细粒度的控制。拦截器可以拦截Controller方法的调用,在方法执行前后做一些处理,例如身份验证、日志记录、性能监控等。一个应用可以配置多个拦截器,它们按照配置的顺序依次执行。 在使用上,filterinterceptor有一些区别: - Filter是基于Servlet规范的,而Interceptor是Spring框架提供的; - Filter可以对所有的请求进行拦截,而Interceptor可以对Controller方法进行拦截; - Filter只能通过web.xml或注解进行配置,而Interceptor可以通过Java代码进行配置; - Interceptor可以访问Spring的上下文,而Filter不能。 总结来说,filterinterceptor都可以用于对请求进行处理和拦截,但是它们的具体实现和使用方式有一些差异。在使用时,可以根据具体需求选择适合的组件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值