servlet · 2021-07-19 0

Java的Listener、Filter、Servlet执行顺序及原理

1.web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>servlet-test</display-name>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

    <context-param>
        <param-name>context-param-1</param-name>
        <param-value>context-value-1</param-value>
    </context-param>

    <context-param>
        <param-name>context-param-2</param-name>
        <param-value>context-value-2</param-value>
    </context-param>

    <!-- 配置listener -->
    <listener>
        <listener-class>com.example.listener.HelloListener</listener-class>
    </listener>

    <!-- 配置filter -->
    <filter>
        <filter-name>helloFilter</filter-name>
        <filter-class>com.example.fileter.HelloFilter</filter-class>
        <init-param>
            <param-name>filter-param-1</param-name>
            <param-value>filter-value-1</param-value>
        </init-param>
        <init-param>
            <param-name>filter-param-2</param-name>
            <param-value>filter-value-2</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>helloFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 配置servlet -->
    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.example.servlet.HelloServlet</servlet-class>
        <init-param>
            <param-name>servlet-param-1</param-name>
            <param-value>servlet-value-1</param-value>
        </init-param>
        <init-param>
            <param-name>servlet-param-2</param-name>
            <param-value>servlet-value-2</param-value>
        </init-param>
        <!--
          servlet启动加载,servlet原本是第一次访问创建对象;
          load-on-startup:服务器启动的时候创建对象;值越小优先级越高,越先创建对象
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/helloServlet</url-pattern>
    </servlet-mapping>

</web-app>

2.java代码

Listener

public class HelloListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("- - - HelloListener contextInitialized 1 - - -");
        ServletContext servletContext = sce.getServletContext();

        Enumeration<String> enumeration = servletContext.getInitParameterNames();
        while (enumeration.hasMoreElements()) {
            String key = enumeration.nextElement();
            String value = servletContext.getInitParameter(key);
            System.out.println(String.format("key: %s value: %s", key, value));
        }

        String contextPath = servletContext.getContextPath();
        String rootPath = servletContext.getRealPath("/");
        System.out.println(String.format("contextPath: %s", contextPath));
        System.out.println(String.format("rootPath: %s", rootPath));

        System.out.println("- - - HelloListener contextInitialized 2 - - -");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("- - - HelloListener contextDestroyed - - -");
    }
}

Filter

public class HelloFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("- - - HelloFilter init 1 - - -");

        Enumeration<String> enumeration = filterConfig.getInitParameterNames();
        while (enumeration.hasMoreElements()) {
            String key = enumeration.nextElement();
            String value = filterConfig.getInitParameter(key);
            System.out.println(String.format("key: %s value: %s", key, value));
        }

        System.out.println("- - - HelloFilter init 2 - - -");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 进入servlet之前调用
        System.out.println("- - - HelloFilter doFilter 1 - - -");

        chain.doFilter(request, response);

        // 处理之后调用
        System.out.println("- - - HelloFilter doFilter 2 - - -");
    }

    @Override
    public void destroy() {
        System.out.println("- - - HelloFilter destroy - - -");
    }
}

Servlet

public class HelloServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("- - - HelloServlet init 1 - - -");
        Enumeration<String> enumeration = this.getInitParameterNames();

        while (enumeration.hasMoreElements()) {
            String key = enumeration.nextElement();
            String value = this.getInitParameter(key);
            System.out.println(String.format("key: %s value: %s", key, value));
        }

        System.out.println("- - - HelloServlet init 2 - - -");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("- - - HelloServlet doGet 1 - - -");

        System.out.println(request.getContextPath());
        response.sendRedirect(request.getContextPath() + "/success.html");

        System.out.println("- - - HelloServlet doGet 2 - - -");
    }
}

3.结果

启动的顺序为 Listener -> Filter -> Servlet

结果:

