分享Tomcat源碼系列三部曲
最近在看Tomcat的源碼,下面用博客記下看源碼的一些心得。
Tomcat是從org.apache.catalina.startup.Bootstrap#main()開始啟動(dòng). 大致分為三個(gè)步驟,即init、load和start。代碼如下:
Java代碼
- public static void main(String args[]) {
 - try {
 - // Attempt to load JMX class
 - new ObjectName("test:foo=bar");
 - } catch (Throwable t) {
 - System.out.println(JMX_ERROR_MESSAGE);
 - try {
 - // Give users some time to read the message before exiting
 - Thread.sleep(5000);
 - } catch (Exception ex) {
 - }
 - return;
 - }
 - if (daemon == null) {
 - daemon = new Bootstrap();
 - try {
 - daemon.init(); ★1
 - } catch (Throwable t) {
 - t.printStackTrace();
 - return;
 - }
 - }
 - try {
 - String command = "start";
 - if (args.length > 0) {
 - command = args[args.length - 1];
 - }
 - if (command.equals("startd")) {
 - args[0] = "start";
 - daemon.load(args);
 - daemon.start();
 - } else if (command.equals("stopd")) {
 - args[0] = "stop";
 - daemon.stop();
 - } else if (command.equals("start")) {
 - daemon.setAwait(true);
 - daemon.load(args); ★2
 - // 反射調(diào)用Catalina的start方法
 - daemon.start(); ★3
 - } else if (command.equals("stop")) {
 - daemon.stopServer(args);
 - }
 - } catch (Throwable t) {
 - t.printStackTrace();
 - }
 - }
 
從以上可以很清楚的看出tomcat是通過參數(shù)的不同進(jìn)行相應(yīng)的命令調(diào)用。
★1 啟動(dòng)、初始化(加載類)
啟動(dòng)之前要進(jìn)行相應(yīng)的init()初始化,進(jìn)行相應(yīng)的環(huán)境設(shè)置以及包的加,以下是init()方法。(org.apache.catalina.startup.Bootstrap.init())
Java代碼
- public void init()
 - throws Exception
 - {
 - setCatalinaHome();//設(shè)置Catalina安裝目錄
 - setCatalinaBase();//設(shè)置Catalina工作目錄
 - initClassLoaders();//加載jar包
 - // 將classload設(shè)置進(jìn)線程,以便我們使用時(shí)進(jìn)行調(diào)用
 - Thread.currentThread().
 - setContextClassLoader(catalinaLoader);
 - SecurityClassLoad.securityClassLoad(catalinaLoader);
 - // 加載啟動(dòng)類和調(diào)用它的process方法
 - if (log.isDebugEnabled())
 - log.debug("Loading startup class");
 - Class startupClass =
 - catalinaLoader.loadClass
 - ("org.apache.catalina.startup.Catalina");
 - Object startupInstance = startupClass.newInstance();
 - // 設(shè)置共享擴(kuò)張類加載器
 - if (log.isDebugEnabled())
 - log.debug("Setting startup class properties");
 - String methodName = "setParentClassLoader";
 - Class paramTypes[] = new Class[1];
 - paramTypes[0] = Class.forName("java.lang.ClassLoader");
 - Object paramValues[] = new Object[1];
 - paramValues[0] = sharedLoader;
 - Method method =
 - startupInstance.getClass().getMethod(methodName,
 - paramTypes);
 - method.invoke(startupInstance, paramValues);
 - catalinaDaemon = startupInstance;
 - }
 
在加載jar的時(shí)候,需要初始化classloader,代碼如下:(org.apache.catalina.startup.Bootstrap)
Java代碼
- private void initClassLoaders() {
 - try {
 - commonLoader = createClassLoader("common", null);
 - catalinaLoader= createClassLoader("server", commonLoader);
 - sharedLoader = createClassLoader("shared", commonLoader);
 - } catch (Throwable t) {
 - log.error("Class loader creation threw exception", t);
 - System.exit(1);
 - }
 - }
 
