上文中描述的簡單的服務器是不符合Servlet規范的,所以本文進一步描述一個簡單的Servlet容器是怎么實現的
所以我們首先要明白Servlet接口規范,規范有不同版本,本人就先一視同仁了:
public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo(); public void destroy(); }
上面的方法中,init() 、service()和 destroy()是與Servlet的生命周期密切相關的方法,熟悉Servlet生命周期的童鞋是比較清楚的
Servlet容器通常只調用Servlet實例的init()方法一次,用于初始化相關信息;
service()方法用于響應客戶端請求,傳入ServletRequest和ServletResponse參數,service()方法會被多次調用
當Servlet容器關閉或Servlet容器需要釋放內存時,會調用Servlet實例的destroy()方法,用于清理自身持有的資源,如內存、文件句柄和線程等,確保所有的持久化狀態與內存中該Servlet對象的當前狀態同步。
下面我們來看一個簡單的Servlet容器怎么實現:
HttpServer1類:
public class HttpServer1 { /** WEB_ROOT is the directory where our HTML and other files reside. * For this package, WEB_ROOT is the "webroot" directory under the working * directory. * The working directory is the location in the file system * from where the java command was invoked. */ // shutdown command private static final String SHUTDOWN_COMMAND = "/SHUTDOWN" ; // the shutdown command received private boolean shutdown = false ; public static void main(String[] args) { HttpServer1 server = new HttpServer1(); server.await(); } public void await() { ServerSocket serverSocket = null ; int port = 8080 ; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1" )); } catch (IOException e) { e.printStackTrace(); System.exit( 1 ); } // Loop waiting for a request while (! shutdown) { Socket socket = null ; InputStream input = null ; OutputStream output = null ; try { socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // create Request object and parse Request request = new Request(input); request.parse(); // create Response object Response response = new Response(output); response.setRequest(request); // check if this is a request for a servlet or a static resource // a request for a servlet begins with "/servlet/" if (request.getUri().startsWith("/servlet/" )) { ServletProcessor1 processor = new ServletProcessor1(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } // Close the socket socket.close(); // check if the previous URI is a shutdown command shutdown = request.getUri().equals(SHUTDOWN_COMMAND); } catch (Exception e) { e.printStackTrace(); System.exit( 1 ); } } } }
上面方法中,Servlet容器根據請求的路徑分發到不同的處理類進行處理,servlet請求交給ServletProcessor1類處理,靜態資源交給StaticResourceProcessor類處理
注:本文中的Servlet容器跟上文相比,將響應請求的功能解耦, 由處理器類(ServletProcessor1類和StaticResourceProcessor類)來承擔
Request類(注意我們這里的Request類已經實現了ServletRequest 接口,已經是按照規范來搞的了)
public class Request implements ServletRequest { private InputStream input; private String uri; public Request(InputStream input) { this .input = input; } public String getUri() { return uri; } private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' ' ); if (index1 != -1 ) { index2 = requestString.indexOf(' ', index1 + 1 ); if (index2 > index1) return requestString.substring(index1 + 1 , index2); } return null ; } public void parse() { // Read a set of characters from the socket StringBuffer request = new StringBuffer(2048 ); int i; byte [] buffer = new byte [2048 ]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1 ; } for ( int j=0; j<i; j++ ) { request.append(( char ) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); } /* implementation of the ServletRequest */ public Object getAttribute(String attribute) { return null ; }
/ /省略后面的代碼
}
Response類(實現ServletResponse接口)
public class Response implements ServletResponse { private static final int BUFFER_SIZE = 1024 ; Request request; OutputStream output; PrintWriter writer; public Response(OutputStream output) { this .output = output; } public void setRequest(Request request) { this .request = request; } /* This method is used to serve a static page */ public void sendStaticResource() throws IOException { byte [] bytes = new byte [BUFFER_SIZE]; FileInputStream fis = null ; try { /* request.getUri has been replaced by request.getRequestURI */ File file = new File(Constants.WEB_ROOT, request.getUri()); fis = new FileInputStream(file); /* HTTP Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF [ message-body ] Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ int ch = fis.read(bytes, 0 , BUFFER_SIZE); while (ch!=-1 ) { output.write(bytes, 0 , ch); ch = fis.read(bytes, 0 , BUFFER_SIZE); } } catch (FileNotFoundException e) { String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>" ; output.write(errorMessage.getBytes()); } finally { if (fis!= null ) fis.close(); } } public PrintWriter getWriter() throws IOException { // autoflush is true, println() will flush, // but print() will not. writer = new PrintWriter(output, true ); return writer; } / /省略后面的代碼 }
上面實現了獲取?PrintWriter對象的方法
StaticResourceProcessor類(靜態資源處理)
public class StaticResourceProcessor { public void process(Request request, Response response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } } }
方法中僅僅簡單的調用了response對象的sendStaticResource()方法
ServletProcessor1類(servlet資源處理類)
public class ServletProcessor1 { 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()); } } }
上面的步驟是首先根據客戶端請求路徑獲取請求的servlet名稱,然后根據servlet類路徑(類載入器倉庫)創建類載入器,進一步根據servlet名稱載入該servlet類并實例化,最后調用該servlet的serice()方法
其中Constants類保持工作目錄常量(Servlet類路徑)
public class Constants { public static final String WEB_ROOT = System.getProperty( "user.dir") + File.separator + "webroot" ; }
我們繼續分析,其實上面的ServletProcessor1類的process()方法是存在問題的,在下面的代碼段
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()); }
這里的Request對象與Resposne對象分別向上轉型為ServletRequest實例和ServletResponse實例
如果了解這個servlet容器內部原理的servlet程序員就可以(在自己實現的serlet類中)將ServletRequest實例和 ServletResponse實例分別向下轉型為真實的Request實例和Response實例,就可以調用各自的公有方法了(Request實例的 parse()方法和Response實例的sendStaticResource()方法),而servlet容器又不能將這些公有方法私有化,因為其 他外部類還要調用它們,一個比較完美的解決方法是分別為Request類和Response類創建外觀類,分別為RequestFacade類與 ResponseFacade類,與前者實現共同的接口,然后保持對前者的引用,相關的接口實現方法分別調用其引用實例的方法,于是世界從此清靜了
注:其實本人認為這里不應該叫做外觀類,可能叫包裝器類更合適吧(因為本人沒聽過外觀類有實現共同接口的說法)
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); } public Enumeration getAttributeNames() { return request.getAttributeNames(); } public String getRealPath(String path) { return request.getRealPath(path); } / /省略后面的代碼 }
ResponseFacade類
public class ResponseFacade implements ServletResponse { private ServletResponse response; public ResponseFacade(Response response) { this .response = response; } public void flushBuffer() throws IOException { response.flushBuffer(); } public int getBufferSize() { return response.getBufferSize(); } / /省略后面的代碼
}
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創?
轉載請注明出處 博客園 刺猬的溫馴?
本人郵箱: chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
