从0-1手写tomcat

Tomcat 是一款广泛使用的 Java Web 服务器,核心职责是接收和处理 HTTP 请求、管理 Servlet 生命周期,并作为客户端与后端服务的中间层实现交互。它能解析 HTTP 协议格式,区分静态资源(如 HTML、图片)与动态资源(如 Servlet 处理的业务请求),通过规范的流程调用对应组件生成响应。
本文结合手写简易 Tomcat 的实践,梳理其核心工作流程 —— 从 Socket 通信建立连接,到请求报文解析、资源处理、响应封装,解析 Web 服务器如何基于 HTTP 协议完成客户端与服务端的高效交互,为理解 Tomcat 底层原理提供实战视角。

简易的服务器接收简易的客户端请求

单次 (单收单发)

简易服务端代码

public class MyServer {  
    public static void main(String[] args) {  
        try {  
            //创建ServerSocket => 服务器  
            ServerSocket serverSocket = new ServerSocket(9090);  
  
            System.out.println("服务器端口号启动:9090");  
  
            //等待客户端连接  阻塞  
            Socket socket = serverSocket.accept();  
  
            //读取客户端发送的消息(IO流)  
            InputStream inputStream = socket.getInputStream();  
  
            byte[] bytes = new byte[1024];  
  
            //读取  
            int len = inputStream.read(bytes);  
  
            //将读取到的字节转换成字符串输出  
            System.out.println("客户端传输的数据为" + new String(bytes, 0, len));  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
    }}

简易客户端代码

public static void main(String[] args) {
    // 主方法:程序入口,用于演示客户端Socket通信
    try{
        // 1. 创建客户端Socket对象,连接指定服务器
        // 参数1:服务器IP地址(127.0.0.1表示本地主机,即当前电脑)
        // 参数2:服务器监听的端口号(9090,需与服务器端配置一致)
        // 注意:执行此句时会发起TCP连接请求,若服务器未启动或端口未监听,会抛出连接异常
        Socket socket = new Socket("127.0.0.1",9090);

        // 2. 通过Socket获取输出流(OutputStream)
        // 输出流用于向服务器发送数据,是客户端与服务器通信的"写通道"
        OutputStream outputStream = socket.getOutputStream();

        // 3. 向服务器发送数据
        // 将字符串"你好"转换为字节数组(getBytes(),默认使用系统字符集)
        // 通过输出流的write()方法将字节数组发送给服务器
        outputStream.write("你好".getBytes());

        // 注意:此处缺少关闭资源的代码(如outputStream.close()、socket.close())
        // 实际开发中需在finally块中关闭,避免资源泄露

    }catch (Exception e){
        // 捕获并打印所有可能的异常(如连接失败、IO错误等)
        // 便于调试时查看问题原因(如服务器未启动、端口被占用等)
        e.printStackTrace();
    }
}

多次(多收多发)

客户端

public class MyClient {  
    public static void main(String[] args) {  
        Scanner input = new Scanner(System.in);  
        try{  
  
            Socket socket = new Socket("127.0.0.1",9090);  
  
            while(true){  
                OutputStream outputStream = socket.getOutputStream();  
  
                String str = input.next();  
  
                outputStream.write(str.getBytes());  
            }  
  
        }catch (Exception e){  
            e.printStackTrace();  
        }    
        }
        }

服务端

public class MyServer {  
    public static void main(String[] args) {  
        try {  
            //创建ServerSocket => 服务器  
            ServerSocket serverSocket = new ServerSocket(9090);  
  
            System.out.println("服务器端口号启动:9090");  
  
            //等待客户端连接  阻塞  
            Socket socket = serverSocket.accept();  
  
            while (true){  
                //读取客户端发送的消息(IO流)  
                InputStream inputStream = socket.getInputStream();  
  
                byte[] bytes = new byte[1024];  
  
                //读取  
                int len = inputStream.read(bytes);  
  
                //将读取到的字节转换成字符串输出  
                System.out.println("客户端传输的数据为" + new String(bytes, 0, len));  
            }  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
    }}

通过浏览器访问服务端并响应数据给浏览器

请求报文

在这里插入图片描述

浏览器访问ip+端口 服务端接收到的请求报文

在这里插入图片描述

响应报文

在这里插入图片描述

响应数据格式需要满足响应报文
例如:

在这里插入图片描述

代码范例:

在这里插入图片描述