tomcat中的加載方式是:
|-------commonLoader (common)-> System Loader
|-------sharedLoader (shared)-> commonLoader -> System Loader
|-------catalinaLoader(server) -> commonLoader -> System Loader
Common是公共類加載器,負(fù)責(zé)加載tomcat內(nèi)部和web應(yīng)用程序可以看到的類(%CATALINA_HOME%/bin/common下的jar文件),Catalina負(fù)責(zé)加載的是tomcat內(nèi)部使用的類(%CATALINA_HOME%/server下的jar文件),這些類對(duì)web應(yīng)用程序不可見。Shared負(fù)責(zé)加載的是web應(yīng)用程序之間共享的類(%CATALINA_BASE%/shared下的jar文件),這些類對(duì)于tomcat內(nèi)部是不可見的。如果%CATALINA_HOME%/conf/catalina.Properties中沒有指定Common的搜索路徑,則用當(dāng)前的類的類加載器即系統(tǒng)類加載器作為Common。
★2 裝載相應(yīng)的資源
下面主要講解tomcat的load()方法。下圖是Catalina.load方法的時(shí)序圖。

(1) 從上面的時(shí)序圖可以看出首先調(diào)用Catalina類的load()方法,具體代碼如下:
(org.apache.catalina.startup.Catalina)。
Java代碼
- public void load() {
 - initDirs();
 - // Before digester - it may be needed
 - initNaming();
 - // Create and execute our Digester
 - Digester digester = createStartDigester();
 - try {
 - inputSource.setByteStream(inputStream);
 - digester.push(this);
 - digester.parse(inputSource); //對(duì)server.xml進(jìn)行解析
 - inputStream.close();
 - }
 - ......
 - // Start the new server
 - if (server instanceof Lifecycle) {
 - try {
 - server.initialize(); //server初始化工作
 - } catch (LifecycleException e) {
 - log.error("Catalina.start", e);
 - }
 - }
 - long t2 = System.currentTimeMillis();
 - log.info("Initialization processed in " + (t2 - t1) + " ms");
 - }
 
(2) 在上面的load()方法中需要進(jìn)行server的初始化工作,下圖為Catalina.initialize的時(shí)序圖,從圖中可以看出server初始化所完成的工作。

至此,load方法結(jié)束,初期化的工作結(jié)束,下面開始進(jìn)入start方法。
★3 容器啟動(dòng)
容器啟動(dòng)時(shí),會(huì)調(diào)用Catalina.start(),下圖為它的時(shí)序圖。從圖中可以看出StandardService的start方法被調(diào)用后會(huì)分別對(duì)Container和Connector進(jìn)行start方法的調(diào)用。

