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 {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("我是login的doGet方法");
response.writeServlet(ResponseUtil.getResponseHeader200("hello"));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
}
}
@WebServlet(urlMapping = "/show")
public class ShowServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("我是show");
response.writeServlet(ResponseUtil.getResponseHeader200("show"));
}
@Override
protected 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 技术,并为后续深入学习打下坚实的基础。