1. 程式人生 > >知識小罐頭06(tomcat8簡單原始碼分析 中)

知識小罐頭06(tomcat8簡單原始碼分析 中)

   更正上一篇一個小錯誤,Connector中首先是將socket請求過來的資訊封裝成一個普通的Request物件(上一篇我寫成HttpRequest物件,失誤失誤,根本就木有HttpRequest這樣的物件。。。),然後在Adapter中封裝成一個HttpServletRequest進行處理,再丟給Container。。。。

  原始碼中可以清晰的看到:

 

 

  ok,這一篇我們就來整個的看看tomcat原始碼,簡單過一遍,看一看裡面是怎麼執行的(我也會刪減大量的非核心的程式碼)

  在看原始碼之前,我想說一點廢話,由於我也是菜鳥,所以大神勿噴啊!

  我自己在看別人分析原始碼部落格的最深的體會就是,別人部落格中分析那一段一段的簡潔而明瞭的原始碼,也許我們看的時候感覺還是比較容易的,有點懂了,差不多了;但是有沒有一種感覺,過不了幾天就印象模糊了,再過幾天就忘的差不多了,下次幾乎又要重新學一遍,賊坑!知道為什麼嗎?因為我們看的只是別人分析後的結果,沒看到別人分析的過程,為什麼別人分析的過程這麼簡潔漂亮而自己分析就是一團糟?為什麼要這麼分析?從哪個方面切入的?假如我自己去分析原始碼能找到切入點嗎?所以很多新人一想到自己要分析原始碼就頭疼,不知道從哪裡入手,腦子很迷糊!

  讓我想起了一句話:誰知盤中餐,粒粒皆辛苦!我們在看著別人花費了幾個小時甚至幾天時間才總結出的原始碼,而我們看起來頂多幾十分鐘,沒有自己親自去辛苦,當然體會不深啊!所以要養成自己分析原始碼的能力和適合自己的方法很重要,就好像學習,學習的知識固然重要,但更重要的是學習的方法,因為知識可能會被淘汰,但是方法卻能伴隨你一生!

  咳,廢話說多了,繼續今天的內容吧!我們先來下載Tomcat原始碼,我下載的版本是7.0.92,下載路徑:https://tomcat.apache.org/download-70.cgi

  注意不需要你原來的Tomcat版本和這個原始碼版本一致,隨便下載一份原始碼就好

 

  下載之後解壓,路徑隨意,但是我放在我的tomcat8目錄裡面

 

1.搭建IDEA匯入Tomcat8原始碼的環境

  我們平常都是執行我們的web專案進行除錯,我們是感受不到Tomcat的存在的,只有報錯的時候才有可能看到tomcat的有關資訊!所以我們要想個辦法把Tomcat變成一個類似我們web專案一樣的尋在,我們不就可以愉快的除錯並且還可以隨意修改其中的內容了嘛!

  首先進入上面下載的那個原始碼資料夾,新建一個catalina-home資料夾和pom.xml檔案

  

  其中pom.xml裡面的內容如下,可以直接進行復制

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>Tomcat8.5</artifactId>
    <name>Tomcat8.5</name>
    <version>8.5</version>

    <build>
        <finalName>Tomcat8.5</finalName>
        <sourceDirectory>java</sourceDirectory>
        <testSourceDirectory>test</testSourceDirectory>
        <resources>
            <resource>
                <directory>java</directory>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <directory>test</directory>
            </testResource>
        </testResources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxrpc</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.5.1</version>
        </dependency>

    </dependencies>
