1.基礎
1.1 web概念
1).軟件架構
1.c/s:客戶端/服務器端
2.b/s:瀏覽器/服務器端
2) .資源分類
1,靜態資源:所有用戶訪問后,得到的結果都是一樣的,稱為靜態資源。靜態資源可以直接被瀏覽
器解析。* 如: html,css, Javascript, jpg
2.動態資源:每個用戶訪問相同資源后,得到的結果可能不一樣,稱為動態盜源。動態瓷源被訪問后,需要先轉換為靜志資源,再返回給瀏覽器,通過瀏覽器進行解析。*如: servlet/jsp, php, asp.....
3).網絡通信三要素
1.IP:電子設備(計算機)在網絡中的唯一標識。
2·端口:應用程序在計算機中的唯一標識。0~65536
3.傳輸協議:規定了數據傳輸的規則.
基礎協議:
Tcp:安全協議,三次握手。速度稍慢
Udp:不安全協議。速度快
1.2常見的web服務器
1.2.1概念
1). 服務器:安裝了服務器軟件的計算機
2). 服務器軟件:接收用戶的請求,處理請求,做出響應
3). web服務器軟件:接收用戶的請求,處理請求,做出響應。
在web服務器軟件中,可以部署web項目,讓用戶通過瀏覽器來訪問這些項目
1.2.2常見web服務器軟件
1) . weblogic:oracle公司,大型的JavaEE服務器,支持所有的JavaEE規范,收費的。
2) .webSphere:IBM公司,大型的JavaEe服務器,支持所有的JavaEE規范,收費的。
3).JBOSS:JBoss公司的,大型的JavaEE服務器,支持所有的JavaEE規范,收費的。
4).Tomcat:Apache基金組織,中小型的JavaEE服務器,僅僅支持少量的JavaE規范servlet/jsp.開源的,免費的。
1.3 Tomcat歷史
1)Tomcat最初由Sun公司的軟件架構師James Duncan Davidson開發,名稱為"JavaMebServer".
2) 1999年,在Davidson的幫助下,該項目于1999年于apache軟件基金會旗下的Jserv項目合并,并發布第一個版本(3.x),即是現在的Tomcat,該版本實現了servlet2.2和JSP 1.1規范。
3) 2001年,Tomcat發布了4.0版本,作為里程碑式的版本,Tomcat完全重新設計了其架構,并實現了servlet 2.3和JSP1.2規范。
目前Tomcat已經更新到9.0.x版本,但是目前企業中的Tomcat服務器,主流版本還是7.x和8.x ,所以本課程是基于8.5版本進行講解
1.4 Tomcat安裝
1.4.1下載
https://tomcat.apache.org/download-80.cgi
1.4.2安裝
將下載的.zip壓縮包,解壓到系統的目(建議是沒有中文不帶空格的目錄)下即可。
1.5 Tomcat目錄結構
Tomcat的主要目錄文件如下:
目錄目錄下文件說明
bin/存放Tomcat的啟動、停止等批處理腳本
?startup.bat
startup.sh
用于在windows和linux下的啟動腳本
?shutdown. bat
shutdown.sh
用于在windows和linux下的停止腳本
conf/用于存放Tomcat的相關配置文件
?catalina用于存儲針對每個虛擬機的context配置
?context. xml用于定義所有web應用均需加載的context配置,如果web應用指定了自己的context.xml該文件將被覆蓋
?catalina.propertiesTomcat的環境變量配置
?catalina.policyTomcat運行的安全策略配置
?logging.propertiesTomcat的日志配置文件,可以通過該文件修改Tomcat 的日志級別及日志路徑等
?server. xmlTomcat服務器的核心配置文件
?tomcat-users.xml定義Tomcat默認的用戶及角色映射信息配置
?web.xmlTomcat中所有應用默認的部署描述文件,主要定義了基礎Servlet和MIME映射
lib/Tomcat服務器的依賴包
logs/Tomcat默認的日志存放目錄
webapps/Tomcat默認的web應用部署目錄
work/web應用Jsp代碼生成和編譯的臨時目錄
1.6 Tomcat 啟動停止
啟動
雙擊bin/startup.bat
停止
雙擊bin/shutdown.bat
訪問
1.7 Tomcat源碼
1.7.1下載
地址:https://tomcat.apache.org/download-80.cgi
E apache-tomcat-8.5.42-src.zip
1.7.2運行
1)解壓zip壓縮包
2)進入解壓目錄,并創建一個目錄,命名為home(可自定義),并將conf,webapps目錄移入home目錄中
3)在當前目錄下創建一個pom.xml文件,引入tomcat的依賴包
4)在idea中導入該工程
5)配置idea的啟動類,配置Mainclass ,并配置VM參數。
-Dfile.encoding=UTF-8
-Dcatalina.home=D:/JetBrains/IntelliJ_IDEA/study/apache-tomcat-8.5.50-src/home
-Dcatalina.base=D:/JetBrains/IntelliJ_IDEA/study/apache-tomcat-8.5.50-src/home
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=
D:/JetBrains/IntelliJ_IDEA/study/apache-tomcat-8.5.50-src/home/conf/logging.properties
6)啟動主方法,運行Tomcat,訪問Tomcat
出現上述異常的原因,是我們直接啟動org.apache.catalina.startup.Bootstrap的時候沒有加載JasperInitializer,從而無法編譯JSP。解決辦法是在tomcat的源碼contextconfig中的configurestart函數中webConfig()函數下面,手動將JSP解析器初始化:
context.addServletContainerInitializer(new JasperInitializer(), null);
7)重啟Tomcat
2. Tomcat架構
2.1 Http工作原理
HTTP協議是瀏覽器與服務器之間的數據傳送協議。作為應用層協議, HTTP是基于TCP/IP協議來傳遞數據的(HTML文件、圖片、查詢結果等) ,HTTP協議不涉及數據包(Packet)傳輸,主要規定了客戶端和服務器之間的通信格式。
從圖上你可以看到,這個過程是:
1)用戶通過瀏覽器進行了一個操作,比如輸入網址并回車,或者是點擊鏈接,接著瀏覽器獲取了這個事件。
2)瀏覽器向服務端發出TCP連接請求。
3)服務程序接受瀏覽器的連接請求,并經過TCP三次握手建立連接.
4)瀏覽器將請求數據打包成一個HTTE協議格式的數據包。
5)瀏覽器將該數據包推入網絡,數據包經過網絡傳輸,最終達到端服務程序。
6)服務端程序拿到這個數據包后,同樣以HTTP協議格式解包,獲取到客戶端的意圖。
7)得知客戶端意圖后進行處理,比如提供靜態文件或者調用服務端程序獲得動態結果。
8)服務器將響應結果(可能是HTML或者圖片等)按照HTTP協議格式打包。
9)服務器將響應數據包推入網絡,數據包經過網絡傳輸最終達到到瀏覽器。
10)瀏覽器拿到數據包后,以HTTP協議的格式解包,然后解析數據,假設這里的數據是HTML。
11)瀏覽器將HTML文件展示在頁面上。
那我們想要探究的Tomcat和Jetty作為一個HTTP服務器,在這個過程中都做了些什么事情呢?主要是接受連接、解析請求數據、處理請求和發送響應這幾個步驟。
2.2 Tomcat整體架構
2.2.1 Http服務器請求處理
瀏覽器發給服務端的是一個HTTP格式的請求,HTTP服務器收到這個請求后,需要調用服務端程序來處理,所謂的服務端程序就是你寫的Java類,一般來說不同的請求需要由不同的Java類來處理。
1)圖1 ,表示HTTP服務器直接調用具體業務類,它們是緊耦合的。
2)圖2 , HTTP服務器不直接調用業務類,而是把請求交給容器來處理,容器通過servlet接口調用業務類。因此servlet接口和Servlet容器的出現,達到了HTTP服務器與業務類解耦的目的。而servlet接口和servlet容器這一整套規范叫作Servlet規范。Tomcat按照servlet規范的要求實現了servlet容器,同時它們也具有HTTP服務器的功能。作為Java程序員,如果我們要實現新的業務功能,只需要實現一個Servlet ,并把它注冊到Tomcat(Servlet容器)中,剩下的事情就由Tomcat幫我們處理了。
2.2.2 Servlet容器工作流程
為了解耦,HTTP服務器不直接調用servlet ,而是把請求交給servlet容器來處理,那Servlet容器又是怎么工作的呢?
當客戶請求某個資源時, HTTP服務器會用一個ServletRequest對象把客戶的請求信息封裝起來,然后調用servlet容器的service方法,Servlet容器拿到請求后,根據請求的URL和servlet的映射關系,找到相應的servlet,如果Servlet還沒有被加載,就用反射機制創建這個servlet,并調用servlet的init方法來完成初始化,接著調用servlet的service方法來處理請求,把servletResponse對象返回給HTTP服務器,HTTP服務器會把響應發送給客戶端。
2.2.3 Tomcat整體架構
我們知道如果要設計一個系統,首先是要了解需求,我們已經了解了Tomcat要實現兩個核心功能:
1)處理socket連接,負責網絡字節流與Request和Response對象的轉化。
2)加載和管理Servlet,以及具體處理Request請求。
因此Tomcat設計了兩個核心組件連接器(connector)和容器(container)來分別做這兩件事情。連接器負責對外交流,容器負責內部處理。
2.3連接器-Coyote
2.3.1架構介紹
coyote是Tomcat的連接器框架的名稱,是Tomcat服務器提供的供客戶端訪問的外部接口。客戶端通過coyote與服務器建立連接、發送請求并接受響應
coyote封裝了底層的網絡通信(Socket請求及響應處理) ,為catalina容器提供了統一的接口,使catalina容器與具體的請求協議及IO操作方式完全解耦。coyote將socket輸入轉換封裝為Request對象,交由Catalina容器進行處理,處理請求完成后,Catalina通過coyote提供的Response對象將結果寫入輸出流。
coyote作為獨立的模塊,只負責具體協議和IO的相關操作, 與Servlet規范實現沒有直接關系,因此即便是Request和Response對象也并未實現servlet規范對應的接口,而是在catalina中將他們進一步封裝為ServletRequest和ServletResponse
2.3.2 IO模型與協議
在Coyote中,Tomcat支持的多種I/O模型和應用層協議,具體包含哪些IO模型和應用層協議,請看下表:
Tomcat支持的IO模型(自8.5/9.0版本起,Tomcat移除了對BIO的支持) :
Tomcat支持的應用層協議
協議分層
在8.0之前,Tomcat默認采用的I/O方式為BIO ,之后改為NIO,無論NIO,NIO2還是APR,在性能方面均優于以往的BIO, 如果采用APR,甚至可以達到Apache HTTP server的影響性能。
Toncat為了實現支持多種I/O模型和應用層協議,一個容器可能對接多個連接器,就好比一個房間有多個門。但是單獨的連接器或者容器都不能對外提供服務,需要把它們組裝起來才能工作,組裝后這個整體叫作service組件。這里請你注意, Service本身沒有做什么重要的事情,只是在連接器和容器外面多包了一層,把它們組裝在一起。Tomcat內可能有多個service ,這樣的設計也是出于靈活性的考慮。通過在Tomceat中配置多個service,可以實現通過不同的端口號來訪問同一臺機器上部署的不同應用。
2.3.3連接器組件
連接器中的各個組件的作用如下:
EndPoint
1)EndPoint:coyote通信端點,即通信監聽的接口,是具體socket接收和發送處理器,是對傳輸層的抽象,因此EndPoint用來實現TCP/IP協議的。
2)Tomcat并沒有EndPoint接口,而是提供了一個抽象類AbstractEndpoint ,里面定義了兩個內部類:Acceptor?/?k'sept?/?和SocketProcessor,Acceptor用于監聽socket連接請求。socketProcessor用于處理接收到的socket請求,它實現Runnable接口,在Run方法里調用協議處理組件processor進行處理。為了提高處理能力,socketProcessor被提交到線程池來執行。而這個線程池叫作執行器(Executor),我在后面的專欄會詳細介紹Tomcat如何擴展原生的Java線程池,
Processor
Processor:Coyote協議處理接口,如果說EndPoint是用來實現TCP/IP協議的,那么Processor用來實現HTTP協議,Processor接收來自EndPoint的socket,讀取字節流解析成Tomcat Request和Response對象,并通過Adapter將其提交到容器處理,Processor是對應用層協議的抽象。
ProtocolHandler
ProtocolHandler:coyote協議接口, 通過Endpoint和Processor ,實現針對具體協議的處理能力。Tomca按照協議和I/O提供了6個實現類: AjpNioProtocol , AjpAprProtocol,
AjpNio2Protocol , Http11NioProtocol,Http11Nio2Protocol
,Httpl1AprProtocol,我們在配置tomcat/conf/server.xml時,至少要指定具體的ProtocolHandler ,當然也可以指定協議名稱,如:?HTTP/1.1 ,如果安裝了APR,那么將使用Http11AprProtocol ,否則使用Http11NioProtocol
Adapter
由于協議不同,客戶端發過來的請求信息也不盡相同,Tomcat定義了自己的Request類來"存放"這些請求信息。ProtocolHandler接口負責解析請求并生成Tomcat Request類。但是這個Request對象不是標準的servletRequest,也就意味著,不能用Tomcat Request作為參數來調用容器。Tomcat設計者的解決方案是引入coyoteAdapter,這是適配器模式的經典運用,連接器調用coyoteAdapter的Sevice方法,傳入的是Tomcat
Request對象,coyoteAdapter負責將Tomcat Request轉成ServletRequest ,再調用容器的Service方法。
2.4容器-catalina
Tomcat是一個由一系列可配置的組件構成的web容器,而Catalina是Tomcat的servlet容器。
Catalina是servlet容器實現,包含了之前講到的所有的容器組件,以及后續章節涉及到的安全、會話、集群、管理等servlet容器架構的各個方面。它通過松耦合的方式集成Coyote ,以完成按照請求協議進行數據讀寫。同時,它還包括我們的啟動入口、Shell程序等。
2.4.1 Catalina 地位
Tomcat的模塊分層結構圖,如下:
Tomcat本質上就是一款Servlet容器, 因此Catalina才是Tomcat的核心,其他模塊都是為Catalina提供支撐的。比如:通過Coyote模塊提供鏈接通信,Jasper/'d??sp?/?模塊提供JSP引擎,Naming提供JNDI服務,Juli提供日志服務。
2.4.2 catalina結構
Catalina的主要組件結構如下:
Catalina
如上圖所示,Catalina負責管理Server ,而Server表示著整個服務器。Server下面有多個服務Service ,每個服務都包含著多個連接器組件Connector(Coyote實現)和一個容器組件Container/k?n?te?n?(r)/?,在Tomcat啟動的時候,會初始化一個Catalina的實例。
Catalina各個組件的職責:
2.4.3 Container結構
Tomcat設計了4種容器,分別是Engine, Host,Context和Wrapper,這4種容器不是平行關系,而是父子關系。Tomcat通過一種分層的架構,使得Servlet容器具有很好的靈活性。
各個組件的含義:
我們也可以再通過Tomcat的server.xml配置文件來加深對Tomcat容器的理解。Tomcat采用了組件化的設計,它的構成組件都是可配置的,其中最外層的是Server,其他組件按照一定的格式要求配置在這個頂層容器中。
那么,Tomcat是怎么管理這些容器的呢?你會發現這些容器具有父子關系,形成一個樹形結構,你可能馬上就想到了設計模式中的組合模式。沒錯,Tomcat就是用組合模式來管理這些容器的。具體實現方法是,所有容器組件都實現了Container接口,因此組合模式可以使得用戶對單容器對象和組合容器對象的使用具有一致性。這里單容器對象指的是最底層的wrapper ,組合容器對象指的是上面的context、Host或者Engine。
Container接口中提供了以下方法(截圖中只是一部分方法)
在上面的接口看到了getParent,setParent,addChild和removeChild等方法。
Container接口擴展了Lifecycle接口,Lifecycle接口用來統一管理各組件的生命周期。
2.5 Tomcat啟動流程
2.5.1 流程
步驟:
1)啟動Tomcat ,需要調用bin/startup.bat (在linux目錄下,需要調用bin/startup.sh) ,在startup.bat腳本中,調用了catalina.bat.
2)在catalina.bat腳本文件中,調用了Bootstrap中的main方法。
3)在Bootstrap的main方法中調用了init方法,來創建Catalina及初始化類加載器。
4)在Bootstrap的main方法中調用了load方法,在其中又調用了Catalina的load方法。
5)在Catalina的load方法中,需要進行一些初始化的工作,并需要構造Digester對象,用于解析XML.
6)然后在調用后續組件的初始化操作。。。加載Tomcat的配置文件,初始化容器組件,監聽對應的端口號,準備接受客戶端請求。
2.5.2源碼解析
2.5.2.1 Lifecycle
由于所有的組件均存在初始化、啟動、停止等生命周期方法,擁有生命周期管理的特性, 所以Tomcat在設計的時候,基于生命周期管理抽象成了一個接口Lifecycle ,而組件Server, Service, Container, Executor, Connector組件,都實現了一個生命周期的接口,從而具有了以下生命周期中的核心方法:
1) init () :初始化組件
2) start () :啟動組件
3) stop () :停止組件
4)destroy():銷毀組件
2.5.2.2各組件的默認實現
上面我們提到的Server, Service,
Engine, Host. Context都是接口, 下圖中羅列了這些接口的默認實現類。當前對于Endpoint組件來說,在Tomcat中沒有對應的Endpoint接口,但是有一個抽象類AbstractEndpoint ,其下有三個實現類:NioEndpoint.Nio2Endpoint,AprEndpoint,這三個實現類,分別對應于前面講解鏈接器Coyote時,提到的鏈接器支持的三種10模型:NIO, NIO2 , APR , Tomcat8.5版本中,默認采用的是NioEndpoint。
ProtocolHandler:Coyote協議接口,通過封裝Endpoint和Processor ,實現針對具體協議的處理功能。Tomcat按照協議和IO提供了6個實現類。
AJP協議
1) AjpNioProtocol :采用NIO的IO模型。
2) jpNio2Protocol:NIO2的IO模型。
3) AjpAprProtocol :采用APR的IO模型,需要依賴于APR庫。
HTTP協議:
1) Http11NioProtocol :采用NIO的IO模型,默認使用的協議(如果服務器沒有安裝APR)
2) Httpl1Nio2Protocol :采用NIO2的IO模型。
3) Http11aprErotocol :采用APR的IO模型,需要依賴于APR庫
2.5.2.3源碼入口
目錄: org.apache.catalina.startup
MainClass:BootStrap---->main(String[]args)
2.5.3 總結
從啟動流程圖中以及源碼中,我們可以看出Tomcat的啟動過程非常標準化,統一按照生命周期管理接口Lifecycle/la?f'sa?k?l/?的定義進行啟動。首先·調用init()方法進行組件的逐級初始化操作,然后再調用start ()方法進行啟動。
每一級的組件除了完成自身的處理外,還要負責調用子組件響應的生命周期管理方法, 組件與組件之間是松耦合的,因為我們可以很容易的通過配置文件進行修改和替換。
2.6 Tomcat請求處理流程
2.6.1請求流程
設計了這么多層次的容器,Tomcat是怎么確定每一個請求應該由哪個Wrapper容器里的Servlet來處理的呢?答案是,Tomcat是用Mapper組件來完成這個任務的。.
Mapper組件的功能就是將用戶請求的URL定位到一個Servlet ,它的工作原理是:Mapper組件里保存了Web應用的配置信息,其實就是容器組件與訪問路徑的映射關系,比如Host容器里配置的域名、Context容器里的Web應用路徑,以及Wrapper容器里Servlet映射的路徑,你可以想象這些配置信息就是一個多層次的Map。
當一個請求到來時,Mapper組件通過解析請求URL里的域名和路徑,再到自己保存的Map里去查找,就能定位到一個Servlet,請你注意,一個請求URL最后只會定位到一個Wrapper容器,也就是一個Servlet。
下面的示意圖中, 就描述了當用戶請求鏈接http://www.itStudy.cn/bbs/findAll 之后,是如何找到最終處理業務邏輯的Servlet
那上面這幅圖只是描述了根據請求的URL如何查找到需要執行的Servlet ,那么下面我們再來解析一下,從Tomcat的設計架構層面來分析Tomcat的請求處理
步驟如下:
1)Connector組件Endpoint中的Acceptor監聽客戶端套接字連接并接收Socket。
2) 將連接交給線程池Executor處理,開始執行請求響應任務。
3)Processor組件讀取消息報文,解析請求行、請求體、請求頭,封裝成Request對象。
4)Mapper組件根據請求行的URL值和請求頭的Host值匹配由哪個Host容器、Context容器、Wrapper容器處理請求。
5)Coyoteadaptor/?'d?pt?/?組件負責將Connector組件和Engine容器關聯起來,把生成的Request對象和響應對象Response傳遞到Engine容器中,調用Pipeline/?pa?pla?n/?。
6)Engine容器的管道開始處理,管道中包含若干個Valve、每個Valve負責部分處理邏輯。執行完Valve后會執行基礎的Valve--standardEnginevalve,負責調用Host容器的Pipeline。
7)Host容器的管道開始處理,流程類似,最后執行Context容器的Pipeline.
8)Context容器的管道開始處理,流程類似,最后執行Wrapper容器的Pipeline.
9) Wrapper容器的管道開始處理,流程類似,最后執行Wrapper容器對應的Servlet對象的處理方法。
2.6.2 請求流程源碼分析
在前面所講解的Tomcat的整體架構中,我們發現Tomcat中的各個組件各司其職,組件之間松耦合,確保了整體架構的可伸縮性和可拓展性,那么在組件內部,如何增強組件的靈活性和拓展性呢?在Toncat中,每個Container組件采用責任鏈模式(Pipelien即是責任鏈模式)來完成具體的請求處理。
在Tomcat中定義了Pipeline和Valve兩個接口,Pipeline用于構建責任鏈,Valve代表責任鏈上的每個處理器。Pipeline中維護了一個基礎的Valve,它始終位于Pipeline的末端(最后執行) ,封裝了具體的請求處理和輸出響應的過程。當然,我們也可以調用addValve ()方法,為Pipeline添加其他的Valve , 后添加的Valve位于基礎的Valve之前,并按照添加順序執行。Pipiline通過獲得首個Valve來啟動整合鏈條的執行。
3.Jasper
3.1 Jasper簡介
對于基于JSP的web應用來說,我們可以直接在JSP頁面中編寫Java代碼,添加第三方的標簽庫,以及使用EL表達式。但是無論經過何種形式的處理,最終輸出到客戶端的都是標準的HTML頁面(包含js , css..) ,并不包含任何的Java相關的語法。也就是說,我們可以把Jsp看做是一種運行在服務端的腳本。那么服務器是如何將JSP頁面轉換為HTML頁面的呢?
Jasper模塊是Tomcat的JSP核心引擎,我們知道JSP本質上是一個Servlet,Tomcat使用Jasper對Jsp語法進行解析,生成Servlet并生成Class字節碼,用戶在進行訪問Jsp時,會訪問Servlet,最終將訪問的結果直接響應在瀏覽器端。另外,在運行的時候, Jasper還會檢測JSP文件是否修改,如果修改,則會重新編譯JSP文件。
3.2 JSP編譯方式
3.2.1運行時編譯
Tomcat并不會在啟動web應用的時候自動編譯JSP文件,而是在客戶端第一次請求時,才編需要訪問的JSP文件。
創建一個web項目,并編寫JSP代碼:
如果這行代碼報錯uri="http://java.sun.com/jsp/jst1/core"
則:在WEB-INF/lib包下需要導入jstl.jar、standard.jar兩個jar包
3.2.1.1編譯過程
Tomcat在默認的web.xml 中配置了一個org.apache.jasper.servlet.JspServlet,用于處理所有的.jsp或.jspx結尾的請求,該Servlet實現即是運行時編譯的入口。
JspServlte處理流程圖
3.2.1.2編譯結果
1)如果在tomcat/conf/web.xml中配置了參數scratchdir,則jsp編譯后的結果,就會存儲在該目錄下。
2)如果沒有配置該選項,則會將編譯后的結果,存儲在Tomcat安裝目錄下的work/Catalina(Engine名稱)/localhost (Host名稱) /Context名稱。假設項目名稱為jsp_demo-01 ,默認的目錄為: work/Catalina/localhost/jsp-demo_01.
3)如果使用的是IDEA開發工具集成Toncat訪問web工程中的jsp ,編譯后的結果,存放在:
C:\Users\Alienware\.IntelliJIdea2018.3\systemltomcat\project_tomcat\work\catalina\localhost\jsp_demo_01wa
r_exploded\org\apache\jsp
3.2.2 預編譯
除了運行時編譯,我們還可以直接在web應用啟動時,一次性將web應用中的所有的JSP頁面一次性編譯完成。在這種情況下,web應用運行過程中,便可以不必再進行實時編譯,而是直接調用JSP頁面對應的Servlet完成請求處理,從而提升系統性能。
Tomcat提供了一個Shell程序Jspc ,用于支持Jsp預編譯,而且在Tomcat的安裝目錄下提供了一個catalina-tasks.xml文件聲明了
Tomcat支持的Ant任務, 因此,我們很容易使用Ant來執行JSP預編譯。(要想使用這種方式,必須得確保在此之前已經下載并安裝了Apache Ant).
3.3 JSP編譯原理
由編譯后的源碼解讀,可以分析出以下幾點
1)其類名為index_jsp ,繼承自org.apache.jasper.runtime.HttpJspBase,該類是Httpservlet的子類,所以jsp本質就是一個Servlet。
2)通過屬性_jspx_dependants保存了當前JSP頁面依賴的資源,包含引入的外部的JSP頁面、導入的標簽、標簽所在的jar包等,便于后續處理過程中使用(如重新編譯檢測,因此它以Map形式保存了每個資源的上次修改時間)。
3)通過屬性_jspx_imports_packages存放導入的java包,默認導入javax.servlet,
javax.servlet.http,javax.servlet.jsp。
4)通過屬性_jspx_impors_classes存放導入的類,通過import指令導入的DateFormat、SimpleDateFormat、Date都會包含在該集合中。_jspx_imports_packages和_jspx_imports_classes 屬性主要用于配置EL引擎上下文。
5)請求處理由方法_jspService完成,而在父類HttpJspBase中的service方法通過模板方法模式,調用了子類的_jspService方法。
6)_jspService方法中定義了幾個重要的局部變量: pageContext、Session,
application, config, out, page,由于整個頁面的輸出有_JspService方法完成,因此這些變量和參數會對整個JSP頁面生效。這也是我們為什么可以在JSP頁面使用這些變量的原因。
7)指定文檔類型的指令(page)最終轉換為response.setContentType()方法調用。
8)對于每一行的靜態內容(HTML)
,調用out.write輸出。
9)對于<%...%>中的java代碼,將直接轉換為Servlet類中的代碼。如果在Java代碼中嵌入了靜態文件,則同樣調用 out.write輸出。
3.3.2 編譯流程
JSP編譯過程如下
Compiler編工作主要包含代碼生成和編譯兩部分
代碼生成
1)Compiler通過一個PageInfo對象保存JSP頁面編譯過程中的各種配置,這些配置可能來源于web應用初始化參數,也可能來源于JSP頁面的指令配置(如page , include ).
2)調用ParserController解析指令節點,驗證其是否合法,同時將配置信息保存到PageInfo中,用于控制代碼生成。
3)調用ParserController解析整個頁面, 由于JSP 是逐行解析,所以對于每一行會創建一個具體的Node對象。如靜態文本 (Templaterext )、Java代碼( scriptlet)、定制標簽( Custonag )、Include指令( IncludeDirective ) .
4)驗證除指令外其他所有節點的合法性,如腳本、定制標簽、EL表達式等
5)收集除指令外其他節點的頁面配置信息。
6)編譯并加載當前JSP頁面依賴的標簽
7)對于JSP頁面的EL表達式,生成對應的映射函數。
8)生成JSP頁面對應的Servlet類源代碼
編譯
代碼生成完成后, Compiler還會生成SMAP信息。如果配置生成SMAP信息,Compiler則會在編譯階段將SMAP信息寫到class文件中.
在編譯階段,Compiler的兩個實現AntCompiler和JDTCompiler分別調用先關框架的API進行源代碼編譯。
對于AntCompiler來說,構造一個Ant的javac的任務完成編譯。
對干JDTCompiler來說調用org.eclinse.idt.internal.comniler.Comniler完成編譯。