Tomcat8.5.23中ApplicationFilterChain对象的线程安全性分析

一、前言

在前一篇文章中分析ApplicationFilterChain(之后简称filterChain)的源码时遗留了线程安全问题未能彻底理解,因此有了本文,来彻底研究下filterChain对象的线程安全是如何保证的。

二、正文

1. 疑惑的产生

之所以会觉得filterChain对象线程不安全,是因为在分析其创建过程时找到了通过new创建出对象的代码,位于ApplicationFilterFactory类的createFilterChain方法中,在这一句打上断点,之后重新多次访问应用,发现断点没有命中,说明并不是每一个浏览器请求都创建了一个filterChain,由此猜测filterChain可能是单例的,后来访问不同URL,发现断点再次命中,进一步猜测filterChain可能是针对每一个URL-Pattern是单例的,若单例猜测成立,则需要在代码中保证线程安全,查看其源码,却没有发现相关的代码,类中只有两个static的ThreadLocal型变量,成员变量并没有做处理,因此确定单例猜想是不成立的,为了进一步验证,使用PC与手机同时访问同一URL,发现两次断点命中后得到的filterChain不是同一个,由此猜测filterChain的创建可能采用了池化技术,以保证两个线程不同时调用同一filterChain的doFilter方法造成冲突,但是在filterChain的创建源码中并没有发现池化相关的操作,因此疑惑产生。

2. 探究的思路与答案

经过高人点拨,得知filterChain并不是单例,而是每个请求都会生成一个,因此继续阅读相关源码,以求回答以下
问题:
(1)filterChain与请求之间是如何关联的?
(2)浏览器的多次请求对应于同一个filterChain如何解释?
(3)调试中观察到的池化复用机制是怎样的?
(4)StandardWrapperValve类名中的Valve有何特殊含义,是否代表了某种特殊机制,这个类到底做了些什么事情?
(5)StandardWrapperValve的invoke方法中出现了StandWrapper,二者之间的关系是怎样的?
(6)池化技术要应对的线程安全问题如何产生与处理?
带着这些问题,一边调试一边阅读了ApplicationFilterFactory、StandardWrapperValve、StandardPipeline、StandardWrapper等相关类的源码,最终找到了这些问题的答案,虽然答案还不是很详尽,但是比较接近了,这里先给出以上问题的答案,再详述探究过程。
问题的答案:
(1)后台解析封装后的请求是一个Request对象,filterChain是其一个成员变量,每当filterChain被new出来之后,都会set到对应的Request对象中去;
(2)其实浏览器的多次请求在到达后台之后可能会被封装成同一个Request对象,每个Request对象在请求处理完毕后会进行recycle,recycle期间不会重置filterChain,所以,复用Request对象,也就复用了其中的filterChain;
(3)复用的主体其实是Request,在将浏览器原始请求封装成Request时,这里应该是采用了池化复用,调试观察到同一URL间歇的请求会得到同一个Request对象,而高并发的请求则会导致new出新的Request对象,猜测在Request的产生有复用机制(这里需要进一步探究),另外一处复用的地方是Servlet对象的复用,在StandardWrapper中完成(这里打算下篇文章分解);
(4)经查阅,StandardWrapperValve是阀的意思,进一步查阅资料得知相关的类还有StandardPipeline、StandardWrapper,分别是管道与容器的意思,阅读相关接口文档得知这三者构成了一种资源池化机制,容器中存储资源,一个容器连接着一个管道,一个管道又包含有多个阀,三者协作来实现资源的无冲突分配,具体的机制在下一篇文章中分析;
(5)StandWrapper是容器,StandardWrapperValve是容器对应的阀,请求的处理是在阀中进行的,但是阀需要先从容器中分配出资源(Servlet实例)来处理请求,这就是二者的关系;
(6)池化技术要应对的线程安全问题主要发生再Servlet实例上,若为每一个请求生成一个Servlet实例,则在高并发的情况下生成的实例将会很多,可能带来频繁的GC影响性能,若Servlet为单例,则多个请求线程使用同一个单例,若要保证线程安全,则需要有同步机制,但加锁带来的成本又太高,同样影响性能,因此才有了池化技术,在Servlet实例处理完请求后将其回收利用处理下一个请求,只在所有当前Servlet都忙碌时才创建新的Servlet实例,只要保证同一个Servlet实例不被两个线程同时调用即可,由此提升性能。另一个场景是Request对象的复用,会在后续的文章中进行分析。

3. 探究的过程