  • 代码中两个关键头部
    1. Content-Type:text/html;charset=utf-8

      • Content-Type:指定响应内容的 “类型” 和 “编码”;
        (媒体类型)
      • text/html:表示内容是 HTML 格式(浏览器会按 HTML 规则渲染,而非纯文本);
      • image/jpegimage/png:分别对应 JPG、PNG 图片,浏览器直接渲染图像(网页图片核心类型)。
      • charset=utf-8:指定字符编码为 UTF-8,避免中文乱码(浏览器按此编码解析文字)。
    2. Content-Length:xxx

      • 表示响应体(实际内容)的字节数;
      • 作用:帮助浏览器判断 “数据是否接收完整”,避免因数据未传完导致的解析异常。

浏览器访问可获得响应的数据

在这里插入图片描述

渲染图片到浏览器

public static void main(String[] args) {  
    try {  
        //创建ServerSocket => 服务器  
        ServerSocket serverSocket = new ServerSocket(9090);  
  
        System.out.println("服务器端口号启动:9090");  
  
        //等待客户端连接  阻塞  
        Socket socket = serverSocket.accept();  
  
        //从本地读取图片  
        FileInputStream fis = new FileInputStream("/Users/ahatc/tomcat/myLearn/TomcatInIdea/handTomcat/01-socket/static/123.png");  
        //根据图片大小生成字节数组  
        byte[] bytesImg = new byte[fis.available()];  
        //读取图片数据到字节数组  
        fis.read(bytesImg);  
  
        //给浏览器响应数据  
        OutputStream outputStream = socket.getOutputStream();  
        //协议版本  
        outputStream.write("HTTP/1.1 200 OK".getBytes()); //状态行  
  
        outputStream.write("\r\n".getBytes());  //回车换行  
        //响应头部  
        outputStream.write("Content-Type: image/png; charset=utf-8".getBytes());  
  
        outputStream.write("\r\n".getBytes());  //回车换行  
  
        //响应头部  
        outputStream.write(("Content-Length:"+bytesImg.length).getBytes());  
  
        outputStream.write("\r\n".getBytes());  //回车换行  
        outputStream.write("\r\n".getBytes());  //回车换行  
  
        //响应包体  
        outputStream.write(bytesImg);  
  
  
    }catch (Exception e){  
        e.printStackTrace();  
    }}

动态渲染图片给浏览器

通过地址后缀 如 /123.png 访问不同图片

//创建ServerSocket => 服务器  
ServerSocket serverSocket = new ServerSocket(9090);  
  
System.out.println("服务器端口号启动:9090");  
  
//等待客户端连接  阻塞  
Socket socket = serverSocket.accept();  
  
//读取浏览器请求的消息  
InputStream inputStream = socket.getInputStream();  
byte[] bt = new byte[1024];  

//同时通过返回的 len1 判断是否读取完毕或实际读取了多少数据(避免数组未填满时处理无效数据)。  
//通过输入流读取数据到字节数组的核心操作  
int len = inputStream.read(bt);  

//打印  
System.out.println(new String(bt, 0, len));  
  
//System.out.println(len);  
String request = new String(bt, 0, len);  

/*  GET /123.png HTTP/1.1  
 *  Host: localhost:9090    
 * ..... 
   */
//提取 /123.png
String imgPath = request.split(" ")[1];  
  
System.out.println("选择的图片为:" + imgPath);  
  
//从本地读取图片  
FileInputStream fis = new FileInputStream("/Users/ahatc/tomcat/myLearn/TomcatInIdea/handTomcat/01-socket/static"+imgPath);  
//根据图片大小生成字节数组
byte[] bytesImg = new byte[fis.available()];  
//读取图片数据到字节数组  
fis.read(bytesImg);

//给浏览器响应数据 同上

[!error]
注意 响应不同数据时 注意修改不同的相应类型
每次修改很麻烦 下面会有优化方法

解析http请求报文(封装)

在网络编程中,“解析响应报文” 是指将接收到的二进制网络数据(即 “报文”)按照约定的协议规则,转换为程序可理解的结构化数据(如字符串、对象、键值对) 的过程

例如:
在这里插入图片描述

解析思路
在这里插入图片描述

示例代码

(根据请求报文去解析)

/**  
 * @className: MyHttpRequest  
 * @author: ahatc  
 * @date: 2025/10/18 16:54  
 * @version: 1.0  
 * @description: 解析请求消息  
 */  
  
public class MyHttpRequest {  
    /**  
	    *  GET /pages/index.html HTTP/1.1     * Host: localhost:9090     * Connection: keep-alive     * sec-ch-ua: "Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"     * ......     * ......    **/  
	//请求消息  
    private String requestMsg;  
    //请求方法  
    private String requestMethod;  
    //请求URL  
    private String requestUrl;  
    //请求模块  /login?account=zhangsan&password=123456  ==>  /login    private String requestModel;  
    //协议版本  
    private String protocol;  
    //请求行  
    private String requestLine;  
    //请求体  
    private String requestBody;  
    //请求参数  
    private HashMap<String,String> requestParam = new HashMap<>();  
    //请求头参数  
    private HashMap<String,String> requestHeaderParam = new HashMap<>();  
  