1. Bootstrap調(diào)用Catalina的start方法
Catalina.start()方法(org.apache.catalina.startup.Catalina.start())
Java代碼
- public void start() {
 - // 啟動(dòng)server
 - if (server instanceof Lifecycle) {
 - try {
 - ((Lifecycle) server).start();
 - ......
 - }
 
2. Catalina調(diào)用StandardServer的start方法
StandardServer.start() (org.apache.catalina.core.StandardServer.start() )
Java代碼
- public void start() throws LifecycleException {
 - synchronized (services) {
 - for (int i = 0; i < services.length; i++) {
 - if (services[i] instanceof Lifecycle)
 - ((Lifecycle) services[i]).start();
 - }
 - }
 
3. StandardServer調(diào)用StandardService的start方法
Java代碼
- org.apache.catalina.core.StandardService.start() )
 - public void start() throws LifecycleException {
 - if (container != null) {
 - synchronized (container) {
 - if (container instanceof Lifecycle) {
 - // standardEngine的啟動(dòng)
 - ((Lifecycle) container).start();
 - }
 - }
 - //兩個(gè)connector的啟動(dòng),8080和8009
 - synchronized (connectors) {
 - for (int i = 0; i < connectors.length; i++) {
 - if (connectors[i] instanceof Lifecycle)
 - ((Lifecycle) connectors[i]).start();
 - }
 - }
 - }
 
以上StandardService.start()方法主要實(shí)現(xiàn)了兩個(gè)功能,standardEngine的啟動(dòng)和connector的啟動(dòng),下面分別來介紹。
#p#
下面是standardEngine的啟動(dòng)和connector的啟動(dòng)
● standardEngine的啟動(dòng)
(1) 首先是StandardEngine.start()被調(diào)用
Java代碼
- public void start() throws LifecycleException {
 - // Standard container startup
 - //進(jìn)行l(wèi)ogger,manager,cluster,realm,resource的啟動(dòng)
 - super.start();
 - }
 
(2) super.start()--->org.apache.catalina.core.ContainerBase#start()
Java代碼
- public synchronized void start() throws LifecycleException {
 - //(省略) server.xml中配置應(yīng)用組件的啟動(dòng)
 - //StandardHost容器的啟動(dòng),
 - Container children[] = findChildren();
 - for (int i = 0; i < children.length; i++) {
 - if (children[i] instanceof Lifecycle)
 - ((Lifecycle) children[i]).start();
 - }
 - //StandardPipeline的啟動(dòng)(容器與容器間的管道)
 - if (pipeline instanceof Lifecycle)
 - ((Lifecycle) pipeline).start();
 - }
 
(3) StandardHost.start()被調(diào)用
Java代碼
- public synchronized void start() throws LifecycleException {
 - //返回到以上的containerBase#start執(zhí)行pipeline
 - super.start();
 - }
 
(4) StandardPipeline#start
Java代碼
- public synchronized void start() throws LifecycleException {
 - // 將會(huì)調(diào)用HostConfig#start方法
 - lifecycle.fireLifecycleEvent(START_EVENT, null);
 - // Notify our interested LifecycleListeners
 - lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
 - }
 
(5) HostConfig#start
Java代碼
- public void start() {
 - //部暑webapps
 - deployApps();
 - }
 
(6) HostConfig#deployApps
Java代碼
- protected void deployApps() {
 - File appBase = appBase();
 - File configBase = configBase();
 - // Deploy XML descriptors from configBase
 - deployDescriptors(configBase, configBase.list());
 - // Deploy WARs, and loop if additional descriptors are found
 - deployWARs(appBase, appBase.list());
 - // Deploy expanded folders
 - deployDirectories(appBase, appBase.list());
 - }
 
(7) deployWARs
Java代碼
- protected void deployWARs(File appBase, String[] files) {
 - ……
 - deployWAR(contextPath, dir, file);
 - }
 
(8) deployWAR
Java代碼
- protected void deployWAR(String contextPath, File war, String file) {
 - if (context instanceof Lifecycle) {
 - // (省略)
 - Class clazz = Class.forName(host.getConfigClass());
 - LifecycleListener listener =
 - (LifecycleListener) clazz.newInstance();
 - ((Lifecycle) context).addLifecycleListener(listener);
 - }
 - context.setPath(contextPath);
 - context.setDocBase(file);
 - //以下這一步跟進(jìn)去,,StandardContext的啟動(dòng)
 - host.addChild(context);
 - }
 
(9) StandardContext#start
在Context的啟動(dòng)過程中,主要完成了以下任務(wù)。
----------------------------------------------------------------------------------------------------------------------
a) 設(shè)置web app的具體目錄webappResources。
b) postWorkDirectory (),創(chuàng)建臨時(shí)文件目錄。Tomcat下面有一個(gè)work目錄,用來存放臨時(shí)文件。
c) 觸發(fā)START_EVENT事件監(jiān)聽,在這個(gè)事件監(jiān)聽里面會(huì)啟動(dòng)ContextConfig的start()事件,ContextConfig是用來配置web.xml的。
d) 為context創(chuàng)建welcome files,通常是這三個(gè)啟動(dòng)文件:index.html、index.htm、index.jsp
e) 配置filter
f) 啟動(dòng)帶有
g) 注冊(cè)JMX。
----------------------------------------------------------------------------------------------------------------------
至此,Container啟動(dòng)完畢,下面是connector的啟動(dòng)。
● connector的啟動(dòng)
(1) org.apache.catalina.connector.Connector.start()
Java代碼
- public void start() throws LifecycleException {
 - // Http11Protocol的啟動(dòng)
 - protocolHandler.start();
 - }
 
