Spring Interceptor vs Filter 拦截器和过滤器区别


=Start=

缘由:

学习、理解一下Java Web的相关知识。

正文:

参考解答:

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

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

实际上Filter和Servlet极其相似,区别只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码,通过使用Filter可以实现更好的复用。


Spring的Interceptor(拦截器)与Servlet的Filter有相似之处,都能实现权限检查、日志记录等。不同的是:

Filter Interceptor Summary
Filter 接口定义在 javax.servlet 包中 接口 HandlerInterceptor 定义在org.springframework.web.servlet 包中。
Filter 定义在 web.xml 中
Filter在只在 Servlet 前后起作用。Filters 通常将 请求和响应(request/response) 当做黑盒子,Filter 通常不考虑servlet 的实现。 拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。允许用户介入(hook into)请求的生命周期,在请求过程中获取信息,Interceptor 通常和请求更加耦合。 在Spring构架的程序中,要优先使用拦截器。几乎所有 Filter 能够做的事情,interceptor 都能够轻松的实现
Filter 是 Servlet 规范规定的。 而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。 使用范围不同
Filter 是在 Servlet 规范中定义的,是 Servlet 容器支持的。 而拦截器是在 Spring容器内的,是Spring框架支持的。 规范不同
Filter 不能够使用 Spring 容器资源 拦截器是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如 Service对象、数据源、事务管理等,通过IoC注入到拦截器即可。 Spring 中使用 interceptor 更容易
Filter 是被 Server(like Tomcat) 调用 Interceptor 是被 Spring 调用。 因此 Filter 总是优先于 Interceptor 执行

interceptor 使用

interceptor 的执行顺序大致为:

  1. 请求到达 DispatcherServlet
  2. DispatcherServlet 发送至 Interceptor ,执行 preHandle
  3. 请求达到 Controller
  4. 请求结束后,postHandle 执行

Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:

  • preHandle() – 在handler执行之前,返回 boolean 值,true 表示继续执行,false 为停止执行并返回。
  • postHandle() – 在handler执行之后, 可以在返回之前对返回的结果进行修改
  • afterCompletion() – 在请求完全结束后调用,可以用来统计请求耗时等等

统计请求耗时:

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

import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{
	
	private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);

	//before the actual handler will be executed
	public boolean preHandle(HttpServletRequest request, 
		HttpServletResponse response, Object handler)
	    throws Exception {
		
		long startTime = System.currentTimeMillis();
		request.setAttribute("startTime", startTime);
		
		return true;
	}

	//after the handler is executed
	public void postHandle(
		HttpServletRequest request, HttpServletResponse response, 
		Object handler, ModelAndView modelAndView)
		throws Exception {
		
		long startTime = (Long)request.getAttribute("startTime");
		
		long endTime = System.currentTimeMillis();

		long executeTime = endTime - startTime;
		
		//modified the exisitng modelAndView
		modelAndView.addObject("executeTime",executeTime);
		
		//log it
		if(logger.isDebugEnabled()){
		   logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
		}
	}
}

例子来源:mkyong

Filter 使用

