一、引言
在面向对象编程(OOP)的过程中,很容易通过继承、多态来解决纵向扩展。但对于横向的功能,如登记所有的客户端请求耗时、统一开启事务等功能,OOP 无能为力。面向切面编程(AOP)的编程思想是对 OOP 的一个补充,本篇所讨论的过滤器和拦截器都属于 AOP 的具体实现。
二、过滤器 Filter
过滤器(Filter)的预处理发生在请求进入容器后,未进入 Servlet 之前。相应的,其后处理发生在 Servlet 处理完成后,返回给前端之前。所以过滤器的 doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
的入参是ServletRequest
,而不是httpservletrequest
。因为过滤器是在httpservlet
之前。
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
log.info("before...");
chain.doFilter(request, response);
log.info("after...");
}
@Override
public void destroy() {
}
Filter
跟Servlet
都是由容器负责创建和销毁的。在一个应用程序中,一个Filter
只会被创建和销毁一次。web 应用程序启动时,容器调用public void init(FilterConfig filterConfig) throws ServletException
方法初始化Filter
;web 应用程序被移除或者是容器关闭时,调用public void destroy()
销毁Filter
。
Filter
中声明的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
方法,用于实现具体的过滤逻辑。其中FilterChain chain
参数是一个过滤链对象,它包含了用户定义的一系列过滤器,这些过滤器根据其定义顺序依次被执行。通过执行chain.doFilter(request, response)
方法可以跳过当前过滤器处理逻辑,执行过滤链中的下一个过滤器。事实上调用Servlet
的doService()
方法是在chain.doFilter(request, response)
这个方法中进行的。
三、拦截器 Interceptor
通过继承HandlerInterceptorAdapter
类并重写下列三个方法可以快速的实现自定义拦截器:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
上述方法分别实现了拦截器的预处理preHanlde
、后处理postHandle
(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理afterCompletion
(已经渲染了页面)。
preHandle()
方法实现了拦截器的预处理。如果存在多个拦截器,则会依次调用拦截器链中每一个拦截器的preHandle()
方法:
- 当
preHandle
方法返回false
时,DispatcherServlet
处理器认为拦截器已经处理完了请求,不再继续执行链中的其它拦截器和处理器,而是从当前拦截器往回执行所有拦截器的afterCompletion
方法,退出拦截器链 - 当
preHandle
方法全为true
时,执行下一个拦截器,直到所有拦截器执行完。再运行被拦截的 Controller。然后返回拦截器链,运行所有拦截器的postHandle
方法,然后从最后一个拦截器往回执行所有拦截器的afterCompletion
方法 - 当有拦截器抛出异常时,会从当前拦截器往回执行所有拦截器的
afterCompletion
方法
四、过滤器和拦截器的对比
二者在功能上很相似,其主要区别在于:
- 适用范围:Filter 依赖于 Servlet 容器,属于 Servlet 规范的一部分,只能用于 Web 程序;而拦截器是独立存在的,由 Spring 框架支持,可以用于 Web 程序、Application、Swing 程序中
- 作用范围:Filter 的执行由 Servlet 容器回调完成,过滤逻辑只能发生在 Servlet 调用前后;而拦截器基于 Java 反射机制,通常通过动态代理的方式来执行,能够深入到方法的前后、异常抛出的前后等,使用起来更加灵活
- 可支配的资源:拦截器属于Spring 组件,是通过 IoC 容器来管理,它能通过依赖注入的方式调用 Spring 里的任何资源、对象,例如 Service 对象、数据源、事务管理等;而 Filter 做不到,Filter 的生命周期由 Servlet 容器管理
五、运用场景
拦截器的主要应用场景有:
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测用户是否登录,如没有则跳转到登录页面;
- 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
- 通用行为:读取 cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。
- OpenSessionInView:如 hibernate,在进入处理器打开Session,在完成后关闭Session。
过滤器通常用于:
- 在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改 HttpServletRequest 的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
六、过滤器和拦截器的执行顺序
综上所述可知,Filter
的执行顺序在Interceptor
之前。一图胜千言:假设自定义了2个过滤器TestFilter1
和TestFilter2
,2个拦截器TestInterceptor
,BaseInterceptor
,其执行流程可能如下: