《深入剖析Tomcat》一2.2 应用程序 1
2.2 應用程序 1
下面從servlet容器的角度審視servlet程序的開發。簡單來說,對一個Servlet的每個HTTP請求,一個功能齊全的servlet容器有以下幾件事要做:
當第一次調用某個servlet時,要載入該servlet類,并調用其init()方法(僅此一次);
針對每個request請求,創建一個javax.servlet.ServletRequest實例和一個javax.servlet.ServletResponse實例;
調用該servlet的service()方法,將servletRequest對象和servletResponse對象作為參數傳入;
當關閉該servlet類時,調用其destroy()方法,并卸載該servlet類。
本章所要建立的servlet容器是一個很小的容器,沒有實現所有的功能。因此,它只能運行非常簡單的servlet,而且也會不調用servlet的init()和destroy()方法。它會做以下幾件事:
等待HTTP請求;
創建一個servletRequest對象和一個servletResponse對象;
若請求的是一個靜態資源,則調用StaticResourceProcessor對象的process()方法,傳入servletRequest對象和servletResponse對象;
若請求的是servlet,則載入相應的servlet類,調用其service()方法,傳入servletRequest對象和servletResponse對象。
注意 在該servlet容器中,每次請求servlet都會載入相應的servlet類。
本節的應用程序包括6個類:
HttpServer1
Request
Response
StaticResourceProcessor
ServletProcessor1
Constants
圖2-1展示了本節中的servlet容器的UML類圖。
該應用程序的入口點(靜態main()方法)在類HttpServer1中。main()方法創建HttpServer1的一個實例,然后調用其await()方法。await()方法會等待HTTP請求,為接收到的每個請求創建一個Request和一個Response對象,并根據該HTTP請求的是靜態資源或是servlet,將該HTTP請求分發給一個StaticResourceProcessor實例或一個ServletProcessor實例。
Constants類中定義了靜態final WEB_ROOT,供其他的類引用。WEB_ROOT指定了該servlet容器中使用的PrimitiveServlet類和靜態資源的位置。
HttpServer1類的實例會一直等待HTTP請求,直到接收到一條關閉命令。可以使用第1章介紹的方法來發布關閉命令。
該應用程序中的各個類會在接下來的幾節中逐個說明。
2.2.1 HttpServer1類
應用程序1中的HttpServer1類與第1章中簡單Web服務器應用程序中的HttpServer類似。但是,該應用程序中的HttpServer1類既可以對靜態資源請求,也可以對于servlet資源請求。若要請求一個靜態資源,可以在瀏覽器的地址欄或URL框中輸入如下格式的URL:
http://machineName:port/staticResource這與第1章的Web服務器應用程序中對靜態資源的請求相同。
若要請求servlet資源,可以使用如下格式的URL:
因此,若要請求本地瀏覽器上的名為PrimitiveServlet的servlet,可以在瀏覽器的地址欄或URL框中輸入如下的URL:
http://localhost:8080/servlet/Primitiveservlet應用程序1中的servlet容器會處理對PrimitiveServlet的請求。但是,若要調用其他的servlet(如ModernServlet),則servlet容器拋出異常。在后面的章節中,你將學會如何構建可以兼具兩種功能的servlet容器。
HttpServer1類的定義在代碼清單2-2中。
該類的await()方法會一直等待HTTP請求,直到接收到一條關閉命令,這點與第1章中的await()方法類似。區別在于,本章中的await()方法可以將HTTP請求分發給StaticResourceProcessor對象或ServletProcessor對象來處理。當URI包含字符串“/servlet/”時,會把請求轉發給servletProcessor對象處理。否則的話,把HTTP請求傳遞給StaticResourceProcessor對象處理。注意代碼清單2-2中灰色的部分。
2.2.2 Request類
servlet的service方法會從servlet容器中接收一個javax.servlet.ServletRequest實例一個和javax.servlet.ServletResponse實例。即,對每個HTTP請求來說,servlet容器必須創建一個ServletRequest對象和一個ServletResponse對象,并將它們作為參數傳給它服務的servlet的service()方法。
ex02.pyrmont.Request類表示被傳遞給servlet的service()方法的一個request對象。它必須實現javax.Servlet.servletRequest接口中聲明的所有方法。但為了簡單起見,這里只給出了部分方法的實現,其余方法的實現會在后面的章節給出。為了能夠編譯Request類,需要將未實現的方法留空。代碼清單2-3中給出了Request類的定義,其簽名返回obejct實例的所有方法都會返回null。
此外,Request類還包括了在第1章中介紹過的parse()和getUri()方法。
2.2.3 Response類
ex02.pyrmont.Response類實現javax.servlet.servletResponse接口,類定義參見代碼清單2-4。該類提供了servletResponse接口中聲明的所有方法的實現。與Request類類似,除了getWriter()方法以外,大部分方法的實現都留空。
在getWriter()方法中,PrintWriter類的構造函數的第2個參數是一個布爾值,表示是否啟用autoFlush。對第2個參數傳入true表示對println()方法的任何調用都會刷新輸出。但是調用print()方法不會刷新輸出。
因此,如果在servlet的service()方法的最后一行調用print()方法,則該輸出內容不會被發送給瀏覽器。這個bug會在后續的版本中修改。
Response類中仍然保留了第1章中介紹過的sendStaticResource()方法。
2.2.4 StaticResourceProcessor類
ex02.pyrmont.StaticResourceProcessor類用于處理對靜態資源的請求。該類只有一個方法,即process()方法。代碼清單2-5給出了StaticResourceProcessor類的定義。
process()方法接收兩個參數:一個ex02.pyrmont.Request實例和一個ex02.pyrmont.Response實例。該方法僅僅調用Response對象的sendStaticResource()方法。
2.2.5 servletProcessor1類
ex02.pyrmont.servletProcessor1類的定義參見代碼清單2-6,該類用于處理對servlet資源的HTTP請求。
代碼清單2-6 servletProcessor1類的定義
servletProcessor1類很簡單,只有一個方法:process()方法。該方法接收兩個參數,一個javax.servlet.ServletRequest實例和一個javax.servlet.ServletResponse實例。該方法通過調用getRequestUri()方法從ServletRequest對象中獲取URI:
String uri = request.getUri();記住,URI的格式如下所示:
/servlet/servletName其中,servletName是請求的servlet資源的類名。
為了載入servlet類,需要從URI中獲取servlet的類名。可以使用process()方法的下一行語句獲取servlet的類名:
接下來,porcess()方法會載入該servlet類。為了載入類,需要創建一個類載入器,并且指明到哪里查找要載入的類。對于本節的servlet容器,類載入器會到Constant.WEB_ROOT指定的工作目錄下的webroot目錄中查找要載入的類。
注意 有關類載入器的詳細內容將在第8章中介紹。
為了載入一個servlet類,可以使用java.net.URLClassLoader類來完成,該類是java.lang.ClassLoader類的一個直接子類。一旦創建了URLClassLoader類的實例后,就可以使用它的loadClass()方法來載入servlet類。實例化URLClassLoader類很簡單。該類有三個構造函數,其中比較簡單的一個構造函數的簽名如下所示:
其中,urls是一個java.net.URL對象數組,當載入一個類時每個URL對象都指明了類載入器要到哪里查找類。若一個URL以“/”結尾,則表明它指向的是一個目錄。否則,URL默認指向一個JAR文件,根據需要載入器會下載并打開這個JAR文件。
注意 在servlet容器中,類載入器查找servlet類的目錄稱為倉庫(repository)。
在應用程序中,類載入器只需要查找一個位置,即工作目錄下的webroot目錄。因此,需要先創建只有一個URL的一個數組。URL類提供了一系列構造函數,因此有很多種方法可以創建URL對象。對于本應用程序,使用與Tomcat中另一個類中使用的相同構造函數,該構造函數的簽名如下所示:
可以為第2個參數指定一個目錄,指定第1個和第3個參數為null,這樣就可以使用構造函數了。但是還有一個構造函數,它接受3個參數:
public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException
因此,若只使用如下語句,編譯器就無法知道要調用哪個構造函數了,并且會報錯:
因此,可以使用下面的代碼,對于編譯器指明第三個參數的類型:
URLStreamHandler streamHandler = null; new URL(null, aString, streamHandler);第2個參數中的字符串指明了倉庫的路徑,也就是查找servlet類的目錄。可以使用下面的代碼生成倉庫:
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;將上述代碼綜合到一起,就得到了創建URLCLassLoader實例的process()方法的部分代碼:
注意 生成倉庫后會調用org.apache.catalina.startup.ClassLoaderFactory類的createClassLoader()方法,生成URL對象后會調用org.apache.catalina.loader.StandardClassLoader類的addRepository()方法。這些方法將在后續章節中介紹。
有了類載入器后,就可以通過調用loadClass()方法來載入servlet類:
接下來,process()方法會創建已載入的servlet類的一個實例,將其向下轉型為javax.servlet.servlet,并調用其service()方法:
2.2.6 運行應用程序
要在Windows平臺上運行該程序,可以在工作目錄下執行如下命令:
java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1在Linux平臺上,需要用冒號分割兩個庫文件:
java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1若想測試應用程序,可以在瀏覽器的地址欄或者URL框中輸入如下地址:
http://localhost:8080/index.html或
http://localhost:8080/servlet/Primitiveservlet當調用PrimitiveServlet類時,可以在瀏覽器中看到如下輸出:
Hello. Roses are red.注意,你是看不到第2個字符串“Violets are blue”的,因為只有第1個字符串會發送到瀏覽器。這個問題將在第3章解決。
總結
以上是生活随笔為你收集整理的《深入剖析Tomcat》一2.2 应用程序 1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 戴尔携手英特尔揭示技术与员工潜能背后的真
- 下一篇: 编程没有捷径 奇葩冒牌程序员的故事