从StandardWrapperValve类入手,看看它是做什么的:
Tomcat8.5.23中ApplicationFilterChain对象的线程安全性分析_第1张图片
(1)首先分析其类关系图如上图所示,直观感觉Valve接口是最重要的切入点,毕竟名字当中都有,仔细阅读Valve接口源码与注释,得出信息如下:
一个阀(Valve)关联着某一个特定的容器(Container),一系列的阀相互关联形成管道(Pipeline),源码注释指出,Valve中有一个重要的方法invoke,其工作流程如下:
a)检测/更改指定请求/响应的properties;
b)检测指定请求的properties,完全生成相应的响应并return;
c)检测请求/响应的properties,对其进行包装以增强功能,并将其传递给下一个Valve;
d)若响应未生成,使用getNext().invoke()调用下一个Valve;
e)检测生成响应的properties。
Valve不应该做的事情:
a)更改用于导向处理控制流的请求properties(例如,一个请求是从一个关联于Host/Context的pipeline发起的,不要更改其目标虚拟主机);
b)创建一个完成的响应并且把请求、响应传递给管道中的下一个Valve;
c)在完整地生成响应之前消耗请求中的inputStream中的字节,或者在传递请求之前对其包装;
d)在getNext().invoke()方法返回之后修改响应头;
e)在getNext().invoke()方法返回之后对outputStream进行操作。
(2)其次分析Contained接口,发现只有两个setContainer与getContainer方法,再分析LifecycleMBeanBase,发现它是用于做JMX程序状态监控的,可暂时不予考虑;
(3)再其次分析ValveBase,发现它主要是定义了container,nextValve成员并实现了一些set/get方法以及JMX相关的内容,意义不是很大,所以继续看StandardWrapperValve;
StandardWrapperValve中有两个原子类型变量引起了我的注意但是后面发现它们只是用于JMX的,所以直接略过,重点还是invoke方法,浏览全部代码后发现基本上也就这一个方法最有价值,因此将其代码贴出来,通过加注释的方式对其进行详细分析如下:

//StandardWrapperValve.java
    /**
     * Invoke the servlet we are managing, respecting the rules regarding
     * servlet lifecycle and SingleThreadModel support.
     *
     * @param request Request to be processed
     * @param response Response to be produced
     *
     * @exception IOException if an input/output error occurred
     * @exception ServletException if a servlet error occurred
     */
    @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();
        //原子变量,用于JMX监控计数
        requestCount.incrementAndGet();
        //取得阀所关联的Servlet容器
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        //取得wrapper容器的父容器context
        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;
        }
		
		//检查wrapper容器本身标识的可用性
        // Check for the servlet being marked unavailable
        if (!unavailable && wrapper.isUnavailable()) {
            container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
                    wrapper.getName()));
            //这里的available表示等待容器可用需要available毫秒的时间
            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()));
            //若available为MAX_VALUE则表示永远不可用
            } else if (available == Long.MAX_VALUE) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                        sm.getString("standardWrapper.notFound",
                                wrapper.getName()));
            }
            unavailable = true;
        }

        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                //如果wrapper容器可用,则使用该容器分配出一个Servlet实例
                servlet = wrapper.allocate();
            }
        //处理各种各样的不可用的异常情况
        } catch (UnavailableException e) {
            container.getLogger().error(
                    sm.getString("standardWrapper.allocateException",
                            wrapper.getName()), e);
            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()));
            }
        } catch (ServletException e) {
            container.getLogger().error(sm.getString("standardWrapper.allocateException",
                             wrapper.getName()), StandardWrapper.getRootCause(e));
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.allocateException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        }
	    //取得请求路径
        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);
        //根据请求、容器、分配的servlet去创建filterChain(注意请求其实会被复用,所以filterChain跟着被复用)
        // 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
        try {
            if ((servlet != null) && (filterChain != null)) {
                //Swallow output是什么操作暂时没弄清楚,不过它目前不是分析的关键,暂时忽略
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    try {
                        SystemLogHandler.startCapture();
                        if (request.isAsyncDispatching()) {
                            //对异步分发的请求进行处理
                            request.getAsyncContextInternal().doInternalDispatch();
                        } else {
                            //基本上算是最后一步了,调用得到的filterChain处理请求(注意servlet被放在了filterChain里面,最后也在filterChain的方法中完成调用)
                            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处理请求(注意servlet被放在了filterChain里面,最后也在filterChain的方法中完成调用)
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }

            }
        //后面就是各种异常情况的处理
        } catch (ClientAbortException e) {
            throwable = e;
            exception(request, response, e);
        } catch (IOException e) {
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (UnavailableException e) {
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
            //            throwable = e;
            //            exception(request, response, e);
            wrapper.unavailable(e);
            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()));
            }
            // Do not save exception in 'throwable', because we
            // do not want to do exception(request, response, e) processing
        } catch (ServletException e) {
            Throwable rootCause = StandardWrapper.getRootCause(e);
            if (!(rootCause instanceof ClientAbortException)) {
                container.getLogger().error(sm.getString(
                        "standardWrapper.serviceExceptionRoot",
                        wrapper.getName(), context.getName(), e.getMessage()),
                        rootCause);
            }
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
            throwable = e;
            exception(request, response, e);
        }
		//每一个请求完成后将相应的filterChain也释放掉
        // Release the filter chain (if any) for this request
        if (filterChain != null) {
            filterChain.release();
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
            	//这里是比较重要的一步,应该是容器对使用完毕的Servlet实例的回收,以便于复用
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, 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) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.unloadException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }
        //收集JMX统计监控用的数据
        long t2=System.currentTimeMillis();
        long time=t2-t1;
        processingTime += time;
        if( time > maxTime) maxTime=time;
        if( time < minTime) minTime=time;
    }