13-Aug-2023 18:41:39.727 信息 [main] org.apache.catalina.core.StandardEngine.startInternal 正在启动 Servlet 引擎:[Apache Tomcat/9.0.78]
13-Aug-2023 18:41:39.744 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
13-Aug-2023 18:41:39.766 信息 [main] org.apache.catalina.startup.Catalina.start [91]毫秒后服务器启动
Connected to server
[2023-08-13 06:41:40,081] Artifact servlet-demo:war exploded: Artifact is being deployed, please wait...
- - - HelloListener contextInitialized 1 - - -
key: context-param-1 value: context-value-1
key: context-param-2 value: context-value-2
contextPath: /servlet-demo
rootPath: /home/zxm/IdeaProjects/jdk8-demo/out/artifacts/servlet_demo_war_exploded/
- - - HelloListener contextInitialized 2 - - -
- - - HelloFilter init 1 - - -
key: filter-param-1 value: filter-value-1
key: filter-param-2 value: filter-value-2
- - - HelloFilter init 2 - - -
- - - HelloServlet init 1 - - -
key: servlet-param-1 value: servlet-value-1
key: servlet-param-2 value: servlet-value-2
- - - HelloServlet init 2 - - -
[2023-08-13 06:41:40,556] Artifact servlet-demo:war exploded: Artifact is deployed successfully
[2023-08-13 06:41:40,556] Artifact servlet-demo:war exploded: Deploy took 475 milliseconds
- - - HelloFilter doFilter 1 - - -
- - - HelloServlet doGet 1 - - -
/servlet-demo
- - - HelloServlet doGet 2 - - -
- - - HelloFilter doFilter 2 - - -
- - - HelloFilter doFilter 1 - - -
- - - HelloFilter doFilter 2 - - -
/opt/apache-tomcat-9.0.78/bin/catalina.sh stop
13-Aug-2023 18:42:00.110 信息 [main] org.apache.catalina.core.StandardServer.await 通过关闭端口接收到有效的关闭命令。正在停止服务器实例。
13-Aug-2023 18:42:00.111 信息 [main] org.apache.coyote.AbstractProtocol.pause 暂停ProtocolHandler["http-nio-8080"]
13-Aug-2023 18:42:00.115 信息 [main] org.apache.catalina.core.StandardService.stopInternal 正在停止服务[Catalina]
13-Aug-2023 18:42:00.128 信息 [main] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["http-nio-8080"]
- - - HelloFilter destroy - - -
- - - HelloListener contextDestroyed - - -
13-Aug-2023 18:42:00.140 信息 [main] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["http-nio-8080"]

4.原理

1) 解析 web.xml,配置到 context

ContextConfig#webConfig() 方法解析 web.xml
ContextConfig#configureContext(WebXml webxml) 把 filter、listener、servlet 保存到 context

filter 有:com.example.fileter.HelloFilter
listener 有:com.example.listener.HelloListener
servlet 有:org.apache.catalina.servlets.DefaultServlet、org.apache.jasper.servlet.JspServlet、com.example.servlet.HelloServlet

servletMapping 有:".jspx" -> "jsp"、".jsp" -> "jsp"、"/helloServlet" -> "helloServlet"、"/" -> "default"

context 是 StandardContext、wrapper 是 StandardWrapper

// ContextConfig.java
private void configureContext(WebXml webxml) {
    ...
    for (FilterDef filter : webxml.getFilters().values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }
    for (FilterMap filterMap : webxml.getFilterMappings()) {
        context.addFilterMap(filterMap);
    }

    for (String listener : webxml.getListeners()) {
        context.addApplicationListener(listener);
    }
    ...
    for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();
        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
        }
        if (servlet.getEnabled() != null) {
            wrapper.setEnabled(servlet.getEnabled().booleanValue());
        }
        wrapper.setName(servlet.getServletName());
        wrapper.setServletClass(servlet.getServletClass());
        context.addChild(wrapper);
    }
    ...
}

2) StandardContext 启动

StandardContext#start() 方法