    /**  
    * @Author Ahtac  
    * @Description 构造方法  
    * @Date 17:31  
    * @Param [requestMsg]  
    * @return  
    **/  
    public MyHttpRequest(String requestMsg) {  
        this.requestMsg = requestMsg;  
  
        //初步解析  
        parse();  
    }  
    /**  
    * @Author Ahtac  
    * @Description 初步解析  
    * @Date 17:32  
    * @Param []  
    * @return void  
    **/   
     public void parse(){  
        requestLine = requestMsg.split("\r\n")[0];  
  
        //解析请求行  
        parseRequestLine();  
  
        //判断是否为POST请求  
        if("POST".equals(requestMethod)){  
            //解析请求体  
            parseRequestBody();  
        }  
  
        //解析请求头  
        parseRequestHead();  
    }  
  
  
  
    /**  
    * @Author Ahtac  
    * @Description 解析请求行  
    * @Date 17:29  
    * @Param []  
    * @return void  
    **/   
     public void parseRequestLine(){  
        String[] splitRequestLineArray = requestLine.split(" ");  
        //请求方法  
        requestMethod = splitRequestLineArray[0];  
        //请求URL  
        requestUrl = splitRequestLineArray[1];  
        //请求协议版本  
        protocol = splitRequestLineArray[2];  
  
        // /login?account=zhangsan&password=123456  获取请求模块  
        //通过  ?  分割  
        String[] splitRequestUrlArray = requestUrl.split("\\?");  
        //获取请求模块  请求模块如果是静态资源的就跟请求url没有区别  
        requestModel = splitRequestUrlArray[0];  
  
        if("GET".equals(requestMethod)){  
            if(requestUrl.contains("?")){  
                // limit:2 表示分割成两部分 以第一个?为主 (防止账号密码中带有?)  
                String[] splitRequestUrl = requestUrl.split("\\?",2);  
  
                ////accout=zhangsan&password=123456  
                String[] splitRequestParam = splitRequestUrl[1].split("&");  
                for(String param:splitRequestParam){  
  
                    String[] value = param.split("=");  
  
                    //防止只有请求参数 没有参数值  
                    if(value.length > 1){  
                        requestParam.put(value[0],value[1]);  
                    }                }            }        }        //System.out.println(requestParam);  
    }  
  
