关于这边blog呢,实际开发中并不会用到,但是我觉得还是很有必要认真的写一下。毕竟我们每天在本地撸码的时候使用的就是tomcat来做web服务器。一个常识就是说我们本地在tomcat里面部署了一个web应用就可以去跑这个应用了,那么这里就有一个很底层的问题,这个web应用是如何在tomcat里面跑起来的呢?我们发了一个http请求,这个请求是如何在到达tomcat上然后又做了些什么才最终返回给我们想要的结果呢?
所以我现在认真的对tomcat的系统架构做一个分析,先交代下大致的情况。
- Tomcat的整体结构
Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。多个 Connector 和一个 Container 就形成了一个 Service, Service也就是服务了,有了 Service 就可以对外提供服务了,但是Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个 Tomcat 的生命周期由Server 控制。
前面的博客里面我也已经讲到说我们平时在操作tomcat的配置文件的时候,一般都是在操作server.xml这个配置文件。下面是这个文件的主要的大致标签,发现没?正好和上面所讲的每一层都一一对应的,这样子也方便以后我们记住了呢。
<Server>
<Service>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Context path="/LinkinPark" docBase="E:\test1" debug="0" privileged="true"></Context>
</Host>
</Engine>
</Service>
</Server>
- 1,Service
Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。下面就来看下在service添加connector和container2个组件关键的代码:
/**
* Set the <code>Container</code> that handles requests for all
* <code>Connectors</code> associated with this Service.
*
* @param container
* The new Container
*
* 这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关联了Container,如果已经关联了,那么去掉这个关联关系——oldContainer.setService(null)。
* 如果这个 oldContainer已经被启动了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个 新的 Container的生命周期。最后将这个过程通知感兴趣的事件监听程序。
* 这里值得注意的地方就是,修改 Container 时要将新的Container 关联到每个 Connector,还好 Container 和 Connector没有双向关联,不然这个关联关系将会很难维护。
*/
public void setContainer(Container container)
{
Container oldContainer = this.container;
if ((oldContainer != null) && (oldContainer instanceof Engine))
((Engine) oldContainer).setService(null);
this.container = container;
if ((this.container != null) && (this.container instanceof Engine))
((Engine) this.container).setService(this);
if (started && (this.container != null) && (this.container instanceof Lifecycle))
{
try
{
((Lifecycle) this.container).start();
}
catch (LifecycleException e)
{
;
}
}
synchronized (connectors)
{
for (int i = 0; i < connectors.length; i++)
connectors[i].setContainer(this.container);
}
if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle))
{
try
{
((Lifecycle) oldContainer).stop();
}
catch (LifecycleException e)
{
;
}
}
// Report this property change to interested listeners
support.firePropertyChange("container", oldContainer, this.container);
}
// --------------------------------------------------------- Public Methods
/**
* Add a new Connector to the set of defined Connectors, and
* associate[əˈsəʊʃieɪt] it with this Service's
* Container.[添加一个新的连接到连接数组里面去,并且要关联服务的容器]
*
* @param connector
* The Connector to be added
* 这个方法比较简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意 Connector
* 用的是数组而不是 List 集合,这个从性能角度考虑可 以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始
* 就分配一个固定大小[0]的数组,我去,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy
* 到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴,其实这也是性能调优的一点。
*/
public void addConnector(Connector connector)
{
synchronized (connectors)
{
connector.setContainer(this.container);
connector.setService(this);
// protected Connector connectors[] = new
// Connector[0];这里是上面的那个连接数组的初始化,长度是0,我去
// 创建一个新的数组,这个数组的要比原来的数组多一个,因为要放新的connector进来呢
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
if (initialized)
{
try
{
connector.initialize();
}
catch (LifecycleException e)
{
log.error(sm.getString("standardService.connector.initFailed", connector), e);
}
}
if (started && (connector instanceof Lifecycle))
{
try
{
((Lifecycle) connector).start();
}
catch (LifecycleException e)
{
log.error(sm.getString("standardService.connector.startFailed", connector), e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
- 2,Server
- 3,组件的生命线“Lifecycle”
现在我们已经知道Service 和 Server 管理它下面组件的生命周期,那它们是如何管理的呢?
除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在 Spring 中。
- 4,Connector
Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。 记住这么一句话就好了:Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求。所以这必然是多线程的,多线程的处理是 Connector 设计的核心。 Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、 Processor、 Protocol, 另外Coyote 也定义自己的 Request 和 Response 对象。完成这个过程有点复杂,我这里大概的说下执行的相关代码:
启动1个连接(HttpConnector.Start)-->进入等待请求的状态,直到一个新的请求到来才会激活它继续执行HttpProcessor.assign)-->处理这次请求(HttpProcessor.Run)-->解析socket(HttpProcessor.process)-->当Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。
- 5,Container
Container (Servlet 容器)是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是: Engine、 Host、 Context、 Wrapper,这四个组件不是平行的,而是父子关系, Engine 包含 Host,Host 包含Context, Context 包含 Wrapper。通常一个 Servlet class 对应一个Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container 了,如 Context,Context 通常就是对应下面这个配置,在前面的文章我也讲到了,说如何通过配置文件来部署一个web应用。
<Context path="/LinkinPark" docBase="E:\test1" debug="0" privileged="true"></Context>
Context 还可以定义在父容器 Host 中, Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。
当Connector接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet处理?
- 6,Engine
- 7,Host
Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
除了所有容器都继承的ContainerBase外,StandardHost 还实现了Deployer接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个web application。Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法, Host可以调用这些方法完成应用的部署等。
- 8,Context
我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置当这个 reloadable 设为 true 时, war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下:
public void backgroundProcess() {
if (!started) return;
count = (count + 1) % managerChecksFrequency;
if ((getManager() != null) && (count == 0)) {
try {
getManager().backgroundProcess();
} catch ( Exception x ) {
log.warn("Unable to perform background process on manager",x);
}
}
if (getLoader() != null) {
if (reloadable && (getLoader().modified())) {
try {
Thread.currentThread().setContextClassLoader
(StandardContext.class.getClassLoader());
reload();
} finally {
if (getLoader() != null) {
Thread.currentThread().setContextClassLoader
}
}
}
if (getLoader() instanceof WebappLoader) {
((WebappLoader) getLoader()).closeJARs(false);
}
}
}
它会调用 reload 方法,而 reload 方法会先调用 stop 方法然后再调用 Start 方法,完成 Context 的一次重新加载。可以看出执行
reload 方法的条件是 reloadable 为 true 和应用被修改,那么这个backgroundProcess 方法是怎么被调用的呢?
这个方法是在 ContainerBase 类中定义的内部类ContainerBackgroundProcessor 被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调用所有容器的 backgroundProcess 方法,因为所有容器都会继承ContainerBase 类,所以所有容器都能够在 backgroundProcess 方法中定义周期执行的事件。
- 9,Wrapper
它基本上描述了对 Servlet 的操作,当装载了Servlet 后就会调用Servlet的init方法,同时会传一个StandardWrapperFacade对象给Servlet,这个对象包装了StandardWrapper, Servlet可以获得的信息都在StandardWrapperFacade封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 可以通过 ServletConfig 拿到有限的容器的信息。当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的filter。
上面这段话说的有点绕,我通俗点讲就是说:wrapper是来管理servlet的,它的实现类在装载一个servlet的时候会调用servlet的一些方法,而且还把自己的一个对象包装成了StandardWrapperFacade这个参数参入给了servlet,所以那么servlet可以拿到配置中的所有的容器的信息了,就这么简单,下面是StandardWrapper.loadServlet核心源码:
try {
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[]{ facade };
SecurityUtil.doAsPrivilege("init",servlet,classType,args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
servlet.init(facade);
}
// Invoke jspInit on JSP pages
if ((loadOnStartup >= 0) && (jspFile != null)) {
// Invoking jspInit
DummyRequest req = new DummyRequest();
req.setServletPath(jspFile);
req.setQueryString(Constants.PRECOMPILE + "=true");
DummyResponse res = new DummyResponse();
if( Globals.IS_SECURITY_ENABLED) {
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args);
args = null;
} else {
servlet.service(req, res);
}
}
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);
} catch (UnavailableException f) {
。。。。。
}
- 10,Tomcat 中其它组件