How Tomcat Works 2
上一節(How Tomcat Works 1 編寫一個簡單靜態web伺服器)編寫了一個簡單的web伺服器,只能處理靜態的資源,本節將繼續向前邁出一個小步,建立兩個不同的servlet容器,能夠利用servlet簡單的處理動態內容。注意每節的程式碼都是基於上一節的繼續豐富,因此有必要從第一節開始看起。
在編寫程式碼之前,需要先大體瞭解一下Servlet是什麼,方便後面的理解,下面就是一個最簡單的Servlet什麼也沒做:
package prymont; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * 每個Servlet都實現Servlet介面 * 該介面有5個方法需要實現,該Servlet只是為了讓大家對 * Servlet有個整體的印象,因此所有方法都未實現。 */ public class PrimitiveServlet implements Servlet{ //當servlet容器正在被關閉或者servlet容器記憶體不夠的時候,該方法由servlet容器呼叫,且只調用一次 //該方法通常用來清除資源。 @Override public void destroy() { } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } //當servlet已經初始化的時候,該方法由servlet容器呼叫,且只調用一次 //該方法適合做一些一次性的載入動作,比如資料庫驅動等。 @Override public void init(ServletConfig arg0) throws ServletException { } //servlet容器負責為每一次請求呼叫一次service方法,並且傳遞一個ServletRequest(封裝客戶端請求) //和ServletResponse(封裝響應)物件。 @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { } }
先來說說我們的第一個版本的Servlet容器要實現的功能:等待Http請求,如果是請求靜態資源則交給靜態資源處理器,如果是請求動態資源則載入相應的Servlet並呼叫它的service方法,同時傳遞ServletRequest和ServletResponse物件。(第一個版本每次請求servlet類都被載入)
本版本針對上次主要新增StaticResourceProcessor和ServletProcessor1兩個類分別處理靜態資源和動態資源,同時Request和Response物件也各自集成了
http://machineName:port/staticResource請求的是一個靜態資源,http://localhost:8080/servlet/PrimitiveServlet請求的則是一個動態的資源,因此對於HttpServer1只需要稍稍改動一下即可滿足需求。
StaticResourceProcessor只是簡單的呼叫了response的sendStaticResource()方法,也沒有可講的,下面重點講解一下ServletProcessor1的實現:
上面就是處理Servlet的整個過程,可以通過http://localhost:8080/servlet/PrimitiveServlet來訪問我們的servlet。package server1; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * 完成servlet類的動態載入 * 並呼叫servlet的service的方法 * */ public class ServletProcessor1 { /** * 載入Servlet * * @param request * @param response */ public void process(Request request, Response response) { // /servlet/servetclass String uri = request.getUri(); // 擷取servlet名字 String servletName = uri.substring(uri.lastIndexOf("/") + 1); // jdk提供的URL類載入器,根據URL載入class URLClassLoader loader = null; URL[] urls = new URL[1]; // 指定載入webapp下所有的class檔案 File classPath = new File(Constant.WEB_APP); URLStreamHandler streamHandler = null; try { // 使用file協議從本機的classPath載入class // getCanonicalPath返回絕對路徑(不帶.) String repository = new URL("file", null, classPath.getCanonicalPath() + File.separator).toString(); urls[0] = new URL(null, repository, streamHandler); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } loader = new URLClassLoader(urls); Class clazz = null; try { // 根據servlet名字載入class,這裡對名字的處理並不完善,實際需要根據包名做拼接 //為了簡單servlet類全不不帶包名 clazz = loader.loadClass(servletName); } catch (ClassNotFoundException e) { e.printStackTrace(); } Servlet servlet = null; try { // 反射建立例項 servlet = (Servlet) clazz.newInstance(); // 向下轉型呼叫service方法 servlet.service((ServletRequest) request, (ServletResponse) response); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ServletException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
我們的第一個程式有一個嚴重的問題,就是當傳遞給servlet的service方法的時候將Request和Response物件都向上轉型了,這樣如果知道內部實現的人,則可以在寫自己的Servlet的時候向下轉型成Request和Response物件,並呼叫他們的方法,實際上這兩個物件是容器私有的不應該暴露給開發者(具體是有些方法不能讓開發者使用),有一個方法是讓這兩個類使用預設的包訪問許可權,其實有一個更優雅的實現方式就是門面模式。
在本節的第二版中增加兩個類,RequestFacade和ResponseFacade用來控制某些方法的可見性。具體的方式是給RequestFacade提供一個帶參建構函式,引數型別為Request物件,門面類封裝servletrequest方法,其實只是傳遞給Request物件實現,這樣在呼叫service方法時傳遞的facade物件,這樣即使通過向下轉型得到的也是被封裝過的Facade物件。RequestFacade物件片段如下:
public class RequestFacade implements ServletRequest {
private ServletRequest request = null;
public RequestFacade(Request request) {
this.request = request;
}
/* implementation of the ServletRequest*/
public Object getAttribute(String attribute) {
return request.getAttribute(attribute);
}
ResponseFacade物件類似,不貼出來程式碼了。
還有一點不同的是ServletProcessor1的部分處理,具體變化的如下:
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
// 反射建立例項
servlet = (Servlet) clazz.newInstance();
// 向下轉型呼叫service方法
servlet.service((ServletRequest) requestFacade,
(ServletResponse) responseFacade);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
通過Request物件構造RequestFacade物件並向上轉型傳遞給service方法。
以上就是本節實現的一個簡單的servlet容器。下一節將會接觸到tomcat中一個非常重要的概念——聯結器