    /**  
    * @Author Ahtac  
    * @Description 解析请求头  
    * @Date 17:31  
    * @Param []  
    * @return void  
    **/    
    public void parseRequestHead(){  
        //通过两个换行回车 分割  使得 分为 请求行 请求头 + 请求体  
        // 获取第一个元素再次通过回车换行符切割 去除第一个元素 剩下全是请求头  
        String[] splitRequestBodyArray = requestMsg.split("\r\n\r\n");  
        String[] splitRequestHeadArray = splitRequestBodyArray[0].split("\r\n");  
  
        //i从1开始则跳过请求行  
        for (int i = 1; i < splitRequestHeadArray.length; i++) {  
            //通过:  进行分割  
            String[] value = splitRequestHeadArray[i].split(":");  
            if(value.length > 1){  
                requestHeaderParam.put(value[0],value[1]);  
            }  
  
        }    }  
  
    /**  
    * @Author Ahtac  
    * @Description 解析请求体 (get请求没有 post请求才有)  
    * @Date 17:30  
    * @Param []  
    * @return void  
    **/    
    public void parseRequestBody(){  
        //通过两个回车换行符 进行分割  
        String[] splitRequestMsg = requestMsg.split("\r\n\r\n");  
  
        //判断有没有请求体  
        if(splitRequestMsg.length > 1){  
            //给请求体赋值  
            requestBody = splitRequestMsg[1];  
  
            String[] splitRequestBodyParam = requestBody.split("&");  
            for(String item:splitRequestBodyParam){  
                String[] value = item.split("=");  
                if(value.length > 1){  
                    requestParam.put(value[0],value[1]);  
                }            }        }  
  
        //System.out.println("请求方法为:"+requestMethod);  
        System.out.println("请求体的参数为: " + requestParam.toString());  
    }  
    
    
   //省略get/set方法

对静态资源进行封装处理(封装)

1.获取媒体类型
2.获取文件对应的字节数据

在这里插入图片描述

封装一个静态资源处理类 统一进行处理

使用效果
在这里插入图片描述

静态资源处理器(类)代码示例

public class StaticResourceHandler {  
    /*  
     * 给一个静态资源路径  
     *   获取文件的字节数据  
     *   以及对应的媒体类型  
     * */  
    //静态资源路径  
    private String filePath;  
  
    //媒体类型  
    private String media;  
  
    //文件字节数据  
    private byte[] fileByte;  
  