// StandardContext.java
@Override
protected synchronized void startInternal() throws LifecycleException {
    ...
    // Configure and call application event listeners
    if (ok) {
        if (!listenerStart()) {
            log.error(sm.getString("standardContext.listenerFail"));
            ok = false;
        }
    }

    // Configure and call application filters
    if (ok) {
        if (!filterStart()) {
            log.error(sm.getString("standardContext.filterFail"));
            ok = false;
        }
    }

    // Load and initialize all "load on startup" servlets
    if (ok) {
        if (!loadOnStartup(findChildren())) {
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
        }
    }
    ...
}

实例化 listener 有:com.example.listener.HelloListener、org.apache.tomcat.websocket.server.WsSessionListener、org.apache.tomcat.websocket.server.WsContextListener
实例化 filter 有:org.apache.tomcat.websocket.server.WsFilter、com.example.fileter.HelloFilter
示例化 servlet 有:org.apache.catalina.servlets.DefaultServlet、org.apache.jasper.servlet.JspServlet、com.example.servlet.HelloServlet

// StandardContext.java
public boolean listenerStart() {
    ...
    // Instantiate the required listeners
    String listeners[] = findApplicationListeners();
    Object results[] = new Object[listeners.length];
    boolean ok = true;
    for (int i = 0; i < results.length; i++) {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(" Configuring event listener class '" + listeners[i] + "'");
        }
        try {
            String listener = listeners[i];
            results[i] = getInstanceManager().newInstance(listener);
        } catch (Throwable t) {
            t = ExceptionUtils.unwrapInvocationTargetException(t);
            ExceptionUtils.handleThrowable(t);
            getLogger().error(sm.getString("standardContext.applicationListener", listeners[i]), t);
            ok = false;
        }
    }
    ...
    // Sort listeners in two arrays
    List<Object> eventListeners = new ArrayList<>();
    List<Object> lifecycleListeners = new ArrayList<>();
    for (Object result : results) {
        if ((result instanceof ServletContextAttributeListener) ||
                (result instanceof ServletRequestAttributeListener) || (result instanceof ServletRequestListener) ||
                (result instanceof HttpSessionIdListener) || (result instanceof HttpSessionAttributeListener)) {
            eventListeners.add(result);
        }
        if ((result instanceof ServletContextListener) || (result instanceof HttpSessionListener)) {
            lifecycleListeners.add(result);
        }
    }

    // Listener instances may have been added directly to this Context by
    // ServletContextInitializers and other code via the pluggability APIs.
    // Put them these listeners after the ones defined in web.xml and/or
    // annotations then overwrite the list of instances with the new, full
    // list.
    eventListeners.addAll(Arrays.asList(getApplicationEventListeners()));
    setApplicationEventListeners(eventListeners.toArray());
    for (Object lifecycleListener : getApplicationLifecycleListeners()) {
        lifecycleListeners.add(lifecycleListener);
        if (lifecycleListener instanceof ServletContextListener) {
            noPluggabilityListeners.add(lifecycleListener);
        }
    }
    setApplicationLifecycleListeners(lifecycleListeners.toArray());
    ...
}

public boolean filterStart() {

    if (getLogger().isDebugEnabled()) {
        getLogger().debug("Starting filters");
    }
    // Instantiate and record a FilterConfig for each defined filter
    boolean ok = true;
    synchronized (filterConfigs) {
        filterConfigs.clear();
        for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
            String name = entry.getKey();
            if (getLogger().isDebugEnabled()) {
                getLogger().debug(" Starting filter '" + name + "'");
            }
            try {
                ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());
                filterConfigs.put(name, filterConfig);
            } catch (Throwable t) {
                t = ExceptionUtils.unwrapInvocationTargetException(t);
                ExceptionUtils.handleThrowable(t);
                getLogger().error(sm.getString("standardContext.filterStart", name), t);
                ok = false;
            }
        }
    }

    return ok;
}