</project>

 

  然後我們開啟我們新建的catalina-home目錄

 

  然後我們進入IDEA,匯入我們的原始碼專案(就是我們下載解壓之後的那個資料夾)

 

  專案匯入之後的目錄應該這樣的

 

  我們要配置一些執行引數(其實就是指定一下我們那幾個複製的和新建的資料夾的路徑)

  Main class(這個類很重要,是Tomcat的啟動類):org.apache.catalina.startup.Bootstrap

  VM options(就是指定一些日誌、工作資料夾等的路徑):-Dcatalina.home=catalina-home -Dcatalina.base=catalina-home -Djava.endorsed.dirs=catalina-home/endorsed -Djava.io.tmpdir=catalina-home/temp -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=catalina-home/conf/logging.properties 

   ok,再點選應用,就可以了,然後你可以右上角執行Tomcat,肯定會報錯,就是一個Test類裡面的什麼Cookie錯誤,沒啥用,你直接把那整個類都註釋掉。(Ctrl+A全選。Ctrl+/註釋);注意,可能會有個很低階的錯誤,就是提示你沒有SDK或者JDK,你只需要在左上角file------>Project Structrue------->Project,在這裡選擇你的JDK版本,沒有這個錯誤更好。

  然後你可以正常啟動,但是在瀏覽器訪問Tomcat的URL路徑:localhost:8080又會報一個500的異常,原因是空指標異常,是一個什麼JasperInitializer沒有被載入,這個需要我們自己該一下原始碼手動讓它載入。

  在IDEA中,Ctrl+N,搜一個ContextConfig的類,在下圖的地方加入   context.addServletContainerInitializer(new JasperInitializer(),null);   這樣就可以正常訪問Tomcat主頁了!

 

  其實到這裡,基本的除錯環境就搭建出來了,有興趣的小夥伴可以Ctrl+N搜一下Bootstrap類,找到main方法,在init,load,start三個方法那裡打斷點進行除錯啊,看看tomcat啟動原理,我後面有時間的話也會一起看一看的!

 

2.簡單從原始碼的角度看一看Tomcat組成

  還記得上一篇說的Tomcat的那些組成部分嗎?這裡還是大概理一下:

  Tomcat最核心的是conf/server.xml這個配置檔案,這個配置檔案中每個標籤都代表一個Tomcat的組成部分,最外面的是一個server標籤,其實可以簡單的把這個server標籤代表我們的Tomcat伺服器,便於理解。一個Tomca例項t只有一個這個server標籤

  次一級的就是service標籤,這個標籤可以配置多個,它是由兩部分組成,connector和container

  其中connector可以配置多個, 分別為處理HTTP協議的和處理AJP協議的(至於還能不能處理其他的協議我也不怎麼清楚,有時間研究一下),以Http的Connector為例,這個Connector裡面就是一個協議處理器(ProtocolHandler),這個協議處理器裡面由三部分組成:Endpoint(底層用socket接收客戶端請求,並呼叫Processor處理),Processor(將使用者的socket請求解析之後,包裝成一個普通的Request物件和Response物件,再呼叫adaptor),Adaptor(就是將普通的Request和Response物件封裝成HttpServletRequest物件,再想辦法丟到Container中)

  Container裡面是一個大容器裡面套著小容器,小容器裡面還有小容器的這樣的一個結構,依次為Engine(一個Service只有一個),Host(可以多個),Context(可以多個),Wrapper(可以多個),當請求到了之後,會有一個管道---閥門機制,讓這個請求從最外面的容器經過一道道閥門到最裡面的容器,最後就到servlet的service方法執行,返回!

 

  接下來,我們就站在程式碼的角度,大概看看這些組成部分用程式碼是什麼樣子的,後面再說整個Tomcat的執行原理:

   首先是Connector,最重要的是一個有參構造:

  這裡不得不說一句,你覺得tomcat是怎麼處理通過HTTP協議或者AJP協議發來的請求的?難道每次都是把這個協議拿過來用正則表示式慢慢的拆開,分析嗎?這也太lower了吧!而且我們關注的不應該是協議本身,而是之後的邏輯,所以Tomcat中就指定了一些處理每個協議的類,假如你用HTTP協議發過來的資訊,Connector就會用反射去例項化HTTP協議處理器對你進行解析,然後我們還可以對協議處理器裡面再進行很多處理,相比之前的用正則表示式慢慢解析,簡直不要太牛!

   

  我們再進去協議處理器看看:

  同時,在Endpoint中有個內部類是Acceptor,用於監聽客戶端請求

 

  我們也來看看Connector裡面的processor是個什麼鬼

 

   進入到process裡面

 

  這個service方法又在這個類裡進行了重寫

 

   最後我們就看看Adapter中的service方法是幹什麼了(貌似就在本篇最前面就截了service的這個圖。。。。)

 

   這一篇就到這裡了,看起來篇幅比較多,其實就是簡單看了看Connector中各個組成部分的原始碼,下一節說說Container中的各個部分吧;最後應該會說一下整個Tomcat的啟動流程!