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+端口 服务端接收到的请求报文

响应报文

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

代码范例:

- 代码中两个关键头部:
-
Content-Type:text/html;charset=utf-8Content-Type:指定响应内容的 “类型” 和 “编码”;
(媒体类型)text/html:表示内容是 HTML 格式(浏览器会按 HTML 规则渲染,而非纯文本);image/jpeg、image/png:分别对应 JPG、PNG 图片,浏览器直接渲染图像(网页图片核心类型)。charset=utf-8:指定字符编码为 UTF-8,避免中文乱码(浏览器按此编码解析文字)。
-
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界面)
- 是静态资源的处理


- 是动态资源的处理

通过注解的方式实现动态资源
通过一个自定义注解 去接收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并使用,无需关心创建细节。
具体查看 工厂设计模式


951

被折叠的 条评论
为什么被折叠?