(2) Http11Protocol#start
Java代碼
- public void start() throws Exception {
 - try {
 - //到了終點(diǎn)的啟動(dòng)
 - endpoint.start();
 - } catch (Exception ex) {
 - log.error(sm.getString("http11protocol.endpoint.starterror"), ex);
 - throw ex;
 - }
 
(3) JIoEndPoint#start
Java代碼
- public void start()
 - throws Exception {
 - for (int i = 0; i < acceptorThreadCount; i++) {
 - //這里的acceptor是一個(gè)線程,里面是一個(gè)serversocket的啟動(dòng)
 - Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
 - acceptorThread.setPriority(threadPriority);
 - acceptorThread.setDaemon(daemon);
 - acceptorThread.start();
 - }
 - }
 
(4) Acceptor#run
Java代碼
- public void run() {
 - // Accept the next incoming connection from the server socket
 - try {
 - //這里進(jìn)行了accept(),等待客戶端消息,進(jìn)行接收
 - Socket socket = serverSocketFactory.acceptSocket(serverSocket);
 - serverSocketFactory.initSocket(socket);
 - // Hand this socket off to an appropriate processor
 - if (!processSocket(socket)) {
 - // Close socket right away
 - try {
 - socket.close();
 - } catch (IOException e) {
 - // Ignore
 - }
 - }
 - }catch ( IOException x ) {
 - if ( running ) log.error(sm.getString("endpoint.accept.fail"), x);
 - } catch (Throwable t) {
 - log.error(sm.getString("endpoint.accept.fail"), t);
 - }
 - }
 
至此Connector.start方法調(diào)用完畢。整個(gè)server啟動(dòng)完畢。
#p#
本次講解一下Tomcat請(qǐng)求處理的流程,不當(dāng)之處還請(qǐng)comment。
一. Tomcat 總體結(jié)構(gòu)
Tomcat采用模塊化管理,下面是 Tomcat 的總體結(jié)構(gòu)圖:

從上圖中可以看出 Tomcat 的核心是兩個(gè)組件:Connector 和 Container。下面是一些概念的介紹。
① Server
一個(gè)server代表了整個(gè)catalina servlet容器,在Tomcat里面的Server的用處是啟動(dòng)和監(jiān)聽服務(wù)端事件(諸如重啟、關(guān)閉等命令)。
② Service
Service是由一個(gè)或多個(gè)Connector與一個(gè)Engine的組合。
③ Connector
Connector將在某個(gè)指定的端口上監(jiān)聽客戶的請(qǐng)求,把從socket傳遞過來的數(shù)據(jù),封裝成Request,傳遞給Engine來處理,并從Engine處獲得響應(yīng)并返回給客戶。
Tomcat通常會(huì)用到兩種Connector:
a) Http Connector 在端口8080處偵聽來自客戶browser的http請(qǐng)求。
b) AJP Connector 在端口8009處偵聽來自其它WebServer(Apache)的servlet/jsp代理請(qǐng)求。
二、請(qǐng)求處理過程解析
1. Connector處理請(qǐng)求
Connector處理請(qǐng)求的流程大致如下:

Connector組件啟動(dòng)后,會(huì)偵聽相關(guān)的端口的客戶端請(qǐng)求。
(1) 接受一個(gè)新的連接請(qǐng)求(org.apache.tomcat.util.net.TcpWorkerThread)
Java代碼
- void runIt(Object[] perThrData){
 - Socket s = null;
 - try {
 - s = endpoint.acceptSocket(); //獲取一個(gè)請(qǐng)求
 - } finally {
 - if (endpoint.isRunning()) {
 - endpoint.tp.runIt(this);
 - // 此處啟動(dòng)另一個(gè)TcpWorkerTread去接受其他請(qǐng)求,此線程處理已接受的請(qǐng)求
 - }
 - }
 - TcpConnection con = null;
 - con = (TcpConnection) perThrData[0];
 - con.setEndpoint(endpoint);
 - con.setSocket(s);endpoint.getConnectionHandler().processConnection(con,(Object[]) perThrData[1]);
 - }
 
