connectors[i].setContainer(this.container);
}
if(started&&(oldContainer!
=null)&&(oldContainerinstanceofLifecycle)){
try{
((Lifecycle)oldContainer).stop();
}catch(LifecycleExceptione){
;
}
}
support.firePropertyChange("container",oldContainer,this.container);
}
这段代码很简单,其实就是先判断当前的这个Service有没有已经关联了Container,如果已经关联了,那么去掉这个关联关系——oldContainer.setService(null)。
如果这个oldContainer已经被启动了,结束它的生命周期。
然后再替换新的关联、再初始化并开始这个新的Container的生命周期。
最后将这个过程通知感兴趣的事件监听程序。
这里值得注意的地方就是,修改Container时要将新的Container关联到每个Connector,还好Container和Connector没有双向关联,不然这个关联关系将会很难维护。
清单2.StandardService.addConnector
publicvoidaddConnector(Connectorconnector){
synchronized(connectors){
connector.setContainer(this.container);
connector.setService(this);
Connectorresults[]=newConnector[connectors.length+1];
System.arraycopy(connectors,0,results,0,connectors.length);
results[connectors.length]=connector;
connectors=results;
if(initialized){
try{
connector.initialize();
}catch(LifecycleExceptione){
e.printStackTrace(System.err);
}
}
if(started&&(connectorinstanceofLifecycle)){
try{
((Lifecycle)connector).start();
}catch(LifecycleExceptione){
;
}
}
support.firePropertyChange("connector",null,connector);
}
}
上面是addConnector方法,这个方法也很简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。
这里值得一提的是,注意Connector用的是数组而不是List集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:
重新创建一个当前大小的数组对象,然后将原来的数组对象copy到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴。
最新的Tomcat6中StandardService也基本没有变化,但是从Tomcat5开始Service、Server和容器类都继承了MBeanRegistration接口,Mbeans的管理更加合理。
以Server为“居”
前面说一对情侣因为Service而成为一对夫妻,有了能够组成一个家庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。
Server要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个Service集合、同时要维护它所包含的所有Service的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的Service。
还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。
Server的类结构图如下:
图4.Server的类结构图
它的标准实现类StandardServer实现了上面这些方法,同时也实现了Lifecycle、MbeanRegistration两个接口的所有方法,下面主要看一下StandardServer重要的一个方法addService的实现:
清单3.StandardServer.addService
publicvoidaddService(Serviceservice){
service.setServer(this);
synchronized(services){
Serviceresults[]=newService[services.length+1];
System.arraycopy(services,0,results,0,services.length);
results[services.length]=service;
services=results;
if(initialized){
try{
service.initialize();
}catch(LifecycleExceptione){
e.printStackTrace(System.err);
}
}
if(started&&(serviceinstanceofLifecycle)){
try{
((Lifecycle)service).start();
}catch(LifecycleExceptione){
;
}
}
support.firePropertyChange("service",null,service);
}
}
从上面第一句就知道了Service和Server是相互关联的,Server也是和Service管理Connector一样管理它,也是将Service放在一个数组中,后面部分的代码也是管理这个新加进来的Service的生命周期。
Tomcat6中也是没有什么变化的。
组件的生命线“Lifecycle”
前面一直在说Service和Server管理它下面组件的生命周期,那它们是如何管理的呢?
Tomcat中组件的生命周期是通过Lifecycle接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就可以控制Tomcat中所有组件的生命周期,这个最高的组件就是Server,而控制Server的是Startup,也就是您启动和关闭Tomcat。
下面是Lifecycle接口的类结构图:
图5.Lifecycle类结构图
除了控制生命周期的Start和Stop方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。
这个机制在其它的框架中也被使用,如在Spring中。
关于这个设计模式会在后面介绍。
Lifecycle接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的Start方法自然就是调用它下面的组件的Start方法,Stop方法也是一样。
如在Server中Start方法就会调用Service组件的Start方法,Server的Start方法代码如下:
清单4.StandardServer.Start
publicvoidstart()throwsLifecycleException{
if(started){
log.debug(sm.getString("standardServer.start.started"));
return;
}
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,null);
lifecycle.fireLifecycleEvent(START_EVENT,null);
started=true;
synchronized(services){
for(inti=0;iif(services[i]instanceofLifecycle)
((Lifecycle)services[i]).start();
}
}
lifecycle.fireLifecycleEvent(AFTER_START_EVENT,null);
}
监听的代码会包围Service组件的启动过程,就是简单的循环启动所有Service组件的Start方法,但是所有Service必须要实现Lifecycle接口,这样做会更加灵活。
Server的Stop方法代码如下:
清单5.StandardServer.Stop
publicvoidstop()throwsLifecycleException{
if(!
started)
return;
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT,null);
lifecycle.fireLifecycleEvent(STOP_EVENT,null);
started=false;
for(inti=0;iif(services[i]instanceofLifecycle)
((Lifecycle)services[i]).stop();
}
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT,null);
}
它所要做的事情也和Start方法差不多。
回页首
Connector组件
Connector组件是Tomcat中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的tcp连接请求,创建一个Request和Response对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的Request和Response对象传给处理这个请求的线程,处理这个请求的线程就是Container组件要做的事了。
由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:
图6.Connector处理一次请求顺序图
(查看清晰大图)
Tomcat5中默认的Connector是Coyote,这个Connector是可以选择替换的。
Connector最重要的功能就是接收连接请求然后分配线程让Container来处理这个请求,所以这必然是多线程的,多线程的处理是Connector设计的核心。
Tomcat5将这个过程更加细化,它将Connector划分成Connector、Processor、Protocol,另外Coyote也定义自己的Request和Response对象。
下面主要看一下Tomcat中如何处理多线程的连接请求,先看一下Connector的主要类图:
图7.Connector的主要类图
(查看清晰大图)
看一下HttpConnector的Start方法:
清单6.HttpConnector.Start
publicvoidstart()throwsLifecycleException{
if(started)
thrownewLifecycleException
(sm.getString("httpConnector.alreadyStarted"));
threadName="HttpConnector["+port+"]";
lifecycle.fireLifecycleEvent(START_EVENT,null);
started=true;
threadStart();
while(curProcessorsif((maxProcessors>0)&&(curProcessors>=maxProcessors))
break;
HttpProcessorprocessor=newProcessor();
recycle(processor);
}
}
threadStart()执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在HttpProcessor的assign方法中,这个方法是代码如下 :
清单7.HttpProcessor.assign
synchronizedvoidassign(Socketsocket){
while(available){
try{
wait();
}catch(InterruptedExceptione){
}
}
this.socket=socket;
available=true;
notifyAll();
if((debug>=1)&&(socket!
=null))
log("Anincomingrequestisbeingassigned");
}
创建HttpProcessor对象是会把available设为false,所以当请求到来时不会进入while循环,将请求的socket赋给当期处理的socket,并将available设为true,当available设为true是HttpProcessor的run方法将被激活,接下去将会处理这次请求。
Run方法代码如下:
清单8.HttpProcessor.Run
publicvoidrun(){
while(!
stopped){
Socketsocket=await();
if(socket==null)
continue;
try{
process(socket);
}catch(Throwablet){
log("process.invoke",t);
}
connector.recycle(this);
}
synchronized(threadSync){
threadSync.notifyAll();
}
}
解析socket的过程在process方法中,process方法的代码片段如下:
清单9.HttpProcessor.process
privatevoidprocess(Socketsocket){
booleanok=true;
booleanfinishResponse=true;
SocketInputStreaminput=null;
OutputStreamoutput=null;
try{
input=newSocketInputStream(socket.getInputStream(),connector.getBufferSize());
}catch(Exceptione){
log("process.create",e);
ok=false;
}
keepAlive=true;
while(!
stopped&&ok&&keepAlive){
finishResponse=true;
try{
request.setStream(input);
request.setResponse(response);
output=socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse)response.getResponse())
.setHeader("Server",SERVER_INFO);
}catch(Exceptione){
log("process.create",e);
ok=false;
}
try{
if(ok){
parseConnection(socket);
parseRequest(input,output);
if(!
request.getRequest().getProtocol().startsWith("HTTP/0"))
parseHeaders(input);
if(http11){
ackRequest(output);
if(connector.isChunkingAllowed())
response.setAllowChunking(true);
}
}
。
。
。
。
。
。
try{
((HttpServletResponse)response).setHeader
("Date",FastHttpDateFormat.getCurrentDate());
if(ok){
connector.get