zoukankan      html  css  js  c++  java
  • Java Servlet(十二):Servlet、Listener、Filter之间的执行流程分析

    时隔几年后,看到本系列文章讲解的内容缺少了不少内容:周末无事分析了Spring Security是如何被集成到Web Servlet(SpringMVC)时,需要重新理清Filter、Listener、Servlet(SpringMVC#DispatcherServlet)之间的执行顺序,于是就有了本篇文章。这个话题是Web Servlet学习中的一个重点,弄清它们之间的执行流程,有助于理解SpringMVC、Spring Security这些框架是否如何与Web Servlet集成到一起。

    测试

    新建一个web servlet pom.xml项目web-servlet-01

    pom.xml引入依赖:

        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
          <scope>compile</scope>
        </dependency>
    
        <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
        <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
        </dependency>

    项目结构:

    定义HelloServlet.java

    package com.dx.test.servlets;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    public class HelloServlet implements Servlet {
        private Log log = LogFactory.getLog(HelloServlet.class);
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            log.info("IndexServlet#init()");
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            log.info("IndexServlet#service()");
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        @Override
        public void destroy() {
            log.info("IndexServlet#destory()");
        }
    }

    在HelloServlet中需要在它的init()、service()、destory()方法中书写日志,为后边测试使用。

    定义MyFilter.java

    package com.dx.test.filters;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    public class MyFilter implements Filter {
        private static Log log= LogFactory.getLog(MyFilter.class);
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("MyFilter#init()");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("MyFilter#doFilter() before");
            chain.doFilter(request, response);
            log.info("MyFilter#doFilter() after");
        }
    
        @Override
        public void destroy() {
            log.info("MyFilter#destroy()");
        }
    }

    注意,在doFilter()方法中添加日志的方式:

    1)在chain.doFilter(request,response)代码之前添加了日志;

    2)在chain.doFilter(request,response)代码之后添加日志。

    定义MyServletRequestListener.java

    package com.dx.test.listeners;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import javax.servlet.ServletRequestEvent;
    import javax.servlet.ServletRequestListener;
    
    public class MyServletRequestListener implements ServletRequestListener {
        private static Log log = LogFactory.getLog(MyServletRequestListener.class);
    
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            log.info("MyServletRequestListener#requestInitialized()");
        }
    
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
            log.info("MyServletRequestListener#requestDestroyed()");
        }
    }

    说明:

    ServletRequestListener是在每次servlet请求之前都会执行#requestInitialized(),在执行servlet会后执行#requestDestroyed()。

    定义MyServletContextListener.java

    package com.dx.test.listeners;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    
    public class MyServletContextListener implements ServletContextListener {
        private static Log log= LogFactory.getLog(MyServletContextListener.class);
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            log.info("MyServletContextListener#contextInitialized()");
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            log.info("MyServletContextListener#contextDestroyed()");
        }
    }

    说明:

    ServletContextListener是在每次servlet服务启动执行init方法,在servlet服务关闭时,执行destory方法。

    web.xml配置

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <welcome-file-list>
        <welcome-file>/index</welcome-file>
      </welcome-file-list>
      <filter>
        <filter-name>myFilter</filter-name>
        <filter-class>com.dx.test.filters.MyFilter</filter-class>
      </filter>
      <filter-mapping>
        <filter-name>myFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
      <listener>
        <listener-class>com.dx.test.listeners.MyServletContextListener</listener-class>
      </listener>
    
      <listener>
        <listener-class>com.dx.test.listeners.MyServletRequestListener</listener-class>
      </listener>
      
      <servlet>
        <servlet-name>hello_servlet</servlet-name>
        <servlet-class>com.dx.test.servlets.HelloServlet</servlet-class>
        <!--<load-on-startup>1</load-on-startup>-->
      </servlet>
      <servlet-mapping>
        <servlet-name>hello_servlet</servlet-name>
        <url-pattern>/hello</url-pattern>
      </servlet-mapping>
    </web-app>

    测试日志:

    启动Servlet服务,输出日志:

    // 启动tomcat
    /opt/apache-tomcat-8.5.49/bin/catalina.sh run
    [2020-01-18 12:53:34,440] Artifact web-servlet-01:war: Waiting for server connection to start artifact deployment...
    18-Jan-2020 12:53:42.076 信息 [main] org.apache.coyote.AbstractProtocol.init 初始化协议处理器 ["http-nio-8080"]
    18-Jan-2020 12:53:42.092 信息 [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
    18-Jan-2020 12:53:42.099 信息 [main] org.apache.coyote.AbstractProtocol.init 初始化协议处理器 ["ajp-nio-8009"]
    18-Jan-2020 12:53:42.100 信息 [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
    18-Jan-2020 12:53:42.100 信息 [main] org.apache.catalina.startup.Catalina.load Initialization processed in 284 ms
    18-Jan-2020 12:53:42.116 信息 [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
    18-Jan-2020 12:53:42.116 信息 [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.49
    18-Jan-2020 12:53:42.120 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
    18-Jan-2020 12:53:42.125 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["ajp-nio-8009"]
    18-Jan-2020 12:53:42.127 信息 [main] org.apache.catalina.startup.Catalina.start Server startup in 26 ms
    Connected to server
    18-Jan-2020 12:53:42.814 信息 [RMI TCP Connection(2)-127.0.0.1] com.dx.test.listeners.MyServletContextListener.contextInitialized MyServletContextListener#contextInitialized()
    18-Jan-2020 12:53:42.822 信息 [RMI TCP Connection(2)-127.0.0.1] com.dx.test.filters.MyFilter.init MyFilter#init()

    首次,访问http://localhost:8080/web_servlet_01_war/hello输出日志:

    // 第一次访问 /hello
    18-Jan-2020 12:54:10.687 信息 [http-nio-8080-exec-5] com.dx.test.listeners.MyServletRequestListener.requestInitialized MyServletRequestListener#requestInitialized()
    18-Jan-2020 12:54:10.687 信息 [http-nio-8080-exec-5] com.dx.test.servlets.IndexServlet.init IndexServlet#init()
    18-Jan-2020 12:54:10.688 信息 [http-nio-8080-exec-5] com.dx.test.filters.MyFilter.doFilter MyFilter#doFilter() before
    18-Jan-2020 12:54:10.688 信息 [http-nio-8080-exec-5] com.dx.test.servlets.IndexServlet.service IndexServlet#service()
    18-Jan-2020 12:54:10.688 信息 [http-nio-8080-exec-5] com.dx.test.filters.MyFilter.doFilter MyFilter#doFilter() after
    18-Jan-2020 12:54:10.688 信息 [http-nio-8080-exec-5] com.dx.test.listeners.MyServletRequestListener.requestDestroyed MyServletRequestListener#requestDestroyed()

    再次,访问http://localhost:8080/web_servlet_01_war/hello输出日志:

    // 第二次访问 /hello
    18-Jan-2020 12:54:29.645 信息 [http-nio-8080-exec-6] com.dx.test.listeners.MyServletRequestListener.requestInitialized MyServletRequestListener#requestInitialized()
    18-Jan-2020 12:54:29.645 信息 [http-nio-8080-exec-6] com.dx.test.filters.MyFilter.doFilter MyFilter#doFilter() before
    18-Jan-2020 12:54:29.645 信息 [http-nio-8080-exec-6] com.dx.test.servlets.IndexServlet.service IndexServlet#service()
    18-Jan-2020 12:54:29.645 信息 [http-nio-8080-exec-6] com.dx.test.filters.MyFilter.doFilter MyFilter#doFilter() after
    18-Jan-2020 12:54:29.646 信息 [http-nio-8080-exec-6] com.dx.test.listeners.MyServletRequestListener.requestDestroyed MyServletRequestListener#requestDestroyed()

    关闭Servlet服务,输出日志:

    // 关闭tomcat
    /opt/apache-tomcat-8.5.49/bin/catalina.sh stop
    18-Jan-2020 12:58:54.440 信息 [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
    18-Jan-2020 12:58:54.441 信息 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
    18-Jan-2020 12:58:54.455 信息 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"]
    18-Jan-2020 12:58:54.463 信息 [main] org.apache.catalina.core.StandardService.stopInternal 正在停止服务[Catalina]
    18-Jan-2020 12:58:54.465 信息 [localhost-startStop-2] com.dx.test.servlets.IndexServlet.destroy IndexServlet#destory()
    18-Jan-2020 12:58:54.465 信息 [localhost-startStop-2] com.dx.test.filters.MyFilter.destroy MyFilter#destroy()
    18-Jan-2020 12:58:54.465 信息 [localhost-startStop-2] com.dx.test.listeners.MyServletContextListener.contextDestroyed MyServletContextListener#contextDestroyed()
    18-Jan-2020 12:58:54.476 信息 [main] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["http-nio-8080"]
    18-Jan-2020 12:58:54.479 信息 [main] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["ajp-nio-8009"]
    18-Jan-2020 12:58:54.479 信息 [main] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["http-nio-8080"]
    18-Jan-2020 12:58:54.480 信息 [main] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["ajp-nio-8009"]
    Disconnected from server

    结论

    执行流程图说明:

    1)用户通过在浏览器中输出网址http://localhost:8080/web_servlet_01_war/hello,访问web容器中部署的servlet服务;

    2)用户请求信息被web容器监听到,web容器会浏览器请求信息进行封装HttpServletRequest、响应信息会被封装到HttpServletResponse,并找到对应的servlet容器;

    3)请求进入servlet容器会被listener监听到,listener分为两类:ServletRequestListener、ServletContextListener。

    ServletRequestListener会在每次servlet请求过程中,都会执行它的#requestInitialized方法,然后交给filter去执行;

    ServletContextListener会在servlet服务启动时,调用它的#contextInitialized()方法;在servlet服务关闭时,调用它的#contextDestroyed()方法。

    4)然后请求被filter执行,调用filter#doFilter() before方法,filter的初始化时刻:“Servlet服务启动” 或 “第一次访问初始化”,根据依据<load-on-startup>1</load-on-startup>参数;

    5)请求交给servlet#service()方法;

    6)servlet#service()方法执行完成后,会回到filter#doFilter() after方法;

    7)执行ServletRequestListener#requestDestroyed()方法;

    8)将请求响应反馈给浏览器;

    9)当服务关闭时,会执行servlet#destory()、filter#destory()、ServletContextListener#contextDestroyed()

    源码分析

    Tomcat接收到请求后,会在容器(Engine、Host、Context、Wrapper各级组件)中匹配,并且在它们的管道中流转,最终会适配到一个StandardWrapper的基础阀的-org.apache.catalina.core.StandardWrapperValve 的invoke方法。

    下边具体看下请求匹配到最基础的 StandardWrapper 组件的管道中,之后是如何处理的:

    StandardWrapperValve阀的#invoke方法:

    final class StandardWrapperValve extends ValveBase {
        ...
        @Override
        public final void invoke(Request request, Response response)
            throws IOException, ServletException {
    
            // Initialize local variables we may need
            boolean unavailable = false;
            Throwable throwable = null;
            // This should be a Request attribute...
            long t1=System.currentTimeMillis();
            requestCount.incrementAndGet();
            StandardWrapper wrapper = (StandardWrapper) getContainer();
            Servlet servlet = null;
            Context context = (Context) wrapper.getParent();
    
            // Check for the application being marked unavailable
            if (!context.getState().isAvailable()) {
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable"));
                unavailable = true;
            }
    
            // Check for the servlet being marked unavailable
            if (!unavailable && wrapper.isUnavailable()) {
                container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName()));
                long available = wrapper.getAvailable();
                if ((available > 0L) && (available < Long.MAX_VALUE)) {
                    response.setDateHeader("Retry-After", available);
                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName()));
                } else if (available == Long.MAX_VALUE) {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName()));
                }
                unavailable = true;
            }
    
            // 分配一个servlet实例用来处理请求
            // Allocate a servlet instance to process this request
            try {
                if (!unavailable) {
                    // 调用StandardWrapper#allocate()方法,获取到servlet实例
                servlet = wrapper.allocate();
                }
            } catch (UnavailableException e) {
                ...
            } catch (ServletException e) {
                ...
            } catch (Throwable e) {
                ...
            }
    
            ...
    
            // 为当前请求创建一个过滤器链
          // Create the filter chain for this request
            ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
    
            // 为当前请求调用过滤器链,注意:这也会调用servlet实例的service()方法
            // Call the filter chain for this request. NOTE: This also calls the servlet's service() method
            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) {
                ...
            } catch (IOException e) {
                ...
            } catch (UnavailableException e) {
                ...
            } catch (ServletException e) {
                ...
            } catch (Throwable e) {
                ...
            } finally {
                // Release the filter chain (if any) for this request
                if (filterChain != null) {
                    filterChain.release();
                }
    
                // Deallocate the allocated servlet instance
                try {
                    if (servlet != null) {
                        wrapper.deallocate(servlet);
                    }
                } catch (Throwable e) {
                    ...
                }
    
                // If this servlet has been marked permanently unavailable,
                // unload it and release this instance
                try {
                    if ((servlet != null) &&
                        (wrapper.getAvailable() == Long.MAX_VALUE)) {
                        wrapper.unload();
                    }
                } catch (Throwable e) {
                    ...
                }
    
                long t2=System.currentTimeMillis();
                long time=t2-t1;
                processingTime += time;
                if( time > maxTime) maxTime=time;
                if( time < minTime) minTime=time;
            }
        }
    }

    上边代码主要包含
    1)servlet = wrapper.allocate(); 调用StandardWrapper#allocate()方法,获取到servlet实例
    2)ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);为当前请求创建一个过滤链,(非异步情况下)并调用filterChain.doFilter(request.getRequest(), response.getResponse());
    3)filter#doFilter()、servlet#service()的执行是在filterChain.doFilter(request.getRequest(), response.getResponse());代码内部执行的。

    ApplicationFilterChain#doFilter内部代码:

    public final class ApplicationFilterChain implements FilterChain {
        ...
    
        @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(
                        new java.security.PrivilegedExceptionAction<Void>() {
                            @Override
                            public Void run()
                                throws ServletException, IOException {
                                internalDoFilter(req,res);
                                return null;
                            }
                        }
                    );
                } catch( PrivilegedActionException pe) {
                    ...
                }
            } 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) {
                    ...
                }
                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)上边代码会递归调用 ApplicationFilterChain#doFilter(...);
    2)递归执行到最底层doFiler,之后会调用上边代码中 servlet.service() 或者 SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);,也即是执行 servlet 的 service() 方法。然后一次从 底层 到 顶层 返回递归调用代码出,结束调用。这也就是之职责链的模式的应用,具体请参考《设计模式(九)责任链(Chain of Responsibility)

    ServletRequestListener 触发:

    org.apache.catalina.core.StandardHostValve#invoke方法:

    final class StandardHostValve extends ValveBase {
        ...
    
        @Override
        public final void invoke(Request request, Response response)
            throws IOException, ServletException {
    
            // Select the Context to be used for this Request
            Context context = request.getContext();
            if (context == null) {
                return;
            }
    
            if (request.isAsyncSupported()) {
                request.setAsyncSupported(context.getPipeline().isAsyncSupported());
            }
    
            boolean asyncAtStart = request.isAsync();
    
            try {
                context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
    
                if (!asyncAtStart && !context.fireRequestInitEvent(request.getRequest())) {
                    // Don't fire listeners during async processing (the listener fired for the request that called startAsync()).
                    // If a request init listener throws an exception, the request is aborted.
                    return;
                }
    
                // Ask this Context to process this request. Requests that are already in error must have been routed here to check for
                // application defined error pages so DO NOT forward them to the the application for processing.
                try {
                    if (!response.isErrorReportRequired()) {
                        context.getPipeline().getFirst().invoke(request, response);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
                    // If a new error occurred while trying to report a previous error allow the original error to be reported.
                    if (!response.isErrorReportRequired()) {
                        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                        throwable(request, response, t);
                    }
                }
    
                // Now that the request/response pair is back under container control lift the suspension so that the error handling can
                // complete and/or the container can flush any remaining data
                response.setSuspended(false);
    
                Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
    
                // Protect against NPEs if the context was destroyed during a long running request.
                if (!context.getState().isAvailable()) {
                    return;
                }
    
                // Look for (and render if found) an application level error page
                if (response.isErrorReportRequired()) {
                    // If an error has occurred that prevents further I/O, don't waste time producing an error report that will never be read
                    AtomicBoolean result = new AtomicBoolean(false);
                    response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
                    if (result.get()) {
                        if (t != null) {
                            throwable(request, response, t);
                        } else {
                            status(request, response);
                        }
                    }
                }
    
                if (!request.isAsync() && !asyncAtStart) {
                    context.fireRequestDestroyEvent(request.getRequest());
                }
            } finally {
                // Access a session (if present) to update last accessed time, based on a strict interpretation of the specification
                if (ACCESS_SESSION) {
                    request.getSession(false);
                }
    
                context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
            }
        }
    
        ...
    }

    说明:
    1)context.fireRequestInitEvent(request.getRequest()):就是调用 StandardContext#fireRequestInitEvent(...) 触发 ServletRequestListener#requestInitialized(event)

    2)context.fireRequestDestroyEvent(request.getRequest()):就是调用 StandardContext#fireRequestDestroyEvent(...) 触发 ServletRequestListener#requestDestroyed(event)

    参考资料:

    Tomcat系列源码分析

  • 相关阅读:
    表单工具可以什么
    页面嵌套的方式展现报表
    EChars图类型
    SVG图类型
    JSP <c:import>和<jsp:include>区别【转】
    Servlet配置
    jsp的scope属性【转载】
    Cookie、Session【转载】
    page、request、session、application区别【转载】
    C++笔记------static 和 const 在类中用法
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/12210783.html
Copyright © 2011-2022 走看看