1. 程式人生 > >深入剖析Tomcat(2)

深入剖析Tomcat(2)

之前根據第一章的內容和程式碼寫了一個部落格,發現在看這個書的時候寫部落格和跑程式碼是比純看書收穫大的多得多的,因此還是很開心自己沒有懶惰。

在看第二章的時候,我選擇了直接看程式碼,這樣就相當於同時做了複習和預習,然後看第二章的時候就可以跳過很多已知內容了。

第二章一共有兩個例程,主要是引入了java包裡面的Servlet,和門面Facade設計模式。

我們先看第二章的HttpServer1,這個程式中引入了Servlet。和第一章的服務端程式相比,這裡多了一個功能,除了可以訪問http://localhost:8080/index.html之外,還可以訪問http://localhost:8080/servlet/primitiveServlet 來請求一個具體的servlet,然後返回那個servlet裡面的service函式裡面的內容。

我們還是從main函式入口看起。

這裡是和第一章中HttpServer的主要區別。先看else的部分,比較簡單。

 

這個StaticResourceProcessor類,十分簡單,就是包裝了request和response,然後呼叫response的sendStaticResource而已。

然後看一些response方法,主要的區別就是實現了ServletResponse介面,所有的介面方法返回null,只實現了getWriter。

 

這裡返回了一個PrintWriter,並制定了autoFlush為true(這個引數會令任何println的呼叫都會重新整理輸出,但是print則不會)。這個printWriter是一個字元列印流的作用。

我們再來看main中的if裡面的內容,判斷如果解析出來的uri是以“/servlet”開頭的話就會進入這個程式碼塊。

這裡的ServetProcessor1比之前的StaticResourceProcessor稍微麻煩一些。主要方法是process方法:

public void process(Request request, Response response) {

String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;

try {
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// the forming of repository is taken from the createClassLoader method in
// org.apache.catalina.startup.ClassLoaderFactory
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
// the code for forming the URL is taken from the addRepository method in
// org.apache.catalina.loader.StandardClassLoader class.
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}

Servlet servlet = null;

try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}

}

這個方法應該是這個程式的核心了,用到了url類載入器來載入一個servlet。我們debug啟動這個程式,然後直接在這個方法裡面打一個斷點,一步一步走就可以看到這個方法是怎麼通過一個“/servlet/primitiveServlet” uri得到一個PrimitiveServlet類例項的。

重點看try裡面的地方:

新建一個URL[]陣列來給url類載入器使用,新建一個URLStreamHandler給URL的建構函式使用,然後找到存放資源的目錄。

 

Constants常數類裡面的WEB_ROOT可以直接找到真實的一個webroot資料夾。然後通過一個URL類的建構函式生成一個路徑

String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;

這個函式會給傳入的classPath前面加上file:,然後最後面加上“\”。

然後通過URL的另一個建構函式生成一個URL例項。

這個建構函式的定義是

public URL(URL context, String spec, URLStreamHandler handler)

上面那個建構函式的定義是

public URL(String protocol, String host, String file)

因此我們不能在呼叫的時候傳入,URL(null, spec, null) 這樣會找不到正確的建構函式,因此這裡一開始新建的URLStreamHandler為null就有用了。

最後生成PrimitiveServlet類的類載入器。

然後我們看第二個程式HttpServer2

這裡的主要的不同就是在ServletProcessor的process方法裡面,process方法用了requestFacade和responseFacade。

 

(其實我沒有完全懂得為什麼要用外觀類,按照書裡面的說法)這裡主要是為了安全性考慮,因為Request和Response裡面的方法都是public方法,如果有懂容器機制的人通過向下轉型來獲取到了Request和Response類,那他們就會呼叫到裡面的方法,因此我們需要將他們隱藏起來。這裡就通過外觀類來將Request和Response分別作為成員變數賦給外觀類,然後再在外觀類裡面呼叫Request和Response裡面的方法以達到安全的目的。