Tomcat 是一個廣泛使用的開源 Java Servlet 容器,用于運行 Java Web 應用程序。雖然 Tomcat 本身功能強大且復雜,但通過手寫一個簡易版的 Tomcat,我們可以更好地理解其核心工作原理。本文將帶你一步步實現一個簡易版的 Tomcat,并深入探討其核心組件和運行機制。
一、項目概述
Tomcat是一個簡易的Java Web服務器,它能夠處理HTTP請求并調用相應的Servlet進行處理。項目的核心功能包括:
-
監聽HTTP請求并解析請求內容。
-
根據請求路徑調用相應的Servlet。
-
支持GET和POST請求方法。
-
使用注解配置Servlet的URL映射。
-
通過反射機制動態加載Servlet類。
二、項目結構
首先,我們來看一下項目的整體結構:
項目的主要類及其功能如下:
-
ResponseUtil:用于生成HTTP響應頭。
-
SearchClassUtil:掃描指定包下的類文件,獲取類的全限定名。
-
WebServlet:自定義注解,用于標記Servlet并指定URL映射。
-
LoginServlet?和?ShowServlet:具體的Servlet實現類,處理不同的HTTP請求。
-
HttpServletRequest?和?HttpServletResponse:模擬HTTP請求和響應對象。
-
GenericServlet?和?HttpServlet:抽象類,提供Servlet的基本實現。
-
Servlet:Servlet接口,定義了Servlet的生命周期方法。
-
MyTomcat:主類,負責啟動服務器并處理HTTP請求。
-
ServletConfigMapping:維護URL與Servlet的映射關系。
三、核心組件解析
?1、 ResponseUtil 類
ResponseUtil
?類用于生成HTTP響應頭。它提供了兩個靜態方法:
-
getResponseHeader200(String context)
:生成狀態碼為200的HTTP響應頭,并將響應內容附加到響應頭后。 -
getResponseHeader404()
:生成狀態碼為404的HTTP響應頭。
public class ResponseUtil {public static final String responseHeader200 = "HTTP/1.1 200 \r\n" +"Content-Type:text/html; charset=utf-8 \r\n" + "\r\n";public static String getResponseHeader404() {return "HTTP/1.1 404 \r\n" +"Content-Type:text/html; charset=utf-8 \r\n" + "\r\n" + "404";}public static String getResponseHeader200(String context) {return "HTTP/1.1 200 \r\n" +"Content-Type:text/html; charset=utf-8 \r\n" + "\r\n" + context;}
}
?2、SearchClassUtil 類
?SearchClassUtil
?類用于掃描指定包下的類文件,并獲取這些類的全限定名。它通過遞歸遍歷目錄結構,找到所有以.class
結尾的文件,并將其路徑轉換為類的全限定名。
public class SearchClassUtil {public static List<String> classPaths = new ArrayList<String>();public static List<String> searchClass() {String basePack = "com.qcby.webapps.myweb";String classPath = SearchClassUtil.class.getResource("/").getPath();basePack = basePack.replace(".", File.separator);String searchPath = classPath + basePack;doPath(new File(searchPath), classPath);return classPaths;}private static void doPath(File file, String classpath) {if (file.isDirectory()) {File[] files = file.listFiles();for (File f1 : files) {doPath(f1, classpath);}} else {if (file.getName().endsWith(".class")) {String path = file.getPath().replace(classpath.replace("/", "\\").replaceFirst("\\\\", ""), "").replace("\\", ".").replace(".class", "");classPaths.add(path);}}}
}
?3.?WebServlet 注解
WebServlet
?是一個自定義注解,用于標記Servlet類并指定URL映射。它包含一個urlMapping
屬性,用于指定Servlet處理的URL路徑。
package com.qcby.util;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {String urlMapping() default "";
}
? 4、LoginServlet 和 ShowServlet 類
?LoginServlet
?和?ShowServlet
?是兩個具體的Servlet實現類,分別處理/login
和/show
路徑的請求。它們繼承自HttpServlet
,并重寫了doGet
方法以處理GET請求。
@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {System.out.println("我是login的doGet方法");response.writeServlet(ResponseUtil.getResponseHeader200("hello"));}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {}
}@WebServlet(urlMapping = "/show")
public class ShowServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {System.out.println("我是show");response.writeServlet(ResponseUtil.getResponseHeader200("show"));}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) {}
}
5、HttpServletRequest 和 HttpServletResponse
HttpServletRequest
?和?HttpServletResponse
?類分別模擬了HTTP請求和響應對象。HttpServletRequest
?包含請求路徑和請求方法,HttpServletResponse
?包含輸出流,用于向客戶端發送響應。
package com.qcby.webapps.servlet.req;public class HttpServletRequest {private String path;private String method;public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}
}
HttpServletRequest
?類封裝了 HTTP 請求的路徑和方法(GET、POST 等)。
package com.qcby.webapps.servlet.req;import java.io.IOException;
import java.io.OutputStream;public class HttpServletResponse {private OutputStream outputStream;public HttpServletResponse(OutputStream outputStream) {this.outputStream = outputStream;}public void writeServlet(String context) throws IOException {outputStream.write(context.getBytes());}
}
HttpServletResponse
?類封裝了 HTTP 響應,提供了向客戶端輸出數據的方法。
6.?GenericServlet 和?HttpServlet 類
GenericServlet
?是一個抽象類,提供了Servlet的基本實現,包括init
和destroy
方法。HttpServlet
?繼承自GenericServlet
,并實現了service
方法,根據請求方法調用相應的doGet
或doPost
方法。
public abstract class GenericServlet implements Servlet {public void init() {System.out.println("------初始化servlet------");}public void destroy() {System.out.println("------實現servlet對象的銷毀------");}
}public abstract class HttpServlet extends GenericServlet {public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {if (request.getMethod().equals("GET")) {doGet(request, response);} else {doPost(request, response);}}protected abstract void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException;protected abstract void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException;
}
7.?Servlet 接口
Servlet
?接口定義了Servlet的生命周期方法,包括init
、service
和destroy
。
package com.qcby.webapps.servlet;import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;import java.io.IOException;public interface Servlet {void init(); // Servlet 初始化void service(HttpServletRequest request, HttpServletResponse response) throws IOException; // 處理請求void destroy(); // 銷毀
}
?8.?MyTomcat 類
MyTomcat
?類是項目的核心,負責啟動服務器并處理HTTP請求。它通過ServerSocket
監聽指定端口,接收客戶端請求,解析請求內容,并根據請求路徑調用相應的Servlet。
package com.qcby;import com.qcby.webapps.servlet.HttpServlet;
import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;import static com.qcby.ServletConfigMapping.servletMap;public class MyTomcat {static HttpServletRequest request = new HttpServletRequest();public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8484);while (true) {Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);int count = 0;while (count == 0) {count = inputStream.available();}byte[] bytes = new byte[count];inputStream.read(bytes);String context = new String(bytes);System.out.println(context);if (context.equals("")) {System.out.println("你輸入了一個空請求");} else {String firstLine = context.split("\\n")[0];request.setPath(firstLine.split("\\s")[1]);request.setMethod(firstLine.split("\\s")[0]);}if (servletMap.containsKey(request.getPath())) {System.out.println("存在于HashMap中");HttpServlet servlet = servletMap.get(request.getPath());servlet.service(request, response);} else {System.out.println("不存在于HashMap中");}}}
}
9.?ServletConfigMapping 類
ServletConfigMapping
?類維護了URL與Servlet的映射關系。它通過SearchClassUtil
掃描指定包下的類,利用反射機制獲取帶有@WebServlet
注解的類,并將其實例化后存入servletMap
中。
package com.qcby;import com.qcby.util.SearchClassUtil;
import com.qcby.util.WebServlet;
import com.qcby.webapps.servlet.HttpServlet;import java.util.HashMap;
import java.util.List;
import java.util.Map;public class ServletConfigMapping {public static Map<String, HttpServlet> servletMap = new HashMap<>();static {List<String> classNames = SearchClassUtil.searchClass();for (String path : classNames) {try {Class<?> clazz = Class.forName(path);WebServlet webServlet = clazz.getDeclaredAnnotation(WebServlet.class);HttpServlet servlet = (HttpServlet) clazz.newInstance();servletMap.put(webServlet.urlMapping(), servlet);System.out.println(servletMap);} catch (Exception e) {e.printStackTrace();}}}
}
四、運行流程
-
啟動 Tomcat:
MyTomcat
?類的?main
?方法啟動,監聽 8484 端口。 -
接收請求:當有客戶端請求到來時,
MyTomcat
?解析請求的路徑和方法。 -
分發請求:根據請求路徑從?
ServletConfigMapping.servletMap
?中獲取對應的 Servlet 實例,并調用其?service
?方法。 -
處理請求:Servlet 根據請求方法調用?
doGet
?或?doPost
?方法,生成響應并返回給客戶端。
通過手寫一個簡易版的 Tomcat,我們深入理解了 Servlet 容器的工作原理。雖然這個簡易版 Tomcat 功能有限,但它涵蓋了 Servlet 容器的核心組件和運行機制。希望本文能幫助你更好地理解 Tomcat 和 Servlet 技術,并為后續深入學習打下堅實的基礎。?