0:前言
		
				
				
				
		
		
				我们知道了tomcat的整体框架了, 也明白了里面都有些什么组件, 以及各个组件是干什么用的了。
		
		
				http://www.csdn.net/Develop/read_article.asp?id=27225
		
		
				我想,接下来我们应该去了解一下 tomcat 是如何处理jsp和servlet请求的。
		
		 
		
				1.  我们以一个具体的例子,来跟踪
				TOMCAT
				,
				
				
				看看它是如何把
				Request
				一层一层地递交给下一个容器,
				
				
				并最后交给
				Wrapper
				来处理的。
				
						
						
				
		
		
				以
				
						http://localhost:8080/web/login.jsp
				
				为例子
				
						
						
				
		
		
				(以下例子,
				
				
				都是以
				tomcat4 
				源码为参考)
		
		
				 
		
		
				这篇心得主要分为3个部分: 前期, 中期, 和末期。
		
		
				 
				前期:讲解了在浏览器里面输入一个URL,是怎么被tomcat抓住的。
		
		
				中期:讲解了被tomcat抓住后,又是怎么在各个容器里面穿梭, 最后到达最后的处理地点。
		
		
				末期:讲解到达最后的处理地点后,又是怎么具体处理的。
				
				
		
		
				2
				、
				
						  
				
				前期 Request的born.
		
		
				
						    在这里我先简单讲一下request这个东西。
		
		
				
						     我们先看着这个URL:
				
						http://localhost:8080/web/login.jsp
				
				
				
				
						 它是动用了8080端口来进行socket通讯的。
		
		
				
						     我们知道, 通过 
		
		
				
						       InputStream in = socket.getInputStream() 和
		
		
				
						       OutputStream out = socket.getOutputStream() 
		
		
				     就可以实现消息的来来往往了。
		
		
				
						     但是如果把Stream给应用层看,显然操作起来不方便。 
		
		
				
						     所以,在tomcat 的Connector里面, socket被封装成了Request和Response这两个对象。
		
		
				
						     我们可以简单地把Request看成管发到服务器来的数据,把Response看成想发出服务器的数据。
		
		
				
						    
						 
						
						
				
		
		
				
						     但是这样又有其他问题了啊? Request这个对象是把socket封装起来了, 但是他提供的又东西太多了。
		
		
				
						     诸如Request.getAuthorization(), Request.getSocket()。  像Authorization这种东西开发人员拿来基本上用不太着,而像socket这种东西,暴露给开发人员又有潜在的危险。 而且啊, 在Servlet Specification里面标准的通信类是ServletRequest和HttpServletRequest,而非这个Request类。 So, So, So. Tomcat必须得捣持捣持Request才行。 最后tomcat选择了使用捣持模式(应该叫适配器模式)来解决这个问题。它把org.apache.catalina.Request 捣持成了 org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又实现了ServletRequest和HttpServletRequest 这两种接口。 这样就提供给开发人员需要且刚刚需要的方法了。
		
		
				
						
						
				 
		
				
						
						
				
				
						    ok, 
				让
				我们在 tomcat的顶层容器 - 
				StandardEngin 
				的invoke()方法这里设置一个断点, 然后访问
		
		
				
						    
				
				
						http://localhost:8080/web/login.jsp
				
				
				
				, 我们来看看在前期都会路过哪些地方:
				
						
						
				
		
		
				
						       
				
				1. 
				run(): 536, java.lang.Thread, Thread.java
				
						
						
				
		
		
				
						    
						   CurrentThread
				
						
						
				
		
		
				
						 
						     2.  run():666, org.apache.tomcat.util.threads.ThreadPool$ControlRunnable, ThreadPool.java
		
		
				
						    
						       
						    ThreadPool
		
		
				
						       3.  runIt():589, org.apache.tomcat.util.net.TcpWorkerThread, PoolTcpEndpoint.java
		
		
				
						  
						    
						    ThreadWorker
		
		
				4.        
				processConnection():  549
		
		
				org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler, Http11Protocol.java
		
		
				
						       
						    
						       http protocol parser
		
		
				
						     
						 5.  Process(): 781, org.apache.coyote.http11.Http11Processor, Http11Processor.java
		
		
				
						 
						     
						    http request processor
		
		
				
						       6. service(): 193, org.apache.coyote.tomcat4.CoyoteAdapter,CoyoteAdapter.java
		
		
				
						  
						    
						    adapter
		
		
				
						       7. invoke(): 995, org.apache.catalina.core.ContainerBase, ContainerBase.java
		
		
				
						   StandardEngin
		
		
				 
		
		
				
						    1. 主线程
		
		
				
						    2. 启动线程池.
		
		
				
						    3. 调出线程池里面空闲的工作线程。
		
		
				
						    4. 把8080端口传过来由httpd协议封装的数据,解析成Request和Response对象。
		
		
				
						    5. 使用
				Http11Processor
				来处理request
		
		
				
						    6. 在
				Http11Processor
				里面, 又会call 
				CoyoteAdapter
				来进行适配处理,把Request适配成实现了ServletRequest和HttpServletRequest接口的
				CoyoteRequest.
		
		
				7
				. 到了这里,前期的去毛拔皮工作就基本上搞定,可以交给
				StandardEngin 
				做核心的处理工作了。
		
		
				 
		
		
				3. 中期。 在各个容器间的穿梭。
		
		
				
						    Request在各个容器里面的穿梭大致是这样一种方式:
		
		
				
						    每个容器里面都有一个管道(pipline), 专门用来传送Request用的。
		
		
				
						    管道里面又有好几个阀门(valve), 专门用来过滤Request用的。
		
		
				
						    在管道的低部通常都会放上一个默认的阀们。 这个阀们至少会做一件事情,就是把Request交给子容器。
		
		
				
						    让我们来想象一下:
		
		
				
						     当一个Request进入一个容器后, 它就在管道里面流动,波罗~ 波罗~ 波罗~ 地穿过各个阀门。在流到最后一个阀门的时候,吧唧~ 那个该死的阀门就把它扔给了子容器。 然后又开始 波罗~ 波罗~ 波罗~ ... 吧唧~....  波罗~  波罗~ 波罗~ ....吧唧~.... 
		
		
				
						    就是通过这种方式, Request 走完了所有的容器。( 感觉有点像消化系统,最后一个地方有点像那里~  )
		
		
				
						    OK, 让我们具体看看都有些什么容器, 各个容器里面又都有些什么阀门,这些阀们都对我们的Request做了些什么吧:
		
		
				
				 
		
				3.1 StandardEngin 
				的pipeline里面放的是:
				StandardEnginValve
				
						
						
				
		
		
				在这里,
				VALVE
				做了三件事:
				
						
						
				
		
		
				1.   
				验证传递过来的
				request
				是不是
				httpservletRequest.
		
		
				2    
				验证传递过来的
				 request 
				是否携带了
				host header
				信息
				.
		
		
				3    
				选择相应的
				host
				去处理它。(一般我们都只有一个host:
				localhost
				,也就是
				127.0.0.1
				)。
		
		
				到了这个地方,
				
				
				我们的
				request
				就已经完成了在
				Engin
				这个部分的历史使命,
				
				
				通向前途未卜的下一站:
				 host
				了。
		
		
				 
		
		
				3.2 StandardHost 
				的pipline里面放的是:
				 StandardHostValve
		
		
				1.   
				验证传递过来的
				request
				是不是
				httpservletRequest.
		
		
				2.   
				根据
				Request
				来确定哪个
				Context
				来处理。
				
						
						
				
		
		
				Context
				其实就是
				webapp
				,
				
				
				比如
				
						http://localhost:8080/web/login.jsp
				
				
				
				
						
						
				
		
		
				这里
				web
				就是
				Context
				罗!
				
						
						
				
		
		
				3.   
				既然确定了是哪个
				Context
				了,那么就应该把那个
				Context
				的
				classloader
				付给当前线程了。
				
						
						
				
		
		
				
						        Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
		
		
				
						   
				
				这样
				request
				就只看得见指定的
				context
				下面的
				classes
				啊,
				 jar
				啊这些,
				
				
				而看不见
				tomcat
				本身的类,
				
				
				什么
				Engin
				啊,
				 Valve
				啊。
				
				
				不然还得了啊!
				
						
						
				
		
		
				4. 既然request到了这里了,看来用户是准备访问web这个web app了,咋们得更新一下这个用户的session不是! Ok , 就由manager更新一下用户的session信息
		
		
				5. 交给具体的Context 容器去继续处理Request.
		
		
				6. Context处理完毕了,把
				classloader
				还回来。
				
						
						
				
		
		
				 
		
		
				3.3 StandardContext 
				的pipline里面放的是:
				 StandardContextValve
		
		
				1.   
				验证传递过来的
				request
				是不是
				httpservletRequest.
		
		
				2.   
				如果
				request
				意图不轨,想要访问
				/meta-inf, /web-inf
				这些目录下的东西,呵呵,没有用D!
				
						
						
				
		
		
				3.   
				这个时候就会根据
				Request
				到底是
				Servlet
				,
				还是
				jsp
				,
				还是静态资源来决定到底用哪种
				Wrapper
				来处理这个
				Reqeust
				了。
		
		
				4.   
				一旦决定了到底用哪种
				Wrapper
				,
				OK
				,交给那个
				Wrapper
				处理。
		
		
				 
		
		
				4. 末期。 不同的需求是怎么处理的.
		
		
				StandardWrapper
		
		
				之前对Wrapper没有做过讲解,其实它是这样一种东西。
		
		
				我们在处理Request的时候,可以分成3种。
		
		
				处理静态的: org.apache.catalina.servlets.DefaultServlet   
		
		
				处理jsp的:org.apache.jasper.servlet.JspServlet 
		
		
				处理servlet的:org.apache.catalina.servlets.InvokerServlet
		
		
				不同的request就用这3种不同的servlet去处理。
		
		
				Wrapper就是对它们的一种简单的封装,有了Wrapper后,我们就可以轻松地拦截每次的Request。也可以容易地调用servlet的init()和destroy()方法, 便于管理嘛!
		
		
				具体情况是这么滴:
		
		
				
						   如果request是找jsp文件,StandardWrapper里面就会封装一个org.apache.jasper.servlet.JspServlet去处理它。
		
		
				
						   如果request是找 静态资源 ,StandardWrapper里面就会封装一个org.apache.jasper.servlet.DefaultServlet  去处理它。
		
		
				
						   如果request是找servlet ,StandardWrapper里面就会封装一个org.apache.jasper.servlet.InvokerServlet 去处理它。
		
		
				 
		
		
				StandardWrapper同样也是容器,既然是容器, 那么里面一定留了一个管道给request去穿,管道低部肯定也有一个阀门(注1),用来做最后一道拦截工作.
		
		
				在这最底部的阀门里,其实就主要做了两件事:
		
		
				
						   一是启动过滤器,让request在N个过滤器里面筛一通,如果OK! 那就PASS。 否则就跳到其他地方去了。
		
		
				
						   二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); 这个方法.
		
		
				
						     如果是 JspServlet, 那么先把jsp文件编译成servlet_xxx, 再invoke servlet_xxx的servie()方法。
		
		
				
						     如果是 DefaultServlet, 就直接找到静态资源,取出内容, 发送出去。
		
		
				
						     如果是 InvokerServlet, 就调用那个具体的servlet的service()方法。
		
		
				
						   ok! 完毕。
		
		
				注1: StandardWrapper 里面的阀门是最后一道关口了。 如果这个阀门欲意把request交给StandardWrapper 的子容器处理。 对不起, 在设计考虑的时候, Wrapper就被考虑成最末的一个容器, 压根儿就不会给Wrapper添加子容器的机会! 如果硬是要调用addChild(), 立马抛出IllegalArgumentException!
		
		
				参考:
		
		
				
						 
						
								    
								
								
						
						
						
				
				<
				
						http://jakarta.apache.org/tomcat/
				
				>
   <
				
						http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html
				
				>
转自:http://dev.csdn.net/article/28/28075.shtm
		
	posted on 2006-05-20 17:09 
船长 阅读(241) 
评论(0)  编辑  收藏