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的實現:

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的整個過程,可以通過http://localhost:8080/servlet/PrimitiveServlet來訪問我們的servlet。

我們的第一個程序有一個嚴重的問題,就是當傳遞給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中一個非常重要的概念——連接器


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章