Servlet 的 Filter 接口需要实现如下方法:

  • void init(FilterConfig paramFilterConfig) – 当容器初始化 Filter 时调用,该方法在 Filter 的生命周期只会被调用一次,一般在该方法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该方法中可以抛出 ServletException 。init 方法必须执行成功,否则 Filter 可能不起作用,出现以下两种情况时,web 容器中 Filter 可能无效: 1)抛出 ServletException 2)超过 web 容器定义的执行时间。
  • doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) – Web 容器每一次请求都会调用该方法。该方法将容器的请求和响应作为参数传递进来, FilterChain 用来调用下一个 Filter。
  • void destroy() – 当容器销毁 Filter 实例时调用该方法,可以在方法中销毁资源,该方法在 Filter 的生命周期只会被调用一次。FrequencyLimitFilter com.company.filter.FrequencyLimitFilter FrequencyLimitFilter /login/*

Filter 和 Interceptor 的一些用途

  • Authentication Filters
  • Logging and Auditing Filters
  • Image conversion Filters
  • Data compression Filters
  • Encryption Filters
  • Tokenizing Filters
  • Filters that trigger resource access events
  • XSL/T filters
  • Mime-type chain Filter

Request Filters 可以:

  • 执行安全检查 perform security checks
  • 格式化请求头和主体 reformat request headers or bodies
  • 审查或者记录日志 audit or log requests
  • 根据请求内容授权或者限制用户访问 Authentication-Blocking requests based on user identity
  • 根据请求频率限制用户访问

Response Filters 可以:

  • 压缩响应内容,比如让下载的内容更小 Compress the response stream
  • 追加或者修改响应 append or alter the response stream
  • 创建或者整体修改响应 create a different response altogether
  • 根据地方不同修改响应内容 Localization-Targeting the request and response to a particular locale
参考链接:

=END=


《 “Spring Interceptor vs Filter 拦截器和过滤器区别” 》 有 11 条评论

  1. 浅析Spring AOP
    https://mp.weixin.qq.com/s/hgML7LAuE04l6jwY6Ph0mQ
    https://docs.spring.io/spring/docs/current/spring-framework-reference/#aop-introduction-defn
    `
    Aop(Aspect-OrientedProgramming)面向切面编程是对OOP的补充,专注于系统中不同模块统一的处理逻辑,比如事务管理,日志管理,缓存等系统级服务。

    众所周知,SpringAop基于代理实现的,比如JDK动态代理和CGLIB代理。

    那么如何使用JDK动态代理呢?
    1):创建实现了InvocationHandler接口的类,实现invoke方法,该方法将在与之关联的代理类方法调用的时候执行
    2):通过Proxy.newProxyInstance()方法返回代理对象
    `

  2. 程序员,你还不会合理选择Filter、Interceptor、Aspect?
    https://juejin.im/post/5d64f867f265da03cf7a9d15
    `
    # Filter过滤器
    过滤器可以拦截到方法的请求和响应(ServletRequest request, ServletResponse response),并对请求响应做出过滤操作。
    过滤器依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,一个过滤器实例只能在容器初始化时调用一次。
    使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。

    # Interceptor拦截器
    依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在一个方法前,调用一个方法,或者在方法后,调用一个方法。

    # Aspect切片
    AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数,对方法进行统一的处理。常见使用日志,事务,请求参数安全验证等。

    # 总结
    如果三者方式同时采用,那他们的执行顺序是什么呢?
    filter -> interceptor -> ControllerAdvice -> aspect -> controller

    返回值顺序,或异常返回顺序
    controller -> aspect -> controllerAdvice -> Interceptor -> Filter
    `

  3. 原创干货 | 过滤器设计缺陷导致权限绕过
    https://mp.weixin.qq.com/s/Cotu3613adQNklEgE9q_YA
    `
    修复建议及防御,以Java为例:

    (1)使用如下方法进行相关路径的获取:
    request.getServletPath()+(request.getPathInfo()== null? “”: request.getPathInfo());

    (2)对相关接口访问进行标准化处理,剔除不相关的元素,例如…/,分隔符(;)后的内容等;

    这里推荐使用ESAPI的canonicalize方法进行集成,对相关输入进行规范化处理,样例代码如下:
    ESAPI.encoder().canonicalize(URI)

    同时在对应的配置文件ESAPI.properties禁用双重uri编码(默认开启):
    Encoder.AllowMultipleEncoding=false

    (3)尽量不要使用类似startsWith()、endsWith()进行判断;

    (4)使用成熟的权限控制框架进行权限校验。(SpringSecurity、Shiro等)
    `

  4. 如何优雅地记录操作日志?
    https://mp.weixin.qq.com/s/JC51S_bI02npm4CE5NEEow
    `
    操作日志几乎存在于每个系统中,而这些系统都有记录操作日志的一套 API。操作日志和系统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?上面这些都是本文要回答的问题。我们主要围绕着如何“优雅”地记录操作日志展开描述,希望对从事相关工作的同学能够有所帮助或者启发。

    1. 操作日志的使用场景

    2. 实现方式
    2.1 使用 Canal 监听数据库记录操作日志
    2.2 通过日志文件的方式记录
    2.3 通过 LogUtil 的方式记录日志
    2.4 方法注解实现操作日志

    3. 优雅地支持 AOP 生成动态的操作日志
    3.1 动态模板

    4. 代码实现解析
    4.1 代码结构
    4.2 模块介绍

    5. 总结
    `

发表回复

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