对前面的知识稍微总结以下:
Connector 组件
Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。
HTTP的解析就暂时先告一段落了,后面如果有再遇到新的东西,再拿出来单独详细的讲解。
今天来讲解一下Servlet容器:
Servlet容器是用来处理请求servlet资源,并为Web客户端填充response对象的模块。Servlet容器实org.apache.catalina.Container接口的实例。在Tomcat中,共有四种类型的容器,分别实:Engine,Host,Context和Wrapper。接下来重点先放在Context和Wrapper。
在分析这两个的源码前,先要做一些简单的分析:
1,首先是Container和Connector,我们知道,在连接器Connector处理完请求后,就要调用Servlet容器的invoke()方法。那么很容易想到,这两个类之间存在着关联的关系。也就是说需要将servlet容器的实例作为参数传入到连接器的setContainer()方法中,这样连接器才能调用servlet容器的invoke()方法。例如下面的代码:
HttpConnector connetcor = new HttpConnector();//生成一个连接器实例
SimpleContainer container = new SimpleContainer();//生成一个Servlet容器对象
connector.setContainer(container);//将Servlet容器对象设置到连接器中
2,对于Catalina中的servlet容器,首先需要注意的是。共有4种类型的容器,分别对应不同的概念层次。
*Engine:表示整个Catalina servlet引擎
*Host:表示包含有一个或多个Context容器的虚拟主机
*Context:表示一个Web应用程序。一个Context可以有多个Wrapper;
*Wrapper:表示一个独立的servlet
部署功能性的Catalina并不是必须讲所有4种容器都包括在内。
一个人容器可以有0个或多个低层级的子容器,例如,一般情况下,一个Context实例会有一个或多个Wrapper实例,一个Host实例中会有0个或多个Context实例。但是,Wrapper类型处于层级结构的最底层,因此,它无法再包含子容器了。可以通过调用Container接口的addChild()方法向某容器添加子容器,该方法的如下:
public void addChild(Container child);
调用Container接口的removeChild()方法删除某容器中的一个子容器,remove()方法如下:
public void removeChile(Container child)
此外,Container接口也提供了findChild()方法和findChildren()方法来查找某个子容器和所有子容器的某个集合。这两个方法如下:
public Container findChild(String name);
public Container[] findChildren();
容器可以包含一些支持的组件,如载入器,记录器,管理器,领域和资源。值得注意的是,Container接口提供了getter和setter方法将这些组件与容器相关联。这些方法包括getLoader()和setLoader(),getLogger()和setLogger(),getManager()和setManager(),getRealm()和setRealm()以及getResources()和setResources()。
更有趣的是,Container接口的设计满足以下条件:在部署应用时,Tomcat管理员可以通过编辑配置文件(server.xml)来决定使用哪种容器。这是通过引用容器中的管道(pipeline)和(value)的集合实现的。这些容器里面的方法N多。但是我们现在主要先把精力放在servlet容器的invoke()方法后会发生什么事。然后,在对应小节中讨论org.apache.catalina包中的4个相关接口,Pipeline,Value,ValueContext和Contained。
管道包含该servlet容器将要调用的任务。一个阀表示一个具体的执行任务,在servlet容器的管道中,有一个基础阀,但是,可以添加任意数量的阀。阀的数量指的是额外添加的阀数量,即,不包括基础阀。有意思的是,可以通过编辑Tomcat的配置文件server.xml来动态添加阀。
管道就像过滤链一样,而阀则好似是过滤器。阀与过滤器类似,可以处理传递给它的request对象和response对象。当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行的。
一个servlet容器可以有一个管道。当调用了容器的invoke()方法后,容器会将处理工作交由管道完成,而管道会调用其中的第一个阀开始处理。当第1个阀处理完后,它会调用后续的阀继续执行任务,直到管道中所有的阀都处理完成。下面是在管理的invoke()方法中执行的伪代码:
//invoke each value added to this pipeline
for(int i = 0;n < valves.length;n++){
valve[i].invoke(...);
}
//then,invoke the basic valve
basicValue.invoke(...);
但是,Tomcat的设计者选择了另一个实现方法,通过引用接口org.apache.catalina.ValueContext来实现阀的遍历执行。下面是它的工作原理。
当连接器调用容器的invoke()方法后,容器中要执行的任务并没有硬编码在invoke()方法中。相反,容器会调用其管道的invoke()方法。Pipeline接口的invoke()方法的签名与Container接口的invoke()方法完全相同。如下所示:
public void invoke(Request request,Response response)throws IOException,ServletException;
下面是Container接口在org.apache.catalina.core.ContainerBase类中的invoke()方法的实现:
public void invoke(Request request,Response response)throws IOException,ServletException{
pipeline.invoke(request,response);
}
其中pipeline是该容器中的Pipeline接口的实例
现在,管道必须保证添加到其中的所有阀及其基础阀都被调用一次,这是通过创建一个ValveContext接口实例来实现的。ValveContext是作为管道的一个内部类实现的,因此,ValveContext接口就可以访问管理的所有成员。ValveContext接口最重要的方法是invokeNext():
public void invokeNext(Request request,Response response)throws IOException,servletException;
在创建了ValueContext实例后,管道会调用ValueContext实例的invokeNext()方法,ValueContext实例会首先调用管道中的第1个阀,第1个阀执行完后,会调用后面的阀继续执行。ValveContext实例会将自身传给每个阀,因此,每个阀都可以调用ValueContext实例的invokeNext()方法。下面是Value接口的invoke()方法:
public void invokeNext(Request request,Response response)throws IOException,servletException;
Value接口的invoke()方法的实现类似如下代码:
public void invoke(Request request,Response response),ValveContext valveContext)throws IOException,ServletException{
//pass the request and response on to the next value in our pipeline
valueContext.invokeNext(request,response);
//now perform what this value is supposed to do
..........
}
大概理论性的东西看完,下面直接进入源码吧!终于可以来看看源码的,受不了了,现在先来看看管道类
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina;
import java.io.IOException;
import javax.servlet.ServletException;
/**
* <p>Interface describing a collection of Valves that should be executed
* in sequence when the <code>invoke()</code> method is invoked. It is
* required that a Valve somewhere in the pipeline (usually the last one)
* must process the request and create the corresponding response, rather
* than trying to pass the request on.</p>
*
* <p>There is generally a single Pipeline instance associated with each
* Container. The container's normal request processing functionality is
* generally encapsulated in a container-specific Valve, which should always
* be executed at the end of a pipeline. To facilitate this, the
* <code>setBasic()</code> method is provided to set the Valve instance that
* will always be executed last. Other Valves will be executed in the order
* that they were added, before the basic Valve is executed.</p>
*
* @author Craig R. McClanahan
* @author Peter Donald
* @version $Revision: 466595 $ $Date: 2006-10-21 23:24:41 +0100 (Sat, 21 Oct 2006) $
*/
public interface Pipeline {
// ------------------------------------------------------------- Properties
/**
* <p>Return the Valve instance that has been distinguished as the basic
* Valve for this Pipeline (if any).
*/
//获取基础阀
public Valve getBasic();
/**
* <p>Set the Valve instance that has been distinguished as the basic
* Valve for this Pipeline (if any). Prioer to setting the basic Valve,
* the Valve's <code>setContainer()</code> will be called, if it
* implements <code>Contained</code>, with the owning Container as an
* argument. The method may throw an <code>IllegalArgumentException</code>
* if this Valve chooses not to be associated with this Container, or
* <code>IllegalStateException</code> if it is already associated with
* a different Container.</p>
*
* @param valve Valve to be distinguished as the basic Valve
*/
//设置基础阀
public void setBasic(Valve valve);
// --------------------------------------------------------- Public Methods
/**
* <p>Add a new Valve to the end of the pipeline associated with this
* Container. Prior to adding the Valve, the Valve's
* <code>setContainer()</code> method will be called, if it implements
* <code>Contained</code>, with the owning Container as an argument.
* The method may throw an
* <code>IllegalArgumentException</code> if this Valve chooses not to
* be associated with this Container, or <code>IllegalStateException</code>
* if it is already associated with a different Container.</p>
*
* @param valve Valve to be added
*
* @exception IllegalArgumentException if this Container refused to
* accept the specified Valve
* @exception IllegalArgumentException if the specifie Valve refuses to be
* associated with this Container
* @exception IllegalStateException if the specified Valve is already
* associated with a different Container
*/
//设置阀
public void addValve(Valve valve);
/**
* Return the set of Valves in the pipeline associated with this
* Container, including the basic Valve (if any). If there are no
* such Valves, a zero-length array is returned.
*/
//获取阀
public Valve[] getValves();
/**
* Cause the specified request and response to be processed by the Valves
* associated with this pipeline, until one of these valves causes the
* response to be created and returned. The implementation must ensure
* that multiple simultaneous requests (on different threads) can be
* processed through the same Pipeline without interfering with each
* other's control flow.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception is thrown
*/
//执行invoke方法
public void invoke(Request request, Response response)
throws IOException, ServletException;
/**
* Remove the specified Valve from the pipeline associated with this
* Container, if it is found; otherwise, do nothing. If the Valve is
* found and removed, the Valve's <code>setContainer(null)</code> method
* will be called if it implements <code>Contained</code>.
*
* @param valve Valve to be removed
*/
//移除阀
public void removeValve(Valve valve);
}
前面我们说过,当连接器调用容器的invoke()方法后,容器中要执行的任务并没有硬编码写在invoke()方法中,而是容器会去调用其管道的invoke()方法。StandardPipeline类是实现了Pipline接口。看看StandardPipeline中的invoke()方法
package org.apache.catalina.core.StandardPipeline;
/**
* Cause the specified request and response to be processed by the Valves
* associated with this pipeline, until one of these valves causes the
* response to be created and returned. The implementation must ensure
* that multiple simultaneous requests (on different threads) can be
* processed through the same Pipeline without interfering with each
* other's control flow.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception is thrown
*/
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new StandardPipelineValveContext()).invokeNext(request, response);
}
StandardPipelineValveContext类是StandardPipeline类的一个内部类,一旦调用了StandardPipeline类中的invoke()方法时,它会把工作移交给StandardPipelineValveContext()方法。看看这个内部类的实现:
// ------------------------------- StandardPipelineValveContext Inner Class
protected class StandardPipelineValveContext
implements ValveContext {
// ------------------------------------------------- Instance Variables
protected int stage = 0;
// --------------------------------------------------------- Properties
/**
* Return descriptive information about this ValveContext
* implementation.
*/
public String getInfo() {
return info;
}
// ----------------------------------------------------- Public Methods
/**
* Cause the <code>invoke()</code> method of the next Valve that is
* part of the Pipeline currently being processed (if any) to be
* executed, passing on the specified request and response objects
* plus this <code>ValveContext</code> instance. Exceptions thrown by
* a subsequently executed Valve (or a Filter or Servlet at the
* application level) will be passed on to our caller.
*
* If there are no more Valves to be executed, an appropriate
* ServletException will be thrown by this ValveContext.
*
* @param request The request currently being processed
* @param response The response currently being created
*
* @exception IOException if thrown by a subsequent Valve, Filter, or
* Servlet
* @exception ServletException if thrown by a subsequent Valve, Filter,
* or Servlet
* @exception ServletException if there are no further Valves
* configured in the Pipeline currently being processed
*/
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);//调用管道中的每个阀,这里把内部类实例也传进去,方便调用其它阀
} else if ((subscript == valves.length) && (basic != null)) {
basic.invoke(request, response, this);//调用基础阀
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
}
invokeNext()方法使用变量subscript和stage标明当前正在调用的阀。当第一次调用管道的invoke()方法,subscript的值为0,stage的值为1,因此,第1个阀(数组索引为0)会被调用。管道中的第1个阀接收ValueContext实例,并调用其invokeNext()方法。这时,subscript的值变为1,这样就会调用第2个阀,以后以此类推。
下来来看看几个重要的接口:
1,Value接口:
阀是Value接口的实例,用来处理接收到的请求,该接口有两个方法,invoke()方法和getInfo()方法。getInfo()方法返回阀的实现信息。
public String getInfo();
public void invokeNext(Request request, Response response)
throws IOException, ServletException;
2,ValueContext接口:
这个接口是内部类StandardPipelineValveContex需要实现的接口,具有的方法跟Value接口一样。
//-------------------------------------------------------------- Properties
public String getInfo();
public void invokeNext(Request request, Response response)
throws IOException, ServletException;
3,Contained接口:
阀可以选择是否实现org.apache.catalina.Contained接口。该接口的实现类可以通过接口中的方法至多与一个servlet容器相关联。
//-------------------------------------------------------------- Properties
/**
* Return the <code>Container</code> with which this instance is associated
* (if any); otherwise return <code>null</code>.
*/
public Container getContainer();
/**
* Set the <code>Container</code> with which this instance is associated.
*
* @param container The Container instance with which this instance is to
* be associated, or <code>null</code> to disassociate this instance
* from any Container
*/
public void setContainer(Container container);
4,Wrapper接口:
Wrapper级的servlet容器是一个org.apache.catalina.Wrapper接口的实例,表示一个独立的serlvet定义。Wrapper接口继承自Container接口,又添加了一些额外的方法。Wrapper接口的实现类要负责管理servlet类的servlet生命周期,即,调用servelet的init(),service(),destory()等方法,由于Wrapper已经是最低级的servlet容器了,因此不能再向其中添加子容器了,若是Wrapper的addChild()方法被调用,会抛出IllegalArgumantException异常。
Wrapper接口有两个比较重要的方法load()和allocate()方法。allocate()方法会分配一个已经初始化的servlet实例,而且allocate()方法还要考虑下该servlet类是否实现了javax.servlet.SingleThreadModel接口。load()方法载入并初始化servlet类。
public javax.serlvet.servlet allocate()throws javax.servlet.servletException;
public void load()throws javax.servlet.servletException
这两个方法暂时不是这里的重点,具体到类装载器会仔细讨论它。
那么现在来看看是如何调用过滤连中的阀的。请先看下面自己画的一张类图:
Connector 组件
Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。
请看下面类图:
HTTP的解析就暂时先告一段落了,后面如果有再遇到新的东西,再拿出来单独详细的讲解。
今天来讲解一下Servlet容器:
Servlet容器是用来处理请求servlet资源,并为Web客户端填充response对象的模块。Servlet容器实org.apache.catalina.Container接口的实例。在Tomcat中,共有四种类型的容器,分别实:Engine,Host,Context和Wrapper。接下来重点先放在Context和Wrapper。
在分析这两个的源码前,先要做一些简单的分析:
1,首先是Container和Connector,我们知道,在连接器Connector处理完请求后,就要调用Servlet容器的invoke()方法。那么很容易想到,这两个类之间存在着关联的关系。也就是说需要将servlet容器的实例作为参数传入到连接器的setContainer()方法中,这样连接器才能调用servlet容器的invoke()方法。例如下面的代码:
HttpConnector connetcor = new HttpConnector();//生成一个连接器实例
SimpleContainer container = new SimpleContainer();//生成一个Servlet容器对象
connector.setContainer(container);//将Servlet容器对象设置到连接器中
2,对于Catalina中的servlet容器,首先需要注意的是。共有4种类型的容器,分别对应不同的概念层次。
*Engine:表示整个Catalina servlet引擎
*Host:表示包含有一个或多个Context容器的虚拟主机
*Context:表示一个Web应用程序。一个Context可以有多个Wrapper;
*Wrapper:表示一个独立的servlet
上述的每个概念层级都由org.apache.catalina包内的一个接口表示,这些接口分别是Engine,Host,Context,Wrapper,他们都继承自Container接口。这4个接口的标准实现分别是StandardEngine类,StandardHost类,StandardContext类和StandardWrapper类,他们都在org.apache.catalina.core包内。见以下类图:
部署功能性的Catalina并不是必须讲所有4种容器都包括在内。
一个人容器可以有0个或多个低层级的子容器,例如,一般情况下,一个Context实例会有一个或多个Wrapper实例,一个Host实例中会有0个或多个Context实例。但是,Wrapper类型处于层级结构的最底层,因此,它无法再包含子容器了。可以通过调用Container接口的addChild()方法向某容器添加子容器,该方法的如下:
public void addChild(Container child);
调用Container接口的removeChild()方法删除某容器中的一个子容器,remove()方法如下:
public void removeChile(Container child)
此外,Container接口也提供了findChild()方法和findChildren()方法来查找某个子容器和所有子容器的某个集合。这两个方法如下:
public Container findChild(String name);
public Container[] findChildren();
容器可以包含一些支持的组件,如载入器,记录器,管理器,领域和资源。值得注意的是,Container接口提供了getter和setter方法将这些组件与容器相关联。这些方法包括getLoader()和setLoader(),getLogger()和setLogger(),getManager()和setManager(),getRealm()和setRealm()以及getResources()和setResources()。
更有趣的是,Container接口的设计满足以下条件:在部署应用时,Tomcat管理员可以通过编辑配置文件(server.xml)来决定使用哪种容器。这是通过引用容器中的管道(pipeline)和(value)的集合实现的。这些容器里面的方法N多。但是我们现在主要先把精力放在servlet容器的invoke()方法后会发生什么事。然后,在对应小节中讨论org.apache.catalina包中的4个相关接口,Pipeline,Value,ValueContext和Contained。
管道包含该servlet容器将要调用的任务。一个阀表示一个具体的执行任务,在servlet容器的管道中,有一个基础阀,但是,可以添加任意数量的阀。阀的数量指的是额外添加的阀数量,即,不包括基础阀。有意思的是,可以通过编辑Tomcat的配置文件server.xml来动态添加阀。
管道就像过滤链一样,而阀则好似是过滤器。阀与过滤器类似,可以处理传递给它的request对象和response对象。当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行的。
一个servlet容器可以有一个管道。当调用了容器的invoke()方法后,容器会将处理工作交由管道完成,而管道会调用其中的第一个阀开始处理。当第1个阀处理完后,它会调用后续的阀继续执行任务,直到管道中所有的阀都处理完成。下面是在管理的invoke()方法中执行的伪代码:
//invoke each value added to this pipeline
for(int i = 0;n < valves.length;n++){
valve[i].invoke(...);
}
//then,invoke the basic valve
basicValue.invoke(...);
但是,Tomcat的设计者选择了另一个实现方法,通过引用接口org.apache.catalina.ValueContext来实现阀的遍历执行。下面是它的工作原理。
当连接器调用容器的invoke()方法后,容器中要执行的任务并没有硬编码在invoke()方法中。相反,容器会调用其管道的invoke()方法。Pipeline接口的invoke()方法的签名与Container接口的invoke()方法完全相同。如下所示:
public void invoke(Request request,Response response)throws IOException,ServletException;
下面是Container接口在org.apache.catalina.core.ContainerBase类中的invoke()方法的实现:
public void invoke(Request request,Response response)throws IOException,ServletException{
pipeline.invoke(request,response);
}
其中pipeline是该容器中的Pipeline接口的实例
现在,管道必须保证添加到其中的所有阀及其基础阀都被调用一次,这是通过创建一个ValveContext接口实例来实现的。ValveContext是作为管道的一个内部类实现的,因此,ValveContext接口就可以访问管理的所有成员。ValveContext接口最重要的方法是invokeNext():
public void invokeNext(Request request,Response response)throws IOException,servletException;
在创建了ValueContext实例后,管道会调用ValueContext实例的invokeNext()方法,ValueContext实例会首先调用管道中的第1个阀,第1个阀执行完后,会调用后面的阀继续执行。ValveContext实例会将自身传给每个阀,因此,每个阀都可以调用ValueContext实例的invokeNext()方法。下面是Value接口的invoke()方法:
public void invokeNext(Request request,Response response)throws IOException,servletException;
Value接口的invoke()方法的实现类似如下代码:
public void invoke(Request request,Response response),ValveContext valveContext)throws IOException,ServletException{
//pass the request and response on to the next value in our pipeline
valueContext.invokeNext(request,response);
//now perform what this value is supposed to do
..........
}
大概理论性的东西看完,下面直接进入源码吧!终于可以来看看源码的,受不了了,现在先来看看管道类
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina;
import java.io.IOException;
import javax.servlet.ServletException;
/**
* <p>Interface describing a collection of Valves that should be executed
* in sequence when the <code>invoke()</code> method is invoked. It is
* required that a Valve somewhere in the pipeline (usually the last one)
* must process the request and create the corresponding response, rather
* than trying to pass the request on.</p>
*
* <p>There is generally a single Pipeline instance associated with each
* Container. The container's normal request processing functionality is
* generally encapsulated in a container-specific Valve, which should always
* be executed at the end of a pipeline. To facilitate this, the
* <code>setBasic()</code> method is provided to set the Valve instance that
* will always be executed last. Other Valves will be executed in the order
* that they were added, before the basic Valve is executed.</p>
*
* @author Craig R. McClanahan
* @author Peter Donald
* @version $Revision: 466595 $ $Date: 2006-10-21 23:24:41 +0100 (Sat, 21 Oct 2006) $
*/
public interface Pipeline {
// ------------------------------------------------------------- Properties
/**
* <p>Return the Valve instance that has been distinguished as the basic
* Valve for this Pipeline (if any).
*/
//获取基础阀
public Valve getBasic();
/**
* <p>Set the Valve instance that has been distinguished as the basic
* Valve for this Pipeline (if any). Prioer to setting the basic Valve,
* the Valve's <code>setContainer()</code> will be called, if it
* implements <code>Contained</code>, with the owning Container as an
* argument. The method may throw an <code>IllegalArgumentException</code>
* if this Valve chooses not to be associated with this Container, or
* <code>IllegalStateException</code> if it is already associated with
* a different Container.</p>
*
* @param valve Valve to be distinguished as the basic Valve
*/
//设置基础阀
public void setBasic(Valve valve);
// --------------------------------------------------------- Public Methods
/**
* <p>Add a new Valve to the end of the pipeline associated with this
* Container. Prior to adding the Valve, the Valve's
* <code>setContainer()</code> method will be called, if it implements
* <code>Contained</code>, with the owning Container as an argument.
* The method may throw an
* <code>IllegalArgumentException</code> if this Valve chooses not to
* be associated with this Container, or <code>IllegalStateException</code>
* if it is already associated with a different Container.</p>
*
* @param valve Valve to be added
*
* @exception IllegalArgumentException if this Container refused to
* accept the specified Valve
* @exception IllegalArgumentException if the specifie Valve refuses to be
* associated with this Container
* @exception IllegalStateException if the specified Valve is already
* associated with a different Container
*/
//设置阀
public void addValve(Valve valve);
/**
* Return the set of Valves in the pipeline associated with this
* Container, including the basic Valve (if any). If there are no
* such Valves, a zero-length array is returned.
*/
//获取阀
public Valve[] getValves();
/**
* Cause the specified request and response to be processed by the Valves
* associated with this pipeline, until one of these valves causes the
* response to be created and returned. The implementation must ensure
* that multiple simultaneous requests (on different threads) can be
* processed through the same Pipeline without interfering with each
* other's control flow.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception is thrown
*/
//执行invoke方法
public void invoke(Request request, Response response)
throws IOException, ServletException;
/**
* Remove the specified Valve from the pipeline associated with this
* Container, if it is found; otherwise, do nothing. If the Valve is
* found and removed, the Valve's <code>setContainer(null)</code> method
* will be called if it implements <code>Contained</code>.
*
* @param valve Valve to be removed
*/
//移除阀
public void removeValve(Valve valve);
}
前面我们说过,当连接器调用容器的invoke()方法后,容器中要执行的任务并没有硬编码写在invoke()方法中,而是容器会去调用其管道的invoke()方法。StandardPipeline类是实现了Pipline接口。看看StandardPipeline中的invoke()方法
package org.apache.catalina.core.StandardPipeline;
/**
* Cause the specified request and response to be processed by the Valves
* associated with this pipeline, until one of these valves causes the
* response to be created and returned. The implementation must ensure
* that multiple simultaneous requests (on different threads) can be
* processed through the same Pipeline without interfering with each
* other's control flow.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception is thrown
*/
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new StandardPipelineValveContext()).invokeNext(request, response);
}
StandardPipelineValveContext类是StandardPipeline类的一个内部类,一旦调用了StandardPipeline类中的invoke()方法时,它会把工作移交给StandardPipelineValveContext()方法。看看这个内部类的实现:
// ------------------------------- StandardPipelineValveContext Inner Class
protected class StandardPipelineValveContext
implements ValveContext {
// ------------------------------------------------- Instance Variables
protected int stage = 0;
// --------------------------------------------------------- Properties
/**
* Return descriptive information about this ValveContext
* implementation.
*/
public String getInfo() {
return info;
}
// ----------------------------------------------------- Public Methods
/**
* Cause the <code>invoke()</code> method of the next Valve that is
* part of the Pipeline currently being processed (if any) to be
* executed, passing on the specified request and response objects
* plus this <code>ValveContext</code> instance. Exceptions thrown by
* a subsequently executed Valve (or a Filter or Servlet at the
* application level) will be passed on to our caller.
*
* If there are no more Valves to be executed, an appropriate
* ServletException will be thrown by this ValveContext.
*
* @param request The request currently being processed
* @param response The response currently being created
*
* @exception IOException if thrown by a subsequent Valve, Filter, or
* Servlet
* @exception ServletException if thrown by a subsequent Valve, Filter,
* or Servlet
* @exception ServletException if there are no further Valves
* configured in the Pipeline currently being processed
*/
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);//调用管道中的每个阀,这里把内部类实例也传进去,方便调用其它阀
} else if ((subscript == valves.length) && (basic != null)) {
basic.invoke(request, response, this);//调用基础阀
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
}
invokeNext()方法使用变量subscript和stage标明当前正在调用的阀。当第一次调用管道的invoke()方法,subscript的值为0,stage的值为1,因此,第1个阀(数组索引为0)会被调用。管道中的第1个阀接收ValueContext实例,并调用其invokeNext()方法。这时,subscript的值变为1,这样就会调用第2个阀,以后以此类推。
下来来看看几个重要的接口:
1,Value接口:
阀是Value接口的实例,用来处理接收到的请求,该接口有两个方法,invoke()方法和getInfo()方法。getInfo()方法返回阀的实现信息。
public String getInfo();
public void invokeNext(Request request, Response response)
throws IOException, ServletException;
2,ValueContext接口:
这个接口是内部类StandardPipelineValveContex需要实现的接口,具有的方法跟Value接口一样。
//-------------------------------------------------------------- Properties
public String getInfo();
public void invokeNext(Request request, Response response)
throws IOException, ServletException;
3,Contained接口:
阀可以选择是否实现org.apache.catalina.Contained接口。该接口的实现类可以通过接口中的方法至多与一个servlet容器相关联。
//-------------------------------------------------------------- Properties
/**
* Return the <code>Container</code> with which this instance is associated
* (if any); otherwise return <code>null</code>.
*/
public Container getContainer();
/**
* Set the <code>Container</code> with which this instance is associated.
*
* @param container The Container instance with which this instance is to
* be associated, or <code>null</code> to disassociate this instance
* from any Container
*/
public void setContainer(Container container);
4,Wrapper接口:
Wrapper级的servlet容器是一个org.apache.catalina.Wrapper接口的实例,表示一个独立的serlvet定义。Wrapper接口继承自Container接口,又添加了一些额外的方法。Wrapper接口的实现类要负责管理servlet类的servlet生命周期,即,调用servelet的init(),service(),destory()等方法,由于Wrapper已经是最低级的servlet容器了,因此不能再向其中添加子容器了,若是Wrapper的addChild()方法被调用,会抛出IllegalArgumantException异常。
Wrapper接口有两个比较重要的方法load()和allocate()方法。allocate()方法会分配一个已经初始化的servlet实例,而且allocate()方法还要考虑下该servlet类是否实现了javax.servlet.SingleThreadModel接口。load()方法载入并初始化servlet类。
public javax.serlvet.servlet allocate()throws javax.servlet.servletException;
public void load()throws javax.servlet.servletException
这两个方法暂时不是这里的重点,具体到类装载器会仔细讨论它。
那么现在来看看是如何调用过滤连中的阀的。请先看下面自己画的一张类图:
注意这一节的调度关系,从Container的invoke------Pipeline的invoke---------StandardPipelineValueContext的invokeNext-----------再到各个Vaule的invoke