目录
在 Spring MVC 中,Interceprtor 与 Filter 两者的应用场景好像差不多,最大的区别可能是前者属于 Spring 的组件,而后者则是 Servlert 三剑客中的一个,它们本质的区别在于两者发生的时机不一致。
Filter 和 Interceprtor 对比:
-
Filter:在执行 Servlet#service 方法之前,会执行过滤器;执行完毕之后也会经过过滤器;
Filter 操作 Request、Response。
-
Interceptor:对会话进行拦截,可以在调用 Handler 方法之前、视图渲染之前、方法返回之前,三个时机触发回调。
Interceptor 操作 Request、Response、handler、modelAndView、exception。
Filter 和 Interceprtor 的执行顺序:
-
Filter 处理 -> Interceptor 前置 -> controller -> Interceptor 处理中 -> Interceptor 处理后 -> Filter 处理后
过滤器基于函数回调方式实现,拦截器基于 Java 反射机制实现。实际开发中,拦截器的应用场景会比过滤器要更多,下面是拦截器和过滤器的主要应用场景:
-
拦截器的应用场景:权限控制,日志打印,参数校验
-
过滤器的应用场景:跨域问题解决,编码转换
Filter
它可以在 HTTP 请求到达 Servlet 之前,被一个或多个 Filter 处理。其工作流程如图所示:
Filter 实现的是 javax.servlet.Filter
接口,而这个接口是在 Servlet 规范中定义的,Filter 会依赖于 Tomcat 等容器,导致它只能在 web 程序中使用。
使 Spring 管理 Filter
方式一:@Component + @Order
在自定义 Filter 类上,加上 @Component
和 @Order
注解,即可被 Spring 管理。
@Component
@Order(1)
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter");
// 要继续处理请求,必须添加 filterChain.doFilter()
filterChain.doFilter(servletRequest,servletResponse);
}
}
注意:
-
如果 Filter 要使请求被继续处理,就一定要调用
filterChain.doFilter()
; -
这里我们可以通过
@Order
控制过滤器的级别,值越小级别越高越先执行。
优缺点:
-
优点:注解方式配置简单,支持自定义 Filter 顺序。
-
缺点:只能拦截所有 URL,不能通过配置去拦截指定的 URL。
方式二:通过 JavaConfig 配置
- 定义一个过滤器
public class LogCostFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
long start = System.currentTimeMillis();
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("Execute cost="+(System.currentTimeMillis()-start));
}
}
- 通过 JavaConfig 配置方式,注册 Filter
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registerMyFilter(){
FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
bean.setOrder(1);
bean.setFilter(new LogCostFilter());
// 匹配"/hello/"下面的所有url
bean.addUrlPatterns("/hello/*");
return bean;
}
@Bean
public FilterRegistrationBean registerMyAnotherFilter(){
FilterRegistrationBean<MyAnotherFilter> bean = new FilterRegistrationBean<>();
bean.setOrder(2);
bean.setFilter(new MyAnotherFilter());
// 匹配所有url
bean.addUrlPatterns("/*");
return bean;
}
}
优缺点:
- 优点:功能强大,配置灵活。只需要把每个自定义的 Filter 声明成 Bean 交给 Spring 管理即可,还可以设置匹配的 URL 、指定 Filter 的先后顺序。
方式三: @WebFilter + @ServletComponentScan
在自定义 Filter 类上,添加 @WebFilter
注解,并在启动类上增加 @ServletComponentScan("com.athena.common.filter")
注解,参数就是 Filter 所在的包路径。
- 定义一个 Filter
@Order(1)
@WebFilter(urlPatterns = "/*", filterName = "MyFilter")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter");
// 要继续处理请求,必须添加 filterChain.doFilter()
filterChain.doFilter(servletRequest,servletResponse);
}
}
- 添加自定义过滤器扫描路径
@SpringBootApplication
@ServletComponentScan("com.athena.common.filter")
public class FilterDemoApplication {
public static void main(String[] args) {
SpringApplication.run(FilterDemoApplication.class, args);
}
}
优缺点:
- 优点:配置简单、集中,支持 Filter 的顺序,支持对 Filter 匹配指定 URL。
对比
方式一不能指定 Filter 的顺序,所以不太推荐。因此,实际使用的时候,推荐使用 方式二 或者 方式三。
应用场景
Filter 的这个特性在生产环境中有很广泛的应用,如:
-
修改请求和响应;
-
防止 xss 攻击;
-
包装二进制流使其可以多次读等。
Interceptor
拦截器(Interceptor)是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application、Swing 等程序中。
自定义拦截器只需要实现接口 HandlerInterceptor 即可。
【示例】
- 实现拦截器
@Order(2)
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 一个简单的安全校验,要求请求头中必须包含 req-name : yihuihui
String header = request.getHeader("req-name");
if ("yihuihui".equals(header)) {
return true;
}
log.info("请求头错误: {}", header);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行完毕!");
response.setHeader("res", "postHandler");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("回收");
}
}
- 注册拦截器
package com.demo.springboot2.web.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private DemoInterceptor1 demoInterceptor1;
@Autowired
private DemoInterceptor2 demoInterceptor2;
// 这个方法是用来配置静态资源的,比如html,js,css,等等
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns("/**") 表示拦截所有的请求,
// excludePathPatterns("/login", "/register") 表示除了登陆与注册之外,因为登陆注册不需要登陆也可以访问
registry.addInterceptor(demoInterceptor1).addPathPatterns("/**").excludePathPatterns("/login", "/register");
registry.addInterceptor(demoInterceptor2).addPathPatterns("/**").excludePathPatterns("/login", "/register");
System.out.println("************addInterceptors**********");
}
}
preHandle
在 handler 方法执行之前(简单理解为 Controller 提供的服务调用之前)会被触发,如果返回 ture,表示拦截通过,可以执行;若果返回 false,表示不允许往后走。
因此在这里,通常可以用来做安全校验、用户身份处理等操作
注意,无论是拦截器,还是 Filter,在使用 Request 中的请求流的时候,要警惕,通常请求参数流的读取是一次性的,如果在这里实现了一个请求参数日志输出,把请求流的数据读出来了,但是又没有写回去,就会导致请求参数丢失了。
postHandler
这个是在 handler 方法执行之后,视图渲染之前被回调,简单来说,我们在这个时候,是可以操作 ModelAndView,往里面添加一下信息,并能被视图解析渲染的。
当然鉴于现在前后端分离的趋势,这个实际上用得也不多了。
afterCompletion
该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。
应用场景
Interceptor 的应用场景:
-
日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
-
权限检查:如登录检测,进入处理器检测是否登录;
-
性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache 也可以自动记录)
-
通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。
参考: