详解Tomcat中Filter的执行流程

 更新时间:2023年06月04日 09:24:27   作者:西直门三太子  
Filter是servlet规范中定义的java web组件, 在所有支持java web的容器中都可以使用,它是位于前端请求到servlet之间的一系列过滤器,也可以称之为中间件,本文详解介绍了Tomcat中Filter是怎样执行的,需要的朋友可以参考下

前言

Filter是什么?Filter是servlet规范中定义的java web组件, 在所有支持java web的容器中都可以使用 它是位于前端请求到servlet之间的一系列过滤器,也可以称之为中间件,它主要是对请求到达servlet之前做一些额外的动作:

  • 1、权限控制
  • 2、监控
  • 3、日志管理
  • 4、等等

这里涉及到两个接口:Filter和FilterChain

Filter和FilterChain密不可分, Filter可以实现依次调用正是因为有了FilterChain。

1、Filter接口

public interface Filter {
   // 容器创建的时候调用, 即启动tomcat的时候调用
   public void init(FilterConfig filterConfig) throws ServletException;
   // 由FilterChain调用, 并且传入FilterChain本身, 最后回调FilterChain的doFilter()方法
   public void doFilter(ServletRequest request, ServletResponse response,
       FilterChain chain) throws IOException, ServletException;
   // 容器销毁的时候调用, 即关闭tomcat的时候调用
   public void destroy();
 }

2、FilterChain接口

public interface FilterChain {
   // 由Filter.doFilter()中的chain.doFilter调用
   public void doFilter(ServletRequest request, ServletResponse response)
     throws IOException, ServletException;
}

执行流程

在前面的文章中,我们知道,tomcat启动会执行StandardWrapperValve.java类的invoke方法:

public final void invoke(Request request, Response response){
  ......
  MessageBytes requestPathMB = request.getRequestPathMB();
  DispatcherType dispatcherType = DispatcherType.REQUEST;
  if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
  request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
  request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
      requestPathMB);
  // Create the filter chain for this request
  ApplicationFilterChain filterChain =
      ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
  // Call the filter chain for this request
  // NOTE: This also calls the servlet's service() method
  Container container = this.container;
  try {
    if ((servlet != null) && (filterChain != null)) {
      // Swallow output if needed
      if (context.getSwallowOutput()) {
        try {
          SystemLogHandler.startCapture();
          if (request.isAsyncDispatching()) {
            request.getAsyncContextInternal().doInternalDispatch();
          } else {
            filterChain.doFilter(request.getRequest(),
                response.getResponse());
          }
        } finally {
          String log = SystemLogHandler.stopCapture();
          if (log != null && log.length() > 0) {
            context.getLogger().info(log);
          }
        }
      } else {
        if (request.isAsyncDispatching()) {
          request.getAsyncContextInternal().doInternalDispatch();
        } else {
          filterChain.doFilter
            (request.getRequest(), response.getResponse());
        }
      }
    }
  } catch (ClientAbortException | CloseNowException e) {
  }
  ......
}

上面的代码做了如下一些动作:

  • 1、每次请求过来都会创建一个过滤器链(filterChain),并把待执行的servlet对象存放到过滤器链中。对于每个url,对应的filter个数都是不固定的,filterchain需要保存每个请求所对应的一个filter数组,以及调用到的filter的position,以便继续向下调用filter。
  • 2、创建了filterChain之后,就开始执行doFilter进行请求的链式处理。

1、创建filterChain