public boolean loadOnStartup(Container children[]) {

    // Collect "load on startup" servlets that need to be initialized
    TreeMap<Integer,ArrayList<Wrapper>> map = new TreeMap<>();
    for (Container child : children) {
        Wrapper wrapper = (Wrapper) child;
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0) {
            continue;
        }
        Integer key = Integer.valueOf(loadOnStartup);
        map.computeIfAbsent(key, k -> new ArrayList<>()).add(wrapper);
    }

    // Load the collected "load on startup" servlets
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                getLogger().error(
                        sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()),
                        StandardWrapper.getRootCause(e));
                // NOTE: load errors (including a servlet that throws
                // UnavailableException from the init() method) are NOT
                // fatal to application startup
                // unless failCtxIfServletStartFails="true" is specified
                if (getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;

}

3) 匹配请求

CoyoteAdapter#service() 方法处理请求

CoyoteAdapter#postParseRequest() 根据 url 找到 servlet

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 执行

// CoyoteAdapter.java
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
     try {
        // Parse and set Catalina and configuration specific
        // request parameters
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            // check valves if we support async
            request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
        }
        ...
    } catch (IOException e) {
        // Ignore
    }
    ... 
}

protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res,
            Response response) throws IOException, ServletException {
    ...
    while (mapRequired) {
        connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
    }
    ...
}

Mapper#internalMapExactWrapper() 方法,根据 url 找到 MappedWrapper (包含 url 对应的 StandardWrapper,也就是 servlet),赋值给 MappingData

此时 request 的 mappingData 成员变量包含匹配的 servlet

// Mapper.java
public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException {

    if (host.isNull()) {
        String defaultHostName = this.defaultHostName;
        if (defaultHostName == null) {
            return;
        }
        host.setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
        host.getCharChunk().append(defaultHostName);
    }
    host.toChars();
    uri.toChars();
    internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}

private void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData)
        throws IOException {
     ...
    // Wrapper mapping
    if (!contextVersion.isPaused()) {
        internalMapWrapper(contextVersion, uri, mappingData);
    }
    ... 
}

private void internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData)
        throws IOException {
     ...
    // Rule 1 -- Exact Match
    MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
    internalMapExactWrapper(exactWrappers, path, mappingData);   
    ...
}

4) 执行

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 执行

// CoyoteAdapter.java
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
     try {
        // Parse and set Catalina and configuration specific
        // request parameters
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            // check valves if we support async
            request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
        }
        ...
    } catch (IOException e) {
        // Ignore
    }
    ... 
}

执行顺序:

  1. StandardEngineValve#invoke()
  2. AccessLogValve#invoke()
  3. ErrorReportValve#invoke()
  4. StandardHostValve#invoke()
  5. NonLoginAuthenticator#invoke()
  6. StandardContextValve#invoke()
  7. StandardWrapperValve#invoke()

ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); 创建过滤器链
filterChain.doFilter(request.getRequest(), response.getResponse()) 执行过滤器链

// StandardWrapperValve.java
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
    ...
    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();
    ...
    // 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());
                }
            }

        }
    }
    ...
}

创建 ApplicationFilterChain,把 ApplicationFilterConfig 添加到 ApplicationFilterChain 的 filters 成员变量中

// ApplicationFilterFactory.java
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;
}

执行所有 filter#doFilter() 方法后,执行 servlet.service(request, response) 方法

// ApplicationFilterChain.java
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {

    if (Globals.IS_SECURITY_ENABLED) {
        final ServletRequest req = request;
        final ServletResponse res = response;
        try {
            java.security.AccessController.doPrivileged((java.security.PrivilegedExceptionAction) () -> {
                internalDoFilter(req, res);
                return null;
            });
        } catch (PrivilegedActionException pe) {
            Exception e = pe.getException();
            if (e instanceof ServletException) {
                throw (ServletException) e;
            } else if (e instanceof IOException) {
                throw (IOException) e;
            } else if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new ServletException(e.getMessage(), e);
            }
        }
    } else {
        internalDoFilter(request, response);
    }
}

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);
        }
    }
}