一、 Tomcat架構
Tomcat的主要角色是 servlet容器,提供一個解釋器,能夠解析并執行JavaScript Object Notation (JON)腳本(后更改為Servlet),并將請求傳送到指定的服務器(如JavaBean)。因此,它是Servlet處理請求、與客戶端通信(RESTful API)以及處理服務器端業務邏輯的關鍵組件。
1.1 HttpServletRequest
HttpServletRequest類在模型中充當?HTTP請求的元數據載體,是連接網絡層(Connector)與業務層(Servlet)的橋梁。?
public class HttpServletRequest {private String url;public String method;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}
}
URL路徑:標識客戶端請求的資源位置(如
/login
)。HTTP方法:明確操作類型(GET獲取、POST提交),用于路由到Servlet的正確處理邏輯。
與實際 Tomcat 實現的差異
簡化實現 | 實際 Tomcat 的 HttpServletRequest |
---|---|
僅包含url 和method 字段 | 包含完整的請求信息(頭、參數、會話、Cookie等) |
直接通過Setter注入數據 | 數據由Tomcat底層協議解析器自動填充 |
無協議規范校驗(如方法合法性) | 嚴格校驗HTTP協議規范(如方法名有效性) |
無線程安全控制 | 每個請求獨立實例,天然線程安全 |
1.2?HttpServletResponse
HttpServletResponse類在模型中充當?響應數據的輸出通道,是業務邏輯(Servlet)與網絡層(Connector)之間的橋梁。?
核心思想
數據輸出本質:所有響應最終通過底層
OutputStream
發送字節流。分層設計:業務層無需關心網絡細節,只需操作抽象響應對象。
協議封裝:實際Tomcat隱藏了HTTP協議拼接的復雜性。
public class HttpServletResponse {//用于持有輸出流,通過此流將數據發送到客戶端private OutputStream outputStream; //接收一個OutputStream(通常來自服務器Socket連接),以便后續寫入響應內容。public HttpServletResponse(OutputStream outputStream){this.outputStream = outputStream;} //將字符串轉換為字節數組并寫入輸出流。public void write(String context) throws IOException {outputStream.write(context.getBytes());}
}
1. 響應數據輸出通道
????????持有輸出流:通過構造函數接收底層網絡輸出流(通常來自Tomcat的
Connector
模塊),直接操作字節流。????????數據寫入:
write()
方法將字符串轉換為字節寫入流,完成響應體的發送。2. 在請求處理流程中的角色
????????Connector階段:由連接器創建實例,并注入與客戶端Socket關聯的
OutputStream
。????????Container階段:傳遞到Servlet的
service()
方法,供業務邏輯生成響應內容。3. 基礎協議支持
????????僅處理響應體:直接寫入原始字節流,但未包含HTTP協議頭(如狀態碼、Content-Type)。
與實際 Tomcat 實現的差異
簡化實現 | 實際 Tomcat 的 HttpServletResponse |
---|---|
僅支持直接寫入響應體 | 支持自動管理狀態碼、響應頭、Cookies等 |
無編碼控制(默認平臺編碼) | 提供setCharacterEncoding() 和setContentType() 方法 |
無緩沖機制(直接寫Socket流) | 內置緩沖區(BufferedOutputStream )提升性能 |
無重定向/轉發支持 | 支持sendRedirect() 和forward() 方法 |
無錯誤處理機制 | 可調用sendError() 返回標準錯誤頁面 |
1.3 servlet接口
servlet接口在模型中定義了?請求處理的唯一入口,是Tomcat將網絡層(Connector)與業務邏輯(開發者代碼)解耦的核心設計。?
核心思想
面向接口編程:Tomcat通過接口調用開發者代碼,降低耦合度。
請求-響應范式:所有Web交互抽象為
request
輸入和response
輸出。容器管理:Servlet實例的創建、調用、銷毀由Tomcat控制,開發者聚焦業務邏輯。
public interface servlet {public void service(HttpServletRequest request, HttpServletResponse response);
}
1. 業務邏輯的統一入口
請求處理抽象化:所有具體業務邏輯(如用戶登錄、數據查詢)必須實現
service()
方法,Tomcat通過此方法將控制權交給開發者。協議無關性:開發者無需關心HTTP協議的解析細節(如報文拆分、狀態碼生成),只需操作
request
和response
對象。2. 在Tomcat處理流程中的角色
Container階段調用:當Tomcat完成URL匹配并確定目標Servlet后,調用其
service()
方法。流程控制樞紐:通過
service()
方法,開發者可以:
讀取請求參數(
request.getParameter()
)。處理業務數據(如訪問數據庫)。
生成響應內容(
response.write()
)。3. 設計模式體現
模板方法模式:實際Servlet實現類(如
HttpServlet
)通過重寫service()
或具體方法(doGet()
/doPost()
)定義行為。控制反轉(IoC):Tomcat容器控制Servlet生命周期及方法調用,開發者僅需實現接口。
與實際?javax.servlet.Servlet
?接口的差異
簡化實現 | 實際 Servlet 接口 |
---|---|
僅定義service() 方法 | 包含init() 、destroy() 等生命周期方法 |
無配置參數支持 | 可通過ServletConfig 獲取初始化參數 |
直接處理HTTP協議 | 通常繼承HttpServlet 抽象類,實現doGet() 等方法 |
無線程安全約束 | 規范明確Servlet實例默認單例多線程調用,需開發者自行保證線程安全 |
1.4 HttpServlet
HttpServlet類在模型中實現了?HTTP方法的路由分發,是Tomcat將協議細節與業務邏輯分離的關鍵設計。?
核心思想
職責分離:協議處理(方法路由)與業務邏輯(
doXxx()
)解耦。擴展性:通過覆蓋特定方法支持新功能,無需修改框架代碼。
規范化:統一Servlet實現模式,降低開發者學習成本。
public abstract class HttpServlet implements servlet{//doGet方法public void doGet(HttpServletRequest request,HttpServletResponse response){}//doPost方法public void doPost(HttpServletRequest request,HttpServletResponse response){}//根據請求方式判斷調用的方法@Overridepublic void service(HttpServletRequest request, HttpServletResponse response) {if(request.getMethod().equals("GET")){doGet(request,response);}else if(request.getMethod().equals("POST")){doPost(request,response);}}
}
核心作用?
1. HTTP方法的路由分發
請求方法解耦:將不同HTTP方法(GET/POST)的處理邏輯分離到獨立方法(
doGet()
/doPost()
),避免在service()
中堆積條件判斷。開發者友好性:子類只需覆蓋關注的方法(如
doGet()
),無需重寫整個service()
。2. 在Tomcat處理流程中的角色
容器調用入口:Tomcat調用
service()
方法后,自動根據請求方法觸發對應的doXxx()
。模板方法模式:定義算法骨架(方法路由),具體步驟由子類實現。
3. 規范擴展性
支持新HTTP方法:通過擴展
service()
方法(如添加doPut()
),兼容更多HTTP方法。統一錯誤處理:可集中處理未實現的方法(如返回405狀態碼)。
與實際?HttpServlet
?的差異
簡化實現 | 實際 javax.servlet.http.HttpServlet |
---|---|
僅支持GET/POST方法 | 支持所有HTTP方法(PUT/DELETE/HEAD等) |
無HTTP協議版本處理 | 區分HTTP/1.0和HTTP/1.1的語義差異 |
無默認錯誤響應 | 自動返回405 Method Not Allowed ?或?400 Bad Request |
無線程安全提示 | 明確說明Servlet實例需線程安全 |
1.5 SearchClassUtil
用于獲得某一個包下所有類的路徑
public class SearchClassUtil {public static List<String> classPaths = new ArrayList<>();public static List<String> searchClass(){//需要掃描的包名String basePath = "com.qcby.myweb";//將獲取到的包名轉換為路徑String classPath = SearchClassUtil.class.getResource("/").getPath();basePath = basePath.replace(".", File.separator);String searchPath = classPath + basePath;doPath(new File(searchPath),classPath);//得到指定包下所有類的絕對路徑,利用絕對路徑和Java反射機制得到類對象return classPaths;}/*** 該方法會得到所有的類,將類的絕對路徑你寫入到classPaths中* file*/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);}}}public static void main(String[] args) {List<String> classes = SearchClassUtil.searchClass();for(String s:classes){System.out.println(s);}}
}
運行結果
1.6 ResponseUtil
/*** 設置響應頭*/
public class ResponseUtil {public static final String responseHeader200 = "HTTP/1.1 200 \r\n"+"Content-Type:text/html\r\n"+"\r\n";public static String getResponseHeader404(){return "HTTP/1.1 404 \r\n"+"Content-Type:text/html\r\n"+"\r\n" + "404";}public static String getResponseHeader200(String context){return "HTTP/1.1 200\r\n"+"Content-Type:text/html\r\n"+"\r\n" + context;}}
1.7 XWServlet
核心目標:替代傳統的?web.xml
?配置文件,通過聲明式編程將 Servlet 的 URL 映射路徑直接綁定到類上。?
URL 路徑映射:通過?
url
?屬性指定 Servlet 處理的請求路徑(如?/user
)。自動化注冊:Tomcat 啟動時自動掃描帶有該注解的類,并將其注冊到容器中。
零配置開發:開發者無需編寫?
web.xml
,直接在代碼中聲明路由規則。
@Target(ElementType.TYPE) //僅允許標注在類或接口上
@Retention(RetentionPolicy.RUNTIME) //確保注解在運行時可通過反射獲取,這是Tomcat動態注冊 Servlet 的前提
public @interface XWServlet {String url() default ""; //指定 Servlet 的訪問路徑,默認值為空字符串(需開發者顯式配置)
}
1.8 TomcatRoute
public class TomcatRoute {public static HashMap<String, HttpServlet> map = new HashMap<>();static {List<String> paths = PackageUtil.getClassName("com.qcby.myweb");for(String path:paths){try {Class clazz = Class.forName(path);HttpServlet a = (HttpServlet) clazz.getDeclaredConstructor().newInstance();XWServlet xwServlet = (XWServlet) clazz.getDeclaredAnnotation(XWServlet.class);map.put(xwServlet.url(),a);} catch (Exception e) {e.printStackTrace();}}}
}
通過?PackageUtil獲取com.qcby.myweb包下的所有類路徑,并通過反射創建類對象,通過XWServlet 獲取類的url,將url及其對應的類對象放入到HashMap中。
1.9 MyTomcat
public class MyTomcat {static HashMap<String,HttpServlet> map = TomcatRoute.map;public static void dispatch(HttpServletRequest request,HttpServletResponse response){HttpServlet servlet = map.get(request.getUrl());if(servlet != null){servlet.service(request,response);}}public static void start() throws IOException {System.out.println("服務器啟動");ServerSocket serverSocket = new ServerSocket(8083);while (true){Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();HttpServletRequest request = new HttpServletRequest();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String str = reader.readLine();request.setMethod(str.split(" ")[0]);request.setUrl(str.split(" ")[1]);OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);dispatch(request,response);}}public static void main(String[] args) throws IOException {start();}
}
1.9.1?dispatch?
public static void dispatch(HttpServletRequest request,HttpServletResponse response){HttpServlet servlet = map.get(request.getUrl());if(servlet != null){servlet.service(request,response);}
}
通過request中的url信息查找 其對應的類對象,若存在,則調用service方式。
1.9.2?Socket
在Tomcat中,Socket(套接字)是實現網絡通信的核心組件,主要用于處理客戶端與服務器之間的數據傳輸。?
數據從客戶端到Tomcat的傳遞
客戶端發起請求
客戶端通過瀏覽器發送HTTP請求,數據經操作系統封裝為TCP/IP數據包,通過網卡傳輸到目標服務器。網卡接收數據
服務器的網卡接收到物理信號后,將其轉換為二進制數據流,傳遞給操作系統內核。Socket監聽與處理
Tomcat的Connector組件通過Endpoint
(如NioEndpoint
)監聽指定端口,接受Socket連接。操作系統將數據流交給Tomcat的Socket實例,由Processor
解析HTTP協議并生成Request
對象。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?注:每個需要網絡的進程都要申請端口號,有些端口號是被明確占用的,如Mysql的端口號是3306,HTTP的端口號是8080。Servlet容器處理
解析后的請求通過Adapter
轉換為ServletRequest
,由Servlet容器(如Engine
、Host
、Context
)處理業務邏輯,生成響應數據。響應返回客戶端
響應數據通過Socket的輸出流發送回客戶端,經操作系統協議棧封裝后,由網卡轉換為物理信號傳輸到客戶端。
System.out.println("服務器啟動..."); //1.定義ServerSocket對象進行服務器的端口注冊ServerSocket serverSocket = new ServerSocket(8080); //端口號用于區分不同的進程while (true){//2.監聽客戶端的Socket連接程序Socket socket = serverSocket.accept(); //accept()--》用于阻塞監聽,沒有程序訪問時,停止執行,直到有程序訪問時,繼續執行下一步//3.從socket對象當中獲取到一個字節輸入流對象InputStream iStream = socket.getInputStream();HttpServletRequest request = new HttpServletRequest();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String str = reader.readLine();request.setMethod(str.split(" ")[0]);request.setUrl(str.split(" ")[1]);OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);dispatch(request,response);}
代碼逐行解析?
1. 接受客戶端連接
Socket socket = serverSocket.accept();
阻塞等待客戶端連接,返回代表該連接的Socket對象。這是建立TCP通信的第一步。
Tomcat對應實現:
Tomcat的Connector組件(如NioEndpoint)負責監聽端口,使用NIO多路復用或BIO模型處理連接,而非簡單阻塞式accept()。實際會通過線程池高效管理連接。2. 獲取輸入流
InputStream inputStream = socket.getInputStream();
獲取與客戶端Socket關聯的輸入流,用于讀取HTTP請求的原始字節數據。
Tomcat對應實現:
Tomcat的CoyoteAdapter會解析輸入流,將其封裝為org.apache.coyote.Request對象,處理協議細節(如HTTP頭、Chunked編碼)。3. 創建請求對象
HttpServletRequest request = new HttpServletRequest();
實例化一個自定義的請求對象,用于承載解析后的請求信息(如URL、方法)。
Tomcat對應實現:
實際創建的是org.apache.catalina.connector.Request對象,包含完整的請求信息(參數、Session、頭信息等),由容器自動填充數據。4. 包裝緩沖讀取器
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
將字節流轉換為字符流,便于按行讀取請求內容。
潛在問題:
直接使用BufferedReader可能無法正確處理二進制數據(如圖片上傳)。Tomcat底層使用ByteBuffer進行高效二進制解析。5. 讀取請求行
String str = reader.readLine();
讀取HTTP請求的第一行(即請求行),格式為方法 URI 協議版本,例如:
GET /index.html HTTP/1.1Tomcat對應實現:
通過Http11Processor解析請求行,嚴格校驗協議合規性(如方法名合法性),并提取完整URI和協議版本。6. 解析HTTP方法與URL
request.setMethod(str.split(" ")[0]); ?// 如"GET" request.setUrl(str.split(" ")[1]); ? ? // 如"/aa.java"
從請求行中提取HTTP方法和請求路徑,填充到請求對象中。
簡化問題:
未處理URI中的查詢參數(如/user?id=1中的id=1)。
未解析協議版本(如HTTP/1.1與HTTP/2差異)。
未處理可能的異常格式(如空路徑或非法方法)。
7. 獲取輸出流
OutputStream outputStream = socket.getOutputStream();
獲取與客戶端Socket關聯的輸出流,用于向客戶端發送響應數據。
Tomcat對應實現:
輸出流會被包裝為org.apache.coyote.Response,支持緩沖、分塊傳輸(chunked encoding)和自動壓縮(如gzip)。8. 創建響應對象
HttpServletResponse response = new HttpServletResponse(outputStream);
實例化自定義響應對象,關聯輸出流以便寫入響應內容。
Tomcat對應實現:
實際創建的是org.apache.catalina.connector.Response對象,管理狀態碼、響應頭和內容編碼。9. 分發請求
dispatch(request, response);
將請求和響應對象傳遞給調度器,執行后續處理(如路由到Servlet或靜態資源)。
Tomcat對應實現:
容器層級路由:依次通過Engine→Host→Context→Wrapper,匹配對應的Servlet。
過濾器鏈執行:在調用Servlet前,執行所有匹配的Filter。
Servlet生命周期:調用service()方法,處理業務邏輯。
請求處理全流程圖示
客戶端│▼ Socket.accept() // 建立連接│▼ InputStream → 解析請求行 // 讀取HTTP請求基礎信息│▼ 創建Request/Response對象 // 封裝協議數據│▼ dispatch() → 路由到Servlet // 業務邏輯處理│▼ OutputStream → 返回響應 // 發送HTTP響應