下面我们具体来看看filterChain是怎么创建的

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
  // If there is no servlet to execute, return null
  if (servlet == null)
    return null;
  // Create and initialize a filter chain object
  ApplicationFilterChain filterChain = null;
  if (request instanceof Request) {
    Request req = (Request) request;
    if (Globals.IS_SECURITY_ENABLED) {
      // Security: Do not recycle
      filterChain = new ApplicationFilterChain();
    } else {
      filterChain = (ApplicationFilterChain) req.getFilterChain();
      if (filterChain == null) {
        filterChain = new ApplicationFilterChain();
        req.setFilterChain(filterChain);
      }
    }
  } else {
    // Request dispatcher in use
    filterChain = new ApplicationFilterChain();
  }
  filterChain.setServlet(servlet);
  filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
  // Acquire the filter mappings for this Context
  StandardContext context = (StandardContext) wrapper.getParent();
  FilterMap filterMaps[] = context.findFilterMaps();
  // If there are no filter mappings, we are done
  if ((filterMaps == null) || (filterMaps.length == 0))
    return filterChain;
  // Acquire the information we will need to match filter mappings
  DispatcherType dispatcher =
      (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
  String requestPath = null;
  Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
  if (attribute != null){
    requestPath = attribute.toString();
  }
  String servletName = wrapper.getName();
  // Add the relevant path-mapped filters to this filter chain
  for (FilterMap filterMap : filterMaps) {
    if (!matchDispatcher(filterMap, dispatcher)) {
      continue;
    }
    if (!matchFiltersURL(filterMap, requestPath))
      continue;
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
        context.findFilterConfig(filterMap.getFilterName());
    if (filterConfig == null) {
      // FIXME - log configuration problem
      continue;
    }
    filterChain.addFilter(filterConfig);
  }
  // Add filters that match on servlet name second
  for (FilterMap filterMap : filterMaps) {
    if (!matchDispatcher(filterMap, dispatcher)) {
      continue;
    }
    if (!matchFiltersServlet(filterMap, servletName))
      continue;
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
        context.findFilterConfig(filterMap.getFilterName());
    if (filterConfig == null) {
      // FIXME - log configuration problem
      continue;
    }
    filterChain.addFilter(filterConfig);
  }
  // Return the completed filter chain
  return filterChain;
}

上面的代码做了一下几件事:

  • 1、把要执行的servlet存放到过滤器链中。
  • 2、如果没有配置过滤器则return一个空的过滤器链(只包含上面设置的servlet)。
  • 3、如果配置url-pattern过滤器,则把匹配的过滤器加入到过滤器链中
  • 4、如果配置servlet-name过滤器,则把匹配的过滤器加入到过滤器链中

注意: filterChain.addFilter()顺序与web.xml中定义的Filter顺序一致,所以过滤器的执行顺序是按定义的上下顺序决定的。

2、执行dofilter

创建了chain之后,就开始执行链式请求了,具体的逻辑如下:

private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
  // Call the next filter if there is one
  if (pos < n) {
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
      Filter filter = filterConfig.getFilter();
      if (request.isAsyncSupported() && "false".equalsIgnoreCase(
          filterConfig.getFilterDef().getAsyncSupported())) {
        request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
      }
      if( Globals.IS_SECURITY_ENABLED ) {
        final ServletRequest req = request;
        final ServletResponse res = response;
        Principal principal =
          ((HttpServletRequest) req).getUserPrincipal();
        Object[] args = new Object[]{req, res, this};
        SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
      } else {
        filter.doFilter(request, response, this);
      }
    } catch (IOException | ServletException | RuntimeException e) {
      throw e;
    } catch (Throwable e) {
      e = ExceptionUtils.unwrapInvocationTargetException(e);
      ExceptionUtils.handleThrowable(e);
      throw new ServletException(sm.getString("filterChain.filter"), e);
    }
    return;
  }
  // We fell off the end of the chain -- call the servlet instance
  try {
    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
      lastServicedRequest.set(request);
      lastServicedResponse.set(response);
    }
    if (request.isAsyncSupported() && !servletSupportsAsync) {
      request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
          Boolean.FALSE);
    }
    // Use potentially wrapped request from this point
    if ((request instanceof HttpServletRequest) &&
        (response instanceof HttpServletResponse) &&
        Globals.IS_SECURITY_ENABLED ) {
      final ServletRequest req = request;
      final ServletResponse res = response;
      Principal principal =
        ((HttpServletRequest) req).getUserPrincipal();
      Object[] args = new Object[]{req, res};
      SecurityUtil.doAsPrivilege("service",
                     servlet,
                     classTypeUsedInService,
                     args,
                     principal);
    } else {
      servlet.service(request, response);
    }
  } catch (IOException | ServletException | RuntimeException e) {
    throw e;
  } catch (Throwable e) {
    e = ExceptionUtils.unwrapInvocationTargetException(e);
    ExceptionUtils.handleThrowable(e);
    throw new ServletException(sm.getString("filterChain.servlet"), e);
  } finally {
    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
      lastServicedRequest.set(null);
      lastServicedResponse.set(null);
    }
  }
}

