上一節(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中一個非常重要的概念——連接器