(2) 新接收的請(qǐng)求被傳到Http11ConnectionHandler中處理。(org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler)
Java代碼
- void processConnection(TcpConnection connection, Object[] thData){
 - Http11Processor processor=null;
 - processor=(Http11Processor)thData[Http11Protocol.THREAD_DATA_PROCESSOR];
 - socket=connection.getSocket();
 - InputStream in = socket.getInputStream();
 - OutputStream out = socket.getOutputStream();
 - processor.setSocket(socket );
 - processor.process(in, out);
 - //processor是org.apache.coyote.http11.Http11Processor 的 一個(gè)實(shí)例
 - }
 
(3) 在 Http11Processor 中處理 http11 協(xié)議相關(guān)的信息(org.apache.coyote.http11.Http11Processor)
Java代碼
- void process(InputStream input, OutputStream output) throws IOException{
 - ~~略~~
 - inputBuffer.setInputStream(input);
 - outputBuffer.setOutputStream(output);
 - inputBuffer.parseHeaders();
 - //http11 協(xié)議頭在此方法中被取出
 - adapter.service(request, response);
 - //adapter 是org.apache.catalina.connector.CoyoteAdapter 的 一個(gè)實(shí)例
 - }
 
接下來的流程交由容器進(jìn)行處理。
2. 容器處理請(qǐng)求
容器交由Pipeline處理,這個(gè)Pipeline里面會(huì)放置一些vavle,請(qǐng)求沿著pipeline傳遞下去并且vavle對(duì)其進(jìn)行相關(guān)的處理。比如說日志等,valve還可以自定義,具體需要查看server.xml配置文件。相關(guān)類圖如下:

Tomcat的主要處理組件Engine、Host、Context和Wrapper的實(shí)現(xiàn)都會(huì)實(shí)現(xiàn)Pipeline接口,實(shí)際對(duì)請(qǐng)求的處理是一個(gè)Adpater,Tomcat中Adapter的實(shí)現(xiàn)是CoyoteAdapter,因此容器請(qǐng)求處理的入口是CoyoteAdapter的service方法。
1. CoyoteAdapter.service
--組裝好請(qǐng)求處理鏈
--StandardEngine. getPipeline().getFirst().invoke(request, response);
--StandardEngineValve.invoke
2. StandardEngineValve.invoke
--Host.getPipeline().getFirst().invoke(request, response);
--StandardHostValve.invoke
3. StandardHostValve.invoke
--Context. getPipeline().getFirst().invoke(request, response);
--StandardContextValve.invoke
4. StandardContextValve.invoke
--ServletRequestListener.requestInitialized
--Wrapper.getPipeline().getFirst().invoke(request, response);
--StandardWrapperValve.invoke
-- ServletRequestListener.requestDestroyed
5. StandardWrapperValve.invoke
--組裝Filter+Servlet
--處理請(qǐng)求
(1) Connector傳來的請(qǐng)求調(diào)用CoyoteAdapter.service()方法。(org.apache.catalina.connector.CoyoteAdapter)
Java代碼
- public void service(org.apache.coyote.Request req,
 - org.apache.coyote.Response res)
 - throws Exception {
 - ~~略~~
 - if (request == null) {
 - request = (Request) connector.createRequest();
 - request.setCoyoteRequest(req);
 - response = (Response) connector.createResponse();
 - response.setCoyoteResponse(res);
 - //創(chuàng)建request、response對(duì)象
 - ~~略~~
 - }
 - try {
 - if (postParseRequest(req, request, res, response)) {
 - connector.getContainer().getPipeline().getFirst().invoke(request, response);
 - //此處的Container是StandardEngine對(duì)象
 - ~~略~~
 - }
 - }
 
(2) 默認(rèn)StandardEngine的Pipeline會(huì)有StandardEngineValve處理單元(參照StandardEngine構(gòu)造函數(shù))。(org.apache.catalina.core.StandardEngineValve)
Java代碼
- public final void invoke(Request request, Response response)
 - throws IOException, ServletException {
 - // Select the Host to be used for this Request
 - Host host = request.getHost();
 - if (host == null) {
 - response.sendError
 - (HttpServletResponse.SC_BAD_REQUEST,
 - sm.getString("standardEngine.noHost",
 - request.getServerName()));
 - return;
 - }
 - // Ask this Host to process this request
 - host.getPipeline().getFirst().invoke(request, response);
 - }
 
(3) 同樣的,StandardHost的Pipeline會(huì)有StandardHostValve處理單元。StandardHostValve如何處理請(qǐng)求跟StandardEngineValve類似,接下來請(qǐng)求進(jìn)入到StandardContextValve.invoke
(4) 同樣的,StandardContext的Pipeline會(huì)有StandardContextValve處理單元。
Java代碼
- public final void invoke(Request request, Response response)
 - throws IOException, ServletException {
 - // Disallow any direct access to resources under WEB-INF or META-INF MessageBytes requestPathMB = request.getRequestPathMB();
 - if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
 - || (requestPathMB.equalsIgnoreCase("/META-INF"))
 - || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
 - || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
 - String requestURI = request.getDecodedRequestURI();
 - notFound(requestURI, response);
 - return;
 - }
 - // Wait if we are reloading
 - while (context.getPaused()) {
 - try {
 - Thread.sleep(1000);
 - } catch (InterruptedException e) {
 - ;
 - }
 - }
 - // Select the Wrapper to be used for this Request
 - Wrapper wrapper = request.getWrapper();
 - if (wrapper == null) {
 - String requestURI = request.getDecodedRequestURI();
 - notFound(requestURI, response);
 - return;
 - }
 - //ServletRequestListener. requestInitialized
 - ~~略~~
 - wrapper.getPipeline().getFirst().invoke(request, response);
 - //ServletRequestListener.requestDestroyed
 - ~~略~~
 - }
 