上面的代码逻辑如下:

  • 1、通过position索引判断是否执行完了所有的filter
  • 2、如果没有,取出当前待执行的索引filter,调用其doFilter方法,在上面的接口说明中,我们看到,所有的filter类都继承了filter接口,都实现了dofilter方法;我们也注意到,该方法接收一个filterChain对象。在这段代码中,filter.doFilter(request, response, this);可以看到,将自身引用传递进去了,那么各个filter在dofilter的方法中,可以根据自身业务需要,来判断是否需要继续进行下面的filter链式执行,如果需要,就执行filterChain.doFilter方法,此时就又回到了此代码中。如果反复
  • 3、如果执行完了所有的filter,则开始执行servlet业务模块servlet.service(request, response);

以上就是详解Tomcat中Filter是怎样执行的的详细内容,更多关于Tomcat Filter执行的资料请关注脚本之家其它相关文章!

相关文章

  • Idea部署tomcat服务实现过程图解

    Idea部署tomcat服务实现过程图解

    这篇文章主要介绍了Idea部署tomcat服务实现过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 如何解决tomcat管理页面403 Access Denied问题

    如何解决tomcat管理页面403 Access Denied问题

    这篇文章主要介绍了如何解决tomcat管理页面403 Access Denied问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Tomcat 启动时 SecureRandom 非常慢解决办法

    Tomcat 启动时 SecureRandom 非常慢解决办法

    这篇文章主要介绍了Tomcat 启动时 SecureRandom 非常慢解决办法的相关资料,需要的朋友可以参考下
    2017-06-06
  • Tomcat设置maxPostSize实现过程解析

    Tomcat设置maxPostSize实现过程解析

    这篇文章主要介绍了Tomcat设置maxPostSize实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Tomcat6.0与windows 2003 server 的IIS服务器集成

    Tomcat6.0与windows 2003 server 的IIS服务器集成

    本例主要讲解Tomcat6.0与windows 2003 server 的IIS服务器集成的问题,用到的工具版 本如下:jdk是6.0、Tomcat 6.0、windows 2003 server 的IIS。
    2009-08-08
  • Tomcat日志自动分割的三种方法

    Tomcat日志自动分割的三种方法

    本文主要介绍了Tomcat日志自动分割的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • MAC 命令行启动tomcat的详细介绍

    MAC 命令行启动tomcat的详细介绍

    这篇文章主要介绍了MAC 命令行启动tomcat的详细介绍的相关资料,主要是修改授权及命令启动的介绍,需要的朋友可以参考下
    2017-08-08
  • tomcat+nginx实现多应用部署的示例代码

    tomcat+nginx实现多应用部署的示例代码

    本文主要介绍了tomcat+nginx实现多应用部署的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Tomcat 热部署的实现原理详解

    Tomcat 热部署的实现原理详解

    这篇文章主要介绍了Tomcat 热部署的实现原理详解的相关资料,需要的朋友可以参考下
    2017-01-01
  • 详解Tomcat出现404的解决方法

    详解Tomcat出现404的解决方法

    这篇文章主要介绍了详解Tomcat出现404的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08

最新评论