    public StaticResourceHandler(String filePath) {  
        this.filePath = filePath;  
  
        //获取字节数据  
        getByte();  
        //获取媒体类型  
        getMediaType();  
  
    }  
    /**  
    * @Author Ahtac  
    * @Description 写入图片字节数据  
    * @Date 18:51   
* @Param []  
    * @return void  
    **/    
    public void getByte(){  
        FileInputStream fileInputStream = null;  
        try {  
            FileInputStream fis = new FileInputStream(filePath);  
            fileByte = new byte[fis.available()];  
            //写入字节数据  
            fis.read(fileByte);  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
    } 
    
    /**  
    * @Author Ahtac  
    * @Description 写入媒体数据类型  
    * @Date 18:51   
* @Param []  
    * @return void  
    **/   
     public void getMediaType(){  
        if (filePath.endsWith("html")) {  
            media = "text/html";  
        } else if (filePath.endsWith("css")) {  
            media = "text/css";  
        } else if (filePath.endsWith("js")) {  
            media = "application/x-javascript";  
        } else if (filePath.endsWith("png")) {  
            media = "image/png";  
        }    
    }
    
    //省略get/set代码

封装http响应报文(封装)

在这里插入图片描述

在这里插入图片描述

MyHttpResponse 类代码示例

public class MyHttpResponse {  
    //响应的客户端  
    private Socket socket;  
  
    public MyHttpResponse(Socket socket) {  
        this.socket = socket;  
    }  
    public void write(String media,byte[] fileByte){  
        try {  
  
            OutputStream outputStream = socket.getOutputStream();  
            //协议版本  
            outputStream.write("HTTP/1.1 200 OK".getBytes()); //状态行  
            outputStream.write("\r\n".getBytes());  //回车换行  
            //响应头部  
            outputStream.write(("Content-Type: "+media+"; charset=utf-8").getBytes());   //动态获取媒体类型怎么解决?  
            outputStream.write("\r\n".getBytes());  //回车换行  
            //响应头部  
            outputStream.write(("Content-Length:"+fileByte.length).getBytes());  
            outputStream.write("\r\n".getBytes());  //回车换行  
            outputStream.write("\r\n".getBytes());  //回车换行  
            //响应包体  
            outputStream.write(fileByte);  
  
            outputStream.close();  
  
        }catch (Exception e){  
            e.printStackTrace();  
        }    }}

动态资源

怎么判断 “是否是动态资源请求”

核心看请求的 “路径(URI)” 和 “业务意图”,举个例子:

  • 🌰 动态资源请求:http://127.0.0.1:9090/login?account=zhangsan&password=123456

    • 路径是/login,业务是 “处理用户登录”—— 服务器需要执行代码(验证账号密码、查数据库),实时生成 “登录成功 / 失败” 的响应,属于动态资源
  • 🌰 静态资源请求:http://127.0.0.1:9090/pages/index.html

    • 路径是/pages/index.html,业务是 “获取一个 HTML 文件”—— 服务器直接从硬盘读取index.html文件返回,属于静态资源

完整代码示例

public class MyServerDynamic {  
    public static void main(String[] args) {  
        try {  
            //创建ServerSocket => 服务器  
            ServerSocket serverSocket = new ServerSocket(9090);  
  
            System.out.println("服务器端口号启动:9090");  
  
            //等待客户端连接  阻塞  
            Socket socket = serverSocket.accept();  
  
            //读取浏览器请求的消息  
            InputStream inputStream = socket.getInputStream();  
            byte[] bt = new byte[1024];  
            int len = inputStream.read(bt);  
            String request = new String(bt, 0, len);  
            System.out.println(request);  
  
            //调用请求报文解析  
            MyHttpRequest myHttpRequest = new MyHttpRequest(request);  
            //响应数据  
            MyHttpResponse httpResponse = new MyHttpResponse(socket);  
  
            //判断有无模块  
            if ("/".equals(myHttpRequest.getRequestUrl())){  
                //访问404界面  
                String path = "/Users/ahatc/tomcat/myLearn/TomcatInIdea/handTomcat/webapps/pages/404.html";  
                //静态资源处理器  
                StaticResourceHandler staticResourceHandler = new StaticResourceHandler(path);  
                //用封装的类给浏览器响应数据  
                httpResponse.write(staticResourceHandler.getMedia(), staticResourceHandler.getFileByte());  
            }  
            //判断是静态资源还是动态资源  
            if (myHttpRequest.getRequestModel().contains(".")){  
                //是静态资源  
  
                //拼接完整路径  
                String path = "/Users/ahatc/tomcat/myLearn/TomcatInIdea/handTomcat/webapps"+myHttpRequest.getRequestUrl();  
                //静态资源处理器  
                StaticResourceHandler staticResourceHandler = new StaticResourceHandler(path);  
                //用封装的类给浏览器响应数据  
                httpResponse.write(staticResourceHandler.getMedia(), staticResourceHandler.getFileByte());  
            }else {  
                //动态资源  
                System.out.println("动态请求"+myHttpRequest.getRequestModel());  
  
                switch (myHttpRequest.getRequestModel()){  
//                    模拟登陆  
                    case "/login":  
                        //JDBC 连接  
                        //加载驱动  
                        Class.forName("com.mysql.cj.jdbc.Driver");  
                        //建立连接  
                        Connection connection = DriverManager.getConnection("jdbc:mysql://159.75.98.28:3306/OMO_learn",  
                                "root",  
                                "password");  
                        //写sql  
                        String sql = "select * from user where account=? and password=?";  
                        //创建执行者对象  
                        PreparedStatement preparedStatement = connection.prepareStatement(sql);  
                        preparedStatement.setString(1,myHttpRequest.getValueByKey("account"));  
                        preparedStatement.setString(2,myHttpRequest.getValueByKey("password"));  
                        //结果集  
                        ResultSet resultSet = preparedStatement.executeQuery();  
                        if (resultSet.next()){  
                            System.out.println("登陆");  
                            httpResponse.write("登陆成功".getBytes());  
                        }else {  
                            System.out.println("登陆");  
                            httpResponse.write("登陆失败".getBytes());  
                        }  
                        preparedStatement.close();  
                        connection.close();  
                        resultSet.close();  
  
  
                        break;  
                    case "/enroll":  
                        System.out.println("注册");  
                        httpResponse.write("注册成功".getBytes());// 动态请求 基本 是“text/html” 所以这里用到了方法的重载 只传字节数据  
                        break;  
                    default:  
                        String path = "/Users/ahatc/tomcat/myLearn/TomcatInIdea/handTomcat/webapps/pages/404.html";  
                        //静态资源处理器  
                        StaticResourceHandler staticResourceHandler = new StaticResourceHandler(path);  
                        //用封装的类给浏览器响应数据  
                        httpResponse.write(staticResourceHandler.getFileByte());  
                }            }  
  
  
  
  
        }catch (Exception e){  
            e.printStackTrace();  
        }    }}

使用servlet(自己写)优化

这里注意使用到了继承和多态和封装. 后续涉及反射

详细代码可查看gitee

1.将重复功能代码抽到servlet

如登陆功能需要重复写jdbc

我们将它抽取出来
代码示例

public class LoginServlet extends BaseServlet {  
  
    @Override  
    public void doGet(MyHttpRequest myHttpRequest, MyHttpResponse httpResponse){  
        try {  
            //JDBC 连接  
            //加载驱动  
            Class.forName("com.mysql.cj.jdbc.Driver");  
            //建立连接  
            Connection connection = DriverManager.getConnection("jdbc:mysql://159.75.98.28:3306/OMO_learn",  
                    "root",  
                    "password");  
            //写sql  
            String sql = "select * from user where account=? and password=?";  
            //创建执行者对象  
            PreparedStatement preparedStatement = connection.prepareStatement(sql);  
            preparedStatement.setString(1,myHttpRequest.getValueByKey("account"));  
            preparedStatement.setString(2,myHttpRequest.getValueByKey("password"));  
            //结果集  
            ResultSet resultSet = preparedStatement.executeQuery();  
            if (resultSet.next()){  
                System.out.println("登陆");  
                httpResponse.write("登陆成功".getBytes());  
            }else {  
                System.out.println("登陆");  
                httpResponse.write("登陆失败".getBytes());  
            }  
            preparedStatement.close();  
            connection.close();  
            resultSet.close();  
        }catch (Exception e){  
            e.printStackTrace();  
        }    }  
    @Override  
    public void doPost(MyHttpRequest myHttpRequest, MyHttpResponse httpResponse){  
        doGet(myHttpRequest,httpResponse);  
    }}

2.使用继承优化需要重复判断get||post请求

所有servlet的父类 baseServlet

代码示例

public abstract class BaseServlet {  
  
    abstract void doGet(MyHttpRequest myHttpRequest, MyHttpResponse httpResponse);  
  
    abstract void doPost(MyHttpRequest myHttpRequest, MyHttpResponse httpResponse);  
  
    /**  
    * @Author Ahtac  
    * @Description 方法判断  
    * @Date 20:12  
    * @Param [myHttpRequest, httpResponse]  
    * @return void  
    **/    public void requestMethodHander(MyHttpRequest myHttpRequest, MyHttpResponse httpResponse){  
        if("GET".equals(myHttpRequest.getRequestMethod())){  
            doGet(myHttpRequest,httpResponse);  
        }else if("POST".equals(myHttpRequest.getRequestMethod())){  
            doPost(myHttpRequest,httpResponse);  
        }    }
    }

使用效果

在这里插入图片描述

这里还存在一个问题 使用switch-case语句 若有 一百个功能需写一百个case
所以 我们继续通过反射进行优化

通过反射继续优化servlet

在这里插入图片描述

解决 TODO (配置文件方式)

通过一个配置文件去制定不同模块名称对应的servlet
并通过一个配置类读取配置文件 (后续会使用注解的方式)

配置文件

在这里插入图片描述

配置类代码示例
public class ServletUtils {  
    private static HashMap<String,String> hashMap = new HashMap<>();  
    static {  
        try{  
            Properties p = new Properties();  
            p.load(new FileInputStream("config/servlet.properties"));  
  
            //获取配置文件的所有key  
            Set<Object> keys = p.keySet();  
  
            //遍历set  
            Iterator it = keys.iterator();  
            while (it.hasNext()) {  
                String key = (String) it.next();  
                String value = p.getProperty(key);  
                hashMap.put(key,value);  
  
            }  
        }catch (Exception e){  
            throw new RuntimeException(e);  
        }    }  
    public static String getValueByKey(String key){  
        return hashMap.get(key);  
    }
    }
使用

在这里插入图片描述

继续优化静态资源

[!info]
前面 我们通过判断模块中是否包含 “ . ” 来区分静态资源 但是实际情况 可能在动态访问中携带有 “ . ” 如账号密码中可能携带

在这里插入图片描述

我们通过一个 DefaultServlet 类 在前面配置类读取配置文件的 servlet类名为空时 就有可能是静态资源 也有 可能是动态资源但不存在(此时访问404界面)

  1. 是静态资源的处理

在这里插入图片描述

在这里插入图片描述

  1. 是动态资源的处理
    在这里插入图片描述

自定义注解

通过注解的方式实现动态资源

通过一个自定义注解 去接收servlet的 访问路径
(解决TODO /login时拿到loginServlet )

@Target(ElementType.TYPE)  // 仅作用于类
@Retention(RetentionPolicy.RUNTIME)  // 运行时保留,允许反射读取

public @interface ServletMapping {
    String value() default "";  // 存储URL映射路径
}

[!note]

  • 作用:标记 Servlet 类对应的访问路径
  • 使用示例:@ServletMapping("/login") 表示该 Servlet 处理/login路径的请求

在通过一个工具类的解析 根据对应的路径 通过反射获取对应的servlet类 存如hashmap

这里使用了别人封装好的三个jar包 解决包包扫描
在这里插入图片描述

代码如下

/**  
 * @className: ServletScannerUtils  
 * @author: ahatc  
 * @date: 2025/11/1 10:51  
 * @version: 1.0  
 * @description: 解析servletMapping注解  
 */  
  
public class ServletScannerUtils {  
  
    private static HashMap<String,BaseServlet> servletMap = new HashMap<>();  
  
    static {  
        //通过现成的jir包  
        //Reflections 扫描文件  
        Reflections reflections = new Reflections("com.cykj.page07.servlet");  
  
        //判断类上添加的ServletMapping注解  的对象 并获取  
        Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(ServletMapping.class);  
  
        //创建  
        try {  
            //创建迭代器  
            Iterator<Class<?>> iterator = typesAnnotatedWith.iterator();  
            while (iterator.hasNext()) {  
                //单个类对象   (反射)
                Class<?> clazz = iterator.next();  
                //获取对象的servletmapping注解  
                ServletMapping annotation = clazz.getAnnotation(ServletMapping.class);  
                //获取注解的值  
                String key = annotation.value();  
                //创建对象  
                BaseServlet o = (BaseServlet) clazz.newInstance();  
  
                servletMap.put(key, o);  
            }        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }    }  
        
        
    public static com.cykj.page07.servlet.BaseServlet getServletByRequestModel(String RequestModel){  
        return servletMap.get(RequestModel);  
    }    
    }

优化效果如下

在这里插入图片描述

拆分三层

数据访问 - 业务逻辑 - 请求处理

springMVC中会解释 serlvet–>controller

在这里插入图片描述

池化思想

程序启动先加载一些连接 放到池子里面 如果需要去池子拿一个连接 用完把连接还回去

数据库连接池(德鲁伊)

ArrayList ==>查询
LinkedList ==> 增删
所以选用LinkedList

我们用现成的

德鲁伊数据库连接池
在这里插入图片描述

新建工具类 DruidDataSourceUtils
代码示例

public class DruidDataSourceUtils {  
    private static DruidDataSource druidDataSource;  
  
    private DruidDataSourceUtils() {}  
  
    static {  
        try {  
            //加载配置文件获取配置信息  
            Properties properties = new Properties();  
            properties.load(new FileInputStream("config/druid.properties"));  
            //根据配置信息创建德鲁伊连接池  
            druidDataSource = new DruidDataSource();  
            druidDataSource.configFromPropety(properties);  
        }catch (Exception e){  
            e.printStackTrace();  
        }    }    //供外部拿取连接池中的连接  
    public static DruidPooledConnection getDruidDataSource() {  
        try {  
            return druidDataSource.getConnection();  
        } catch (SQLException e) {  
            throw new RuntimeException(e);  
        }    }    //归还连接  
    public  static void closeDruidDataSource(DruidPooledConnection druidPooledConnection) {  
        try {  
            //归还连接  
            if (druidPooledConnection != null) {  
                druidPooledConnection.close();  
            }        } catch (SQLException e) {  
            throw new RuntimeException(e);  
        }    }  
    //释放资源  
    public static void releaseOtherSource(PreparedStatement  preparedStatement, ResultSet resultSet) {  
        try {  
            if (preparedStatement != null) {  
                preparedStatement.close();  
            }        }catch (SQLException e){  
            throw new RuntimeException(e);  
        }  
        try {  
            if (resultSet != null) {  
                resultSet.close();  
            }        }catch (SQLException e){  
            throw new RuntimeException(e);  
        }    }}

最后连接数据库时

在这里插入图片描述

前端原始的请求发送

在这里插入图片描述

优化

File工具类 自动获取媒体类型

StaticResourceHandler中,使用 Java 的Files工具类(Files.probeContentType方法)自动获取文件的媒体类型,目的是快速、准确地识别文件的类型(如图片、视频、文本等),为静态资源的处理(如响应头设置、格式校验)提供依据,避免手动判断文件类型的繁琐和误差。

在这里插入图片描述

解决post消息接收不全

通过请求报文中 content-length 来判断 post请求时携带的长度 通过循环 去接收

在这里插入图片描述

在这里插入图片描述

使用单例设计模式

通过单例设计模式解决每个 servlet 都需要重复创建 业务层实例

在这里插入图片描述

/**  
 * 单例设计模式 饿汉模式  
**/  

//2.自己创建一个私有静态实例  
private static UserServiceIml instance = new UserServiceIml();  
  
//单例设计模式 
//1。私有构造方法 不让外界创造实例  
private UserServiceIml() {}  
  
//3.提供共有静态的访问方法  
public static UserServiceIml getInstance() {  
    return instance;  
}



/**  
 * 单例设计模式 懒汉模式  
 **/  
 
//2.自己定义一个私有静态实例(先不创建)  
private static UserServiceIml instance;  
  
//单例设计模式
 //1。私有构造方法 不让外界创造实例  
private UserServiceIml() {}  
  
//3.提供共有静态的访问方法  
public static UserServiceIml getInstance() {  
    //用到才创建  
    if (instance == null) {  
        instance = new UserServiceIml();  
    }    return instance;  
}

使用工厂设计模式

为了解耦 “对象创建” 和 “对象使用”,避免UserServiceIml直接依赖UserDaoIml的具体实现,让代码更灵活、易维护。

DaoFactory(工厂)负责创建UserDao对象UserServiceIml(使用方)只需通过工厂获取UserDao并使用,无需关心创建细节。

具体查看 工厂设计模式

在这里插入图片描述

在这里插入图片描述

多线程

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值