1. 程式人生 > >How Tomcat Works 2

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中一個非常重要的概念——聯結器