在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 {@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) {// 处理 GET 请求的逻辑}@Overridepublic 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{@Overridepublic void init() {}@Overridepublic 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 开发底层世界的大门,让我们一起在技术的海洋中不断探索和前行!