DIY Tomcat:手写一个简易Servlet容器
在Java Web开发领域,Tomcat堪称经典,它作为Servlet容器,承载着无数Web应用的运行。今天,我将带大家一同探索如何手写一个简易的Tomcat,深入理解其底层原理。
一、背景知识
在开始之前,我们需要对几个关键概念有所了解:
-
Servlet :是一种运行在服务器端的 Java 接口,用于响应客户端请求并生成动态内容。
-
Servlet 容器 :负责管理 Servlet 的生命周期,包括加载、实例化、调用和销毁等操作。
-
Socket 编程 :用于实现网络通信,Tomcat 通过监听 Socket 接收客户端请求。
-
二、核心代码解析
1. MyTomcat.java
这是整个简易 Tomcat 的核心入口。我们创建了一个 ServerSocket 对象,监听 8080 端口,等待客户端的连接。
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept();
// 处理客户端请求的逻辑
}
当有客户端连接时,通过 Socket 获取输入流,读取请求信息,并解析出请求方法和路径。
InputStream inputStream= socket.getInputStream();
HttpServletResponse response=new HttpServletResponse(socket);
HttpServletRequest request=new HttpServletRequest();
int count=0;
while(count==0){
count=inputStream.available();
}
byte[] bytes=new byte[count];
inputStream.read(bytes);
String context=new String(bytes);
// 解析请求方法和路径
String Context = new String(bytes);
String firstLine = Context.split("\\n")[0];
String method = firstLine.split("\\s")[0];
String path = firstLine.split("\\s")[1];
request.setPath(path);
request.setMethod(method);
根据解析出的路径,在 Servlet 容器中查找对应的 Servlet 对象,并调用其 service 方法。
if(ServletConfigMapping.servletMap.get(request.getPath())!=null)
{
System.out.println("------------------");
HttpServlet ClassServlet=ServletConfigMapping.servletMap.get(request.getPath());
ClassServlet.service(request, response);
}
2. ServletConfigMapping.java
这个类负责初始化 Servlet 容器,扫描指定包下的所有类,获取带有 @WebServlet 注解的类,并将其路径和对象存入 HashMap 中。
public static void init() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
List<String> classesPath= SearchClassUtil.searchClass();
for(String path:classesPath){
getMessage(path);
}
}
public static void getMessage(String classPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class clazz=Class.forName(classPath);
WebServlet webServlet=(WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
HttpServlet servlet=(HttpServlet) clazz.newInstance();
servletMap.put(webServlet.urlMapping(),servlet);
}
3. LoginServlet.java 和 ShowServlet.java
这两个类是具体的 Servlet 实现,继承自 HttpServlet,并重写 doGet 和 doPost 方法,用于处理不同的 HTTP 请求。
@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
// 处理 GET 请求的逻辑
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) {
// 处理 POST 请求的逻辑
}
}
4. SearchClassUtil.java
该工具类用于扫描指定包下的所有类文件,获取其全类名,为 Servlet 容器的初始化提供类路径信息。
public static List<String> searchClass() {
String basePack = "com.qcby.webapps";
String classPath = SearchClassUtil.class.getResource("/").getPath();
try {
classPath = URLDecoder.decode(classPath, StandardCharsets.UTF_8.name());
} catch (Exception e) {
e.printStackTrace();
}
basePack = basePack.replace(".", File.separator);
String searchPath = classPath + basePack;
doPath(new File(searchPath), classPath);
return classPaths;
}
5. WebServlet.java
这是一个自定义注解,用于标注 Servlet 类,并指定其映射的 URL 路径。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebServlet {
String urlMapping() default "";
}
6. GenericServlet.java、HttpServlet.java 和 Servlet.java
这些类和接口构成了 Servlet 的基本体系结构。Servlet 接口定义了 Servlet 的生命周期方法,GenericServlet 提供了通用的实现,HttpServlet 则针对 HTTP 请求进行了特殊处理,将请求分发到 doGet 或 doPost 方法。
public interface Servlet {
void init();
void destroy();
void service(HttpServletRequest request, HttpServletResponse response);
}
public abstract class GenericServlet implements Servlet{
@Override
public void init() {}
@Override
public void destroy() {}
}
public abstract class HttpServlet extends GenericServlet {
public void service(HttpServletRequest request, HttpServletResponse response){
if(request.getMethod().equals("GET"))
{
doGet(request,response);
}
else if(request.getMethod().equals("POST"))
{
doPost(request,response);
}
}
public abstract void doGet(HttpServletRequest request,HttpServletResponse response);
public abstract void doPost(HttpServletRequest request,HttpServletResponse response);
}
7. HttpServletRequest.java 和 HttpServletResponse.java
这两个类用于封装请求和响应信息,方便在 Servlet 中进行操作。
public class HttpServletRequest {
private String method;
private String path;
// getter 和 setter 方法
}
public class HttpServletResponse {
}
三、运行效果展示
将上述代码整合后,启动 MyTomcat,它便会开始监听 8080 端口。当我们在浏览器中访问对应的路径,如 http://localhost:8080/login
或 http://localhost:8080/show
时,Tomcat 会根据请求路径找到对应的 Servlet,并调用其 doGet 或 doPost 方法来处理请求,最终返回相应的响应。
四、总结与展望
通过手写这个简易的 Tomcat,我们深入理解了 Servlet 容器的基本工作原理,包括 Socket 编程、请求解析、Servlet 的加载和调用等关键环节。虽然这个实现功能简单,但它为我们进一步学习和研究 Tomcat 的源码提供了宝贵的实践经验。
在未来的学习中,我们可以继续完善这个简易 Tomcat,添加更多的功能,如支持静态资源的访问、会话管理、过滤器和监听器等,逐步使其功能更加丰富和完善,向真正的 Tomcat 靠拢。
希望这次的分享能为大家打开 Java Web 开发底层世界的大门,让我们一起在技术的海洋中不断探索和前行!