(5) 同樣的,StandardWrapper這個(gè)Pipeline會(huì)有StandardWrapperValve這個(gè)處理單元。在invoke()方法調(diào)用Filter的同時(shí),servlet.service()方法也將會(huì)被調(diào)用。
(org.apache.catalina.core.StandardWrapperValve)
Java代碼
- void invoke(Request request, Response response, ValveContext valveContext)
 - throws IOException, ServletException{
 - Servlet servlet = null;
 - HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
 - //org.apache.catalina.Request被封裝成javax.servlet.http.HttpServletRequest.
 - HttpServletResponse hres =(HttpServletResponse) response.getResponse();
 - // org.apache.catalina.Response被封裝成javax.servlet.http.HttpServletResponse.
 - servlet = wrapper.allocate(); // 裝載servlet
 - if ((servlet != null) && (filterChain != null)) {
 - filterChain.doFilter(hreq, hres); //調(diào)用此servlet的filterchain
 - }
 
(6) 調(diào)用servlet的filterchain 處理 request和response
(org.apache.catalina.core.ApplicationFilterChain)
Java代碼
- void doFilter(ServletRequest request, ServletResponse response) throws
 - IOException, ServletException{
 - ~~略~~
 - internalDoFilter(request,response);
 - ~~略~~
 - }
 
(7) 調(diào)用internalDoFilter()處理請(qǐng)求。(org.apache.catalina.core.ApplicationFilterChain)
Java代碼
- void internalDoFilter(ServletRequest request, ServletResponse response) throws
 - IOException, ServletException{
 - // 此處省略filter 處理的代碼,filter 被一個(gè)一個(gè)調(diào)用。
 - // 如果http請(qǐng)求的是一個(gè)jsp頁面, 下面的 servlet 會(huì)是 org.apache.jasper.servlet.JspServlet 類的一個(gè)實(shí)例
 - // 若是 html 頁面, 下面的 servlet 會(huì)是 org.apache.catalina.servlets.DefaultServlet 類的一個(gè)實(shí)例
 - if ((request instanceof HttpServletRequest) &&
 - (response instanceof HttpServletResponse)) {
 - servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
 - servlet.service(request, response);
 - } else {
 - servlet.service(request, response);
 - }
 - }
 
至此,servlet.service()方法被調(diào)用。
【編輯推薦】















 
 
 



 
 
 
 