由以上代码的分析可以看出,虽然代码很多,但很多都是特殊情况与异常、统计信息的代码,StandardWrapperValve.invoke()真正做的事情也就两件,一是使用容器分配出可用的Servlet实例,二是利用请求、Servlet、容器构建出相应的filterChain并调用来处理请求。
(4)最后,再说一下filterChain与Request是如何关联的吧,其实很简单,从ApplicationFilterFactory类的createFilterChain方法的代码就能看出二者是相互依存的,另外去查看一下Request类的源码就会发现filterChain是它的一个成员变量,而且在recycle方法中没有重置,因此可以确定filterChain与Request是关联的,生命周期是相同的,这里贴出ApplicationFilterFactory的代码,Request的代码太多就不贴了,读者可以自己去看一下:

//ApplicationFilterFactory.java
    /**
     * Construct a FilterChain implementation that will wrap the execution of
     * the specified servlet instance.
     *
     * @param request The servlet request we are processing
     * @param wrapper The wrapper managing the servlet instance
     * @param servlet The servlet instance to be wrapped
     *
     * @return The configured FilterChain instance or null if none is to be
     *         executed.
     */
    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();
                    //filterChain刚创建出来就被设置到了req中,足以说明二者相互依存的关系
                    req.setFilterChain(filterChain);
                    //------------------------------------------
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

(5)最后的最后,其实还有StandardPipeline和StandardWrapper的原理没有讲,这些涉及到StandardWrapperValve的创建、组织与销毁,以及Servlet实例的复用,但这些都放在一篇文章中就显得太冗长了,后面会再开新的文章来讲。

4. 分析源码时的额外收获

分析源码时会打很多断点,跟踪很多次程序的运行,因为代码很多都是相互关联的,所以在分析的过程中也会接触到除分析目标以外的一些知识,将其分别记录如下:
(1)根据RequestDispatcher接口中常量推测,Tomcat会为每一个被分发的请求设置一个request attribute name与attribute value(各种类型的都有);
(2)forward操作必须在flush操作之前,否则IllegalStateException,后续未提交的输出丢失;
(3)看到了装饰者模式(ServletRequestWrapper、ServletResponseWrapper)、观察者模式(ContainerListener)、适配器模式(CoyoteAdapter)的应用;
(4)RequestDispatcher接口除了有forward方法还有include方法,用于使能服务端include,被include的Servlet只能改内容,不能改状态或者头等信息;
(5)RequestDispatcher是servlet API,而AsyncDispatcher是Tomcat API;
(6)SpringBoot与Tomcat的结合点:通过TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer(ServletContextInitializer… initializers)来启动Tomcat;
(7)Tomcat容器分为四个层级:Engine、Host、Context、Wrapper;
(8)Tomcat容器可能会关联一些组件以增强其功能,目前支持的组件包括:
a)Class Loader;
b)Logger;
c)Manager(管理容器关联的session池);
d)Realm(安全域的只读接口,用于验证用户身份与角色);
e)Resources(访问静态资源的JNDI目录上下文);
(9)一个容器通常只有一个Pipeline单例,容器的请求处理功能通常被封装在一个容器相关的阀中,通常是pipeline的最后一个阀,这个阀通过setBasic来设置,其他的阀在此之前按照添加的顺序被执行;
(10)StandardPipeline规定:请求正在被处理时,addValve与removeValve方法是不准被调用的,否则保持单线程状态的机制就需要被改变;
(11)调试发现,Request对象在每一个请求完毕时都会recycle(),而recycle()中没有重置filterChain,因此filterChain随着Request对象的复用而被复用了;
(12)new Request()的调用栈:new Request()<–Connector.createRequest()<–CoyoteAdapter.service()(通过org.apache.coyote.Request.getNote(ADAPTER_NOTES)复用Request)<–Http11Processor.service()<–AbstractProcessorLight.process()<–AbstractProtocol.ConnectionHandler.process()<–NioEndpoint.SocketProcessor.doRun()<–(Tomcat自定义的Socket连接处理Runnable实现类)SocketProcessorBase.run()<–Tomcat向ThreadPoolExecutor提交的TaskThread.WrappingRunnable对象被调度执行;
(13)Connector对象处理请求的调用链:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

三、后记

优秀的源码博大精深,加上自己水平有限,所以若文中有错误、遗漏,请各位读者不吝指出,先行感谢;
项目、源码分析系列将持续更新,欢迎关注。

你可能感兴趣的:(Java-Web,Java,Tomcat,源码分析,JavaWeb,Java,Tomcat,源码,FilterChain)