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)
参考资料:
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。