Filter
Filter
是servlet
规范中定义的java web组件, 在所有支持java web的容器中都可以使用-
Filter
和Filter Chain
是密不可分的,Filter
可以实现依次调用正是因为有了Filter Chain
上图是
Filter
对请求进行拦截的原理图, 那么java web容器(以tomcat为例子)是如何实现这个功能的呢?下面看下
Filter
和Filter Chain
的源码// Filter public interface Filter { // 容器创建的时候调用, 即启动tomcat的时候调用 public void init(FilterConfig filterConfig) throws ServletException; // 由FilterChain调用, 并且传入Filter Chain本身 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; // 容器销毁的时候调用, 即关闭tomcat的时候调用 public void destroy(); } // FilterChain public interface FilterChain { // 由Filter.doFilter()中的chain.doFilter调用 public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; }
- 正是因为
Filter Chain
在调用每一个Filter.doFilter()
时将自身引用传递进去, 才实现了Filter
的依次调用, 在Filter
全部调用完之后再调用真正处理请求的servlet
, 并且再次逆序回调Filter
. 可能这么看还是不太明白是怎么实现Filter的顺序调用, 调用真正的servlet
, 逆序调用Filter
的, 一起看下Tomcat的源码就一目了然了.
- 正是因为
-
在tomcat中
Filter Chain
的默认实现是ApplicationFilterChain
, 在ApplicationFilterChain
中最关键的方法就是internalDoFilter
, 整个Filter
流程的实现就是由这个方法完成.// internalDoFilter(只保留关键代码) private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { // Call the next filter if there is one // pos: 当前的filter的索引, n: 调用链中所有的Filter的数量 // 如果调用链中还有没有调用的Filter就继续调用, 否则跳过if语句 if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { // 获取Filter Filter filter = filterConfig.getFilter(); if( Globals.IS_SECURITY_ENABLED ) { ... 其他代码 ... } else { // 这句话是重点, 调用Filter的doFilter方法并把Filter Chain本身传进去(this) filter.doFilter(request, response, this); } } catch (IOException | ServletException | RuntimeException e) { ... 异常处理代码 ... } return; } // We fell off the end of the chain -- call the servlet instance try { ... 其他代码 ... // Use potentially wrapped request from this point if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) { ... 其他代码 ... } else { // 调用真正的Filter servlet.service(request, response); } } catch (IOException | ServletException | RuntimeException e) { ... 异常处理代码 ... } finally { ... 始终要执行的代码 ... } }
- 看了上面我添加的注释之后应该可以知道
Filter
的正序调用的过程和调用真正的servlet
的过程了, 但是Filter
的逆序调用在哪里体现了呢?
- 看了上面我添加的注释之后应该可以知道
假设下面的
Filter
就是调用链中的最后一个Filter
public class LogFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Log.info("before");
chain.doFilter(request, response);
Log.info("after");
}
}
那么在调用
chain.doFilter
之后就跳过了if
语句从而调用了真正的servlet
, 然后internalDoFilter
方法就结束(出栈)了, 紧接着就是调用Log.info("after")
了, 然后LogFilter的doFilter就结束了(也出栈了)
, 紧接着就是internalDoFilter
中filter.doFilter(request, response, this)
的结束然后return
, 然后就是调用上一个filter的chain.doFilter()
之后的代码, 以此类推.因此
Filter
调用链的实现其实就是一个方法调用链的过程. 刚开始,Filter Chain
每调用一个Filter.doFilter()
方法就是向方法调用栈中进行压栈操作(代码上的体现就是执行Filter.doFilter
之前的代码), 当Filter
全部调用完成之后就调用真正处理请求的servlet
, 然后由方法调用链自动进行出栈操作(代码上的体现就是执行Filter.doFilter
之后的代码), 从而完成整个Filter
的调用链. 因为Filter
功能实现实际上就是利用了方法的压栈出栈, 所以可以在调用chain.doFilter
之前将方法返回, 让容器不在调用servlet
方法, 从而实现权限的控制, 关键词的过滤等功能.
Interceptor
-
Interceptor
不是servlet
规范中的java web组件, 而是Spring提供的组件, 功能上和Filter差不多. 但是实现上和Filter不一样.
Interceptor
功能的实现主要是在Spring Mvc的DispatcherServelt.doDispatch
方法中, 让我们来看看源码
// Interceptor的源码
public interface HandlerInterceptor {
// 在调用真正的处理请求类之前调用
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
// 在调用真正的处理请求类之后调用
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
// 在完成渲染或者出错之后调用
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
// doDispatch源码(只保留关键代码)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
....
其它的处理代码
....
// 调用拦截器的前置处理方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 调用真正的处理请求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 找到渲染模版
applyDefaultViewName(processedRequest, mv);
// 调用拦截器的后置处理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
....
异常处理代码
....
}
finally {
....
始终要执行的代码
....
}
其实看了doDispatch
的关键代码, Spring Mvc对整个请求的处理流程已经很清楚了:
调用拦截器的前置方法 -> 调用处理请求的方法 -> 渲染模版 -> 调用拦截器的后置处理方法 -> 调用拦截器的完成方法
接下来看一看Spring Mvc是如何实现依此调用这么多拦截器的前置方法, 后置方法, 完成方法的
进入到mapperHandler.applyPreHandle()
方法中(调用拦截器的前置方法)
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果拦截器数组不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 按顺序调用拦截器数组中的preHandle方法
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// 如果拦截器的preHandle方法返回false, 则调用当前拦截器的triggerAfterCompletion方法, 然后返回, 并且不再调用后续的拦截器
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
进入到mappedHandler.applyPostHandle()
方法中(调用拦截器的后置方法)
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果拦截器数组不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 倒序调用拦截器数组中拦截器的postHandle方法
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
不管是否出异常triggerAfterCompletion
方法始终会被调用
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 拦截器数组不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 从成功执行的最后一个拦截器开始逆序调用afterCompletion方法
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
看过以上三个方法之后, Spring Mvc如何处理拦截器的前置, 后置, 完成方法就一目了然了. 其实Spring Mvc就是将拦截器统一放到了拦截器数组中, 然后在调用真正的处理请求方法之前和之后正序或者倒序遍历拦截器, 同时调用拦截器的相应的方法. 最后不管是否正常结束这个流程还是出异常都会从成功的最后一个拦截器开始逆序调用afterCompletion
方法
总结
- 从以上分析可以看到过滤器和拦截器实现的方式的不同.
Filter
是利用了方法的调用(入栈出栈)完成整个流程, 而Interceptor
是利用了for
循环完成了整个流程. -
Filter
的实现比较占用栈空间, 在Filter
多的情况下可能会有栈溢出的风险存在. -
Interceptor
的实现逻辑更加的清晰简单 -
Filter
组件更加的通用, 只要支持java servlet
的容器都可以使用, 而Interceptor
必须依赖于Spring - Filter的优先级是高于
Interceptor
, 即请求是先到Filter
再到Interceptor
的, 因为Interceptor
的实现主体还是一个servlet