第十七课: StandardWrapper
课前复习:
不知道大家是否还有印象,就是在 6 、 7 节课说的 4 种 container, 粗略的从大到小来说就是 engine,host,context , 和 wrapper 。当时写的时候很纠结,因为后面有详细介绍这 4 个的计划,所以前面写的可能不是很详尽。
让我们回忆一下,当一个请求到来的时候,发生了什么。比如什么创建 Request 这里就不说了,之后 connector 会调用与之关联的容器的 invoke 方法,之后那就肯定会调用 pipeline 的 invoke ,之后一顿 invoke valve 。好,那让我们回想一下之前写过的 context 和 wrapper ,总结一个比较详细的执行过程。
1. Connector 创建 req 和 resp
2. 调用 StandardContext 的 invoke ,调用 xxxPipeline 的 invoke 方法
3. Pipeline 调用了 wrapper 的 invoke 方法
4. Wrapper 调用 valve 的 invoke 方法
5. valve 调用了 servlet 的 allocate (这里在以前的课程中讲过)
6. allocate 方法调用 servlet 的 load 方法 ( 当 servlet 需要加载的时候 )
7. init 方法,之后就是 servlet 处理了。
关于 SingleThreadModel
这个 SingleThreadModel 在 servlet2.4 以上版本就已经移除了,因为这个东西只能给你的 servlet 的 service 保证同一时刻只有一个进程在访问,给人一种假象的安全。而且只是给 service 方法给予同步,这显然是不能完全解决多线程访问的问题的。其实说这个是为了给下面的 StandardWrapper 做铺垫。因为我们都知道 StrandardWrapper 是负责加载它所代表的 servlet 并 allocate 一个对象的实例。之后交给 valve 来调用 servlet 的 service 方法。这个 allocate 在使用 和 不使用 SingleThreadModel 的时候是不同的。好的,我们接下来就说这个 StandardWrapper 。
StandardWrapper
这个之前介绍过了,我们这次主要介绍的是 allocate 方法, 我们看下面这一段源码可以发现,当没有实现 SingleThreadModel 的时候, allocate 总是返回第一次时候产生的 servlet 实例。而如果实现 SingleThreadModel 接口,那么就开始控制分配的数量,当分配的大于 nInstance 时候,就 load 一个 servlet 实例,当然这个 load 实在 maxInstance 控制之内的。
代码如下。
public Servlet allocate() throws ServletException { if (debug >= 1) log("Allocating an instance"); // If we are currently unloading this servlet, throw an exception if (unloading) throw new ServletException (sm.getString("standardWrapper.unloading", getName())); // If not SingleThreadedModel, return the same instance every time if (!singleThreadModel) { // Load and initialize our instance if necessary if (instance == null) { synchronized (this) { if (instance == null) { try { instance = loadServlet(); } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } } } if (!singleThreadModel) { if (debug >= 2) log(" Returning non-STM instance"); countAllocated++; return (instance); } } synchronized (instancePool) { while (countAllocated >= nInstances) { // Allocate a new instance if possible, or else wait if (nInstances < maxInstances) { try { instancePool.push(loadServlet()); nInstances++; } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } else { try { instancePool.wait(); } catch (InterruptedException e) { ; } } } if (debug >= 2) log(" Returning allocated STM instance"); countAllocated++; return (Servlet) instancePool.pop(); } }
Load
这个没什么多说的,以前说过了,我们知道 wrapper 接口有一个 load 方法,其实他和 Allocate 里的一样,都是调用的 LoadServlet 方法。我们来看看它的源码。
先看看这一段。
// 这里是说如果不是第一次访问了,并且不是singleThreadModel //就直接返回 Servlet实例 if (!singleThreadModel && (instance != null)) return instance; if ((actualClass == null) && (jspFile != null)) { Wrapper jspWrapper = (Wrapper) ((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME); if (jspWrapper != null) { actualClass = jspWrapper.getServletClass(); // Merge init parameters String paramNames[] = jspWrapper.findInitParameters(); for (int i = 0; i < paramNames.length; i++) { if (parameters.get(paramNames[i]) == null) { parameters.put (paramNames[i], jspWrapper.findInitParameter(paramNames[i])); } } } }这是Tomcat4 的方法,以后的版本就没有了。都是直接loadservletclass。之后就是Loader了,回忆一下我们当时讲的,是自定义的一个classLoader,需要注意的是,container提供一个特殊的servlet,可以访问container的内部内容,名称以org.apache.catalina.起始。之后就是加载servlet,之后就是权限验证,没啥说的。在之后就是在init()的前后fire事件。之后用instanceof来确定是否是实现了singleThreadModel的,如果是就放入pool(如果pool为空就创建一个新的)中。剩下就是Return了。
ServletConfig
接下来就是 servletConfig ,这个东西我们回忆一下是在哪里看到这个东西的,想想在 servlet 被 load 的时候,就会调用一个 init 方法,而且他的参数是 ServletConfig config ,那么,我们就需要知道 servlet 是怎么拿到 servletConfig 的,我们看源码,会发现 StandardWrapper 他实现了 ServletConfig 接口,所以说他把自己传过去就行了。但是想一个事情,如果这样可以,那么 servlet 的每一个实现就都可以用 wrapper 之内的方法了, wrapper 是 container 的,所以我们要保证安全,那么就像 req,resp 那样,用一个 façade 设计模式就行,这个就是隐藏子系统。
Filter
做过 web 开发,大家应该知道 filter 这个东西,那么 filter 是怎么实现的呢,我们想一下之前学的东西,我们应该能想到 ServletWrapperValve 做了这件事情,因为 filter 是跟 service 方法前后后关系。那么我们就可以知道 valve 的 invoke 方法做了什么:
1. 首先要得到一个 wrapper 的对象, allocate 一个 servlet
2. 来一系列的 Filter ,调用他们的 doFilter 方法,当然还有 service 方法
3. 销毁,这个应该是在 servlet 超时的时候才进行。
剩下就是讲解一下 filter 的 chain 了,这个东西其实是一个 ArrayList ,但是注意 filterChain 是一个对象,其中的 filter 是一个该对象之中的一个 arraylist, 所以我们就知道,这个东西其实就是靠 iterator 来实现一个接一个的调用过滤器。所以,本章就结束了,这章其实没有多少自己写的东西,基本都是对源代码的一种分析,希望大家可以多理解,或者下一个 tomcat 的 sourcecode 版本,之后多用断点 跑一跑就能理解是怎么工作的了。
最后还是冒充一下大神说说读代码的事吧,刚才有人问了问为啥我能读懂一些,刚开始的时候我也看不懂,其实就是你最开始看的时候不要纠结于所有的语句,至少你能看懂这个方法主要是干啥功能的,大概哪几个语句能实现就行了,等你慢慢的看完这个,再看完另一个,你就能知道到底那个语句是干什么的了。