从零自研 Java Web 框架:30 个核心问题系统拆解全流程
告别黑箱,开源项目真实可运行
30 个核心问题 + 3 万行自研代码,从 CPU 指令到手写 Tomcat、IoC、Mapper,彻底打通全链路
作者介绍
CodeStats,资深底层技术爱好者与实战派架构师,WWAIC(全周 AI 编程)范式创始人。专注计算机体系结构、操作系统内核、Java 虚拟机实现原理与自研框架落地。长期在 CSDN 分享硬核技术文章,手写 IoC 容器、嵌入式 Tomcat、MyBatis 风格 Mapper、连接池及代码分析引擎,致力于用通俗语言讲透 Java 程序从 CPU 指令到 Web 框架的完整运行逻辑。
💡 个人代表项目 CodeStats —— 一个完全自研、零商业依赖的 Java Web 平台,100% 代码由 AI 辅助生成,一周内从零交付可运行系统。本文所有示例均取自该项目真实源码。
📑 目录(30 个问题)
-
CPU 如何执行一条指令?
-
中断如何实现“并发”假象?
-
用户态与内核态如何切换?
-
进程和线程的本质区别是什么?为什么线程切换更轻量?
-
如何手写一个 IoC 容器?
-
如何手写一个 MVC 框架?
-
如何手写嵌入式 Tomcat?
-
如何手写 MyBatis 风格的 Mapper?
-
如何手写数据库连接池?
-
如何手写 JdbcTemplate?
-
如何手写日志框架?
-
如何手写缓存框架?
-
如何手写 Stream 惰性求值?
-
如何手写类加载隔离?
-
如何手写分页插件?
-
如何手写 Session 管理?
-
如何手写 Filter 链?
-
如何手写静态资源服务器?
-
如何手写文件上传解析器?
-
如何手写 JSON 序列化器?
-
代码分析引擎(Pipeline 模式)
-
依赖分析(词法扫描 + 循环检测)
-
NIO 如何解决 C10K 问题?
-
事务管理器如何实现?
-
AOP 动态代理的核心是什么?
-
Spring Boot 自动配置如何工作?
-
Feign 声明式 HTTP 客户端如何模拟?
-
一级/二级缓存如何设计?
-
WWAIC 范式是什么?
-
全链路自洽思维如何建立?
1. CPU 如何执行一条指令?
一句话核心原理
冯·诺依曼架构:程序 = 内存中连续排列的指令。CPU 无限循环:取指 → 译码 → 执行 → 写回,PC(程序计数器)指向下一条指令。
设计思路
设计一个指令模拟器:byte[] memory 存指令,int pc 做计数器,switch(opcode) 执行。指令长度可变(1~3 字节),PC 累加当前指令长度。
CodeStats 中的体现
Connector 的事件循环是同样的无限循环模型:
java
// Connector.java
while (running) {
Socket socket = serverSocket.accept(); // 阻塞等待“事件”
threadPool.submit(() -> process(socket)); // 处理“事件”
}
2. 中断如何实现“并发”假象?
一句话核心原理
硬件定时器每隔 1ms 发送中断信号。CPU 保存当前进程的 PC 和寄存器,切换到内核调度器,再恢复另一个进程的现场。每秒切换上千次,我们以为“同时运行”。
设计思路
设计协作式调度器:一个定时器线程每隔 10ms 主动调用 yield(),切换当前运行的 Runnable。
CodeStats 中的体现
线程池中的多个线程由 OS 内核调度,Java 线程 1:1 映射到 OS 线程:
java
threadPool.submit(() -> process(socket)); // 操作系统自动中断当前线程,切换到另一个线程
3. 用户态与内核态如何切换?
一句话核心原理
CPU 特权级:Ring 0(内核态) 可执行所有指令,Ring 3(用户态) 受限。用户程序想读文件,执行 syscall 指令,CPU 切换到 Ring 0,跳转到内核函数。
设计思路
设计一个系统调用门:定义 trap(int sysno, ...),根据 sysno 调用对应的内核函数。
CodeStats 中的体现
JdbcTemplate 底层调用 DriverManager.getConnection(),最终触发 Socket 的 read 系统调用。
4. 进程和线程的本质区别是什么?为什么线程切换更轻量?
一句话核心原理
-
进程:拥有独立页表,切换需换页表 → TLB 全部失效 → 开销大
-
线程:共享进程页表,只需保存私有栈和寄存器 → TLB 保持有效 → 开销小
设计思路
设计“轻量级线程”模型:多个任务共享同一个内存视图,各自持有独立的栈。
CodeStats 中的体现
连接池中的多个线程共享 idleConnections(堆对象),但每个线程有自己的栈帧:
java
Connection conn = idleConnections.poll(); // 多个线程同时调用,线程安全
5. 如何手写一个 IoC 容器?
一句话核心原理
三步:扫描包找出 @Component 等注解类 → 创建 BeanDefinition → 实例化 Bean,通过反射注入 @Autowired 字段。
设计思路
-
BeanDefinition:beanClassName、scope、lazyInit -
BeanFactory:singletonObjects缓存 +getBean() -
注入:遍历字段,
field.set(bean, dependency)
CodeStats 实现代码
java
// DefaultListableBeanFactory.java
protected void populateBean(Object bean, BeanDefinition bd) throws Exception {
for (Field field : bean.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Object dependency = resolveDependency(field, bean);
field.set(bean, dependency);
}
}
}
// 多候选 Bean 处理:@Qualifier 或字段名匹配,或 @Primary
protected Object resolveDependency(Field field, Object bean) throws Exception {
Map<String, ?> candidates = getBeansOfType(field.getType());
if (candidates.size() == 1) return candidates.values().iterator().next();
Qualifier qualifier = field.getAnnotation(Qualifier.class);
String targetBeanName = qualifier != null ? qualifier.value() : field.getName();
if (candidates.containsKey(targetBeanName)) return candidates.get(targetBeanName);
for (Object candidate : candidates.values()) {
if (candidate.getClass().isAnnotationPresent(Primary.class)) return candidate;
}
throw new Exception("Multiple beans found, use @Qualifier or @Primary");
}
6. 如何手写一个 MVC 框架?
一句话核心原理
DispatcherServlet 作为前端控制器:初始化扫描 @Controller 收集映射;请求到达时匹配路径,解析参数,反射调用方法;若方法有 @ResponseBody,将返回值 JSON 序列化后写入响应。
设计思路
-
HandlerMapping:
path→(controller, method, isResponseBody) -
ParamBinder:支持
@RequestParam、@PathVariable、@RequestBody及类型转换 -
静态资源处理:路径安全校验后从
webapps/读取文件
CodeStats 实现代码
java
// DispatcherServlet.java
private void processRequest(HttpServletRequest req, HttpServletResponse res) {
for (HandlerMapping hm : handlerMappings) {
if (req.getUri().matches(hm.regex)) {
Map<String, String> pathVars = extractPathVars(matcher, hm.varNames);
Object[] args = ParamBinder.resolveParameters(hm.method, req, res, pathVars);
Object result = hm.method.invoke(hm.controller, args);
if (hm.isResponseBody) {
res.setContentType("application/json; charset=utf-8");
res.write(toJson(result));
}
res.send();
return;
}
}
serveStaticFile(uri, res);
}
7. 如何手写嵌入式 Tomcat?
一句话核心原理
Tomcat 核心架构:Server → Service → Connector + Engine → Host → Context → Wrapper。
-
Connector 监听端口,解析 HTTP
-
Pipeline-Valve 责任链处理请求
-
Wrapper 包装并调用 Servlet
设计思路
-
Connector:
ServerSocket+ 线程池,解析请求行/Header/Body -
Pipeline:
List<Valve>+ 递归调用invokeNext() -
Context:维护
servletMappings(URL → servletName)
CodeStats 实现代码
java
// Connector.java
public void start() {
serverSocket = new ServerSocket(port);
while (running) {
Socket socket = serverSocket.accept();
threadPool.submit(() -> {
Request req = new Request(socket.getInputStream());
req.parse();
Response res = new Response(socket.getOutputStream());
engine.getPipeline().invoke(req, res);
});
}
}
// SimplePipeline.java
public void invoke(Request request, Response response) throws Exception {
new ValveChainImpl().invokeNext(request, response);
}
private class ValveChainImpl implements Valve.ValveChain {
private int index = 0;
public void invokeNext(Request req, Response resp) throws Exception {
if (index < valves.size())
valves.get(index++).invoke(req, resp, this);
else if (basic != null)
basic.invoke(req, resp, this);
else
container.getPipeline().invoke(req, resp);
}
}
8. 如何手写 MyBatis 风格的 Mapper?
一句话核心原理
@Mapper 标记接口,启动时扫描并为每个接口创建 JDK 动态代理。MapperProxy.invoke() 中:获取 @Select 注解的 SQL → 解析 #{xxx} 为 ? → 构建参数 → 调用 JdbcTemplate 执行 → 结果映射。
设计思路
-
SqlParser:正则替换
#{xxx}为?,提取参数名列表 -
参数构建:从
@Param或方法参数对象中获取值 -
结果映射:根据返回类型(
List<T>/T/int)选择不同执行方法
CodeStats 实现代码
java
// MapperProxy.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Select select = method.getAnnotation(Select.class);
String sql = select.value();
ParsedSql parsed = SqlParser.parse(sql); // "SELECT * FROM user WHERE id = ?", ["id"]
Object[] paramValues = buildParamValues(method, args, parsed.getParamNames());
Class<?> returnType = method.getReturnType();
if (returnType == List.class) {
Class<?> elementType = extractElementType(method);
return jdbcTemplate.queryForList(parsed.getSql(), elementType, paramValues);
} else {
return jdbcTemplate.queryForObject(parsed.getSql(), returnType, paramValues);
}
}
9. 如何手写数据库连接池?
一句话核心原理
核心三组件:空闲队列(BlockingQueue)、限流器(Semaphore)、连接验证(validationQuery)。通过代理覆盖 close() 实现归还而非真正关闭。
设计思路
-
初始化创建
minIdle个连接放入队列 -
getConnection():获取 Semaphore 许可 → 从队列poll()(空则新建) → 验证无效则递归重试 → 返回PooledConnection包装 -
归还:若队列未满则放回,否则关闭;释放 Semaphore
CodeStats 实现代码
java
// SimpleDataSource.java
public Connection getConnection() throws SQLException {
semaphore.acquire();
Connection conn = idleConnections.poll();
if (conn == null) {
conn = DriverManager.getConnection(url, username, password);
}
if (!isValid(conn)) {
conn.close();
return getConnection();
}
return new PooledConnection(conn, this);
}
void returnConnection(Connection conn) {
if (idleConnections.size() < maxIdle) {
idleConnections.offer(conn);
} else {
conn.close();
}
semaphore.release();
}
10. 如何手写 JdbcTemplate?
一句话核心原理
模板方法模式:固定 SQL 执行骨架,变化的部分(结果映射)通过回调接口 RowMapper 开放。
设计思路
-
query(sql, rowMapper, args):获取连接 → 创建PreparedStatement→ 绑定参数 → 执行 → 遍历ResultSet调用rowMapper.mapRow() -
queryForObject:单结果,空则 null,超过一条抛异常 -
自动映射:下划线转驼峰 +
TypeConverter类型转换
CodeStats 实现代码
java
// JdbcTemplate.java
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) {
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
for (int i = 0; i < args.length; i++) ps.setObject(i + 1, args[i]);
ResultSet rs = ps.executeQuery();
List<T> results = new ArrayList<>();
while (rs.next()) {
results.add(rowMapper.mapRow(rs));
}
return results;
}
}
public static <T> RowMapper<T> beanRowMapper(Class<T> clazz) {
return rs -> {
T bean = clazz.getDeclaredConstructor().newInstance();
ResultSetMetaData meta = rs.getMetaData();
for (int i = 1; i <= meta.getColumnCount(); i++) {
String columnName = meta.getColumnLabel(i);
String propertyName = underscoreToCamel(columnName); // user_name → userName
Object value = rs.getObject(i);
setProperty(bean, propertyName, value); // 反射 + TypeConverter
}
return bean;
};
}
11. 如何手写日志框架?
核心原理
Appender + Layout + Filter,模板方法 + 策略模式。
-
Appender:输出目的地(控制台、文件、滚动文件、异步)
-
Layout:格式化 LogEvent(PatternLayout 支持
%d、%level、%msg、%ex) -
Filter:责任链过滤(如
LevelRangeFilter)
设计思路
LoggerFactory 从 XML 加载配置,Logger 门面提供 log.info() 等方法,Appender.doAppend() 通过模板方法调用子类实现的 append()。
CodeStats 关键类
LoggerFactory, ConsoleAppender, FileAppender, RollingFileAppender, AsyncAppender, PatternLayout, LevelRangeFilter
12. 如何手写缓存框架?
核心原理
ConcurrentHashMap + TTL + 定期清理。
-
每个缓存条目记录
expireTime -
get()时惰性删除过期条目 -
后台线程定期清理全量过期条目
设计思路
统一 Cache<K,V> 接口,支持 put(key, value, ttl)、get(key)、computeIfAbsent。实现分为 LocalCache(基于 ConcurrentHashMap)和 CaffeineCache(可选)。
CodeStats 关键类
Cache, LocalCache, CacheFactory, CacheManager
13. 如何手写 Stream 惰性求值?
核心原理
流水线模式:中间操作不执行,只记录 PipelineStage;终端操作触发遍历,每个元素依次经过所有 stage。
设计思路
-
MyStream接口:filter,map,forEach -
PipelineStage:process(input, sink)函数式接口 -
构建阶段:每次中间操作返回新
MyStreamImpl,合并 stage 链 -
执行阶段:
forEach时从最外层 stage 开始,对源集合每个元素递归调用
CodeStats 关键类
MyStreamImpl, MyPipelineStage
14. 如何手写类加载隔离?
核心原理
打破双亲委派模型:优先从当前 Web 应用加载类,再委托父加载器。
Tomcat 的 WebappClassLoader 先 findClass() 再 super.loadClass()。
设计思路
继承 URLClassLoader,重写 loadClass():
-
已加载类直接返回
-
系统类(
java.、javax.)委托父加载器 -
优先
findClass()从当前应用WEB-INF/classes和WEB-INF/lib加载 -
未找到再委托父加载器
CodeStats 实现代码
WebappClassLoader.java(见项目源码)
15. 如何手写分页插件?
核心原理
MyBatis 插件机制通过 ThreadLocal 传递分页参数,在 Executor 拦截器中改写 SQL,追加 LIMIT / ROWNUM。
设计思路
PageHelper.startPage(page, size) → 存入 ThreadLocal → 拦截 Executor.query() → 解析原 SQL,生成 COUNT 和分页 SQL → 执行并清理 ThreadLocal。
CodeStats 实现代码
DynamicSqlExecutor.executePageQuery() 直接支持分页,未用插件,但原理相同。
16. 如何手写 Session 管理?
核心原理
Cookie 中存放 JSESSIONID,服务器端 Map<String, HttpSession> 存储会话数据。Request.getSession() 根据 Cookie 中的 JSESSIONID 查找或创建新 Session。
设计思路
-
SessionManager:静态
ConcurrentHashMap+ 原子 ID 生成器 -
HttpSessionImpl:存储属性、创建时间、最后访问时间
-
Request 解析 Cookie,Response 添加
Set-Cookie头
CodeStats 关键类
SessionManager, HttpSessionImpl
17. 如何手写 Filter 链?
核心原理
责任链模式:ApplicationFilterChain 维护 Filter 列表和当前索引,递归调用 doFilter()。
设计思路
FilterChain.doFilter(request, response) 中:若还有下一个 Filter 则调用它,否则调用 servlet.service()。每个 Filter 可在调用前后执行自定义逻辑。
CodeStats 实现代码
ApplicationFilterChain.java
18. 如何手写静态资源服务器?
核心原理
路径安全校验(防止 ../ 越权),MIME 类型映射,直接通过 Files.copy() 输出文件流。
设计思路
serveStaticFile(uri, res):
-
将 URI 转换为文件系统路径(以
webapps/为根) -
调用
Path.normalize(),检查是否仍在根目录下 -
根据扩展名设置
Content-Type -
Files.copy(file, outputStream)
CodeStats 实现代码
DispatcherServlet.serveStaticFile()
19. 如何手写文件上传解析器?
核心原理
解析 multipart/form-data 请求体,查找边界字符串,读取每个 part 的头部和内容,将文件内容写入临时文件,普通字段存入 Map。
设计思路
-
MultipartResolver:-
从
Content-Type提取boundary -
使用
BoundaryFinder高效匹配边界 -
逐个 part 解析:读取头部 → 判断是普通字段还是文件 → 写入临时文件或直接存值
-
-
实现
AutoCloseable,请求结束后删除临时文件
CodeStats 关键类
MultipartResolver, BoundaryFinder, FileItem
20. 如何手写 JSON 序列化器?
核心原理
反射遍历对象字段,递归序列化,用 IdentityHashMap 检测循环引用,忽略 static 和 transient 字段。
设计思路
JsonUtil.toJson(obj):
-
null→"null" -
String→ 转义引号、换行等 -
Number/Boolean→ 直接toString() -
Map→ 序列化为{...} -
Collection/ 数组 → 序列化为[...] -
其他 JavaBean → 反射获取所有字段(包括私有),序列化为
{"fieldName": value}
CodeStats 实现代码
JsonUtil.java
21. 代码分析引擎(Pipeline 模式)
核心原理
Pipeline 模式将分析流程拆分为多个独立 Stage:CollectStage → ParallelAnalyzeStage → AggregateStage → OutputStage
设计思路
-
CollectStage:递归扫描目录,过滤扩展名和排除路径
-
ParallelAnalyzeStage:多线程并行分析每个文件,统计代码行/注释行/空行/复杂度
-
AggregateStage:按指定字段排序,汇总语言分布
-
OutputStage:输出 JSON/CSV/Console 格式
CodeStats 关键类
Pipeline, CollectStage, ParallelAnalyzeStage, AggregateStage, OutputStage
22. 依赖分析(词法扫描 + 循环检测)
核心原理
通过词法扫描 Java 源码提取包名、导入、类声明、字段类型、方法返回类型/参数类型,通过同包、导入、java.lang 隐式导入规则解析全限定名。用 Tarjan 算法检测循环依赖。
设计思路
-
SimpleJavaLexer:扫描 Token,跳过注释和字符串
-
JavaDependencyExtractor:解析包名、导入、类声明,收集类型引用
-
TypeNameResolver:将简单名解析为全限定名
-
DependencyGraph:使用 Tarjan 算法找出强连通分量(循环依赖组)
CodeStats 关键类
JavaDependencyExtractor, SimpleJavaLexer, TypeNameResolver, DependencyGraph
23. NIO 如何解决 C10K 问题?
核心原理
使用 Selector 单线程管理成千上万个非阻塞 Channel,只在有事件(可读/可写/连接)时才处理,避免了 BIO 中每个线程阻塞在 read() 上。
设计思路
-
ServerSocketChannel设置为非阻塞,注册OP_ACCEPT -
Selector.select()阻塞等待事件 -
新连接注册
OP_READ,数据到来时提交给线程池处理 -
线程池只负责业务逻辑,不阻塞 I/O
CodeStats 实现代码
Connector 的 NIO 版本(项目中有两种实现,NIO 版本见源码)
24. 事务管理器如何实现?
核心原理
AOP 代理 + Connection.setAutoCommit(false)。将事务注解的方法通过动态代理包裹,在方法执行前开启事务,执行后提交,异常时回滚。
设计思路
自定义 @Transactional 注解,通过 BeanPostProcessor 为带有该注解的 Bean 生成代理,代理中管理 Connection 的事务状态。
CodeStats 实现代码
预留接口,完整实现可参考 TransactionProxy 示例类。
25. AOP 动态代理的核心是什么?
核心原理
JDK 动态代理(基于接口)或 CGLIB(基于子类)在运行时生成代理对象,在代理中插入横切逻辑(如日志、事务)。
设计思路
InvocationHandler.invoke() 中:在 method.invoke() 前后执行增强代码。结合 BeanPostProcessor 可在 Bean 初始化后替换为代理对象。
CodeStats 关键类
MapperProxy(类比 AOP 的环绕通知),BeanPostProcessor(预留扩展)
26. Spring Boot 自动配置如何工作?
核心原理
@EnableAutoConfiguration 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 中配置的 AutoConfiguration 类,这些类使用 @Conditional 条件注解按需生效。
设计思路
CodeStats 未完整实现自动配置,但通过 ConfigLoader 读取 application.properties 提供了类似的效果。可扩展为扫描 META-INF/services 加载配置类。
CodeStats 关键类
ConfigLoader
27. Feign 声明式 HTTP 客户端如何模拟?
核心原理
与 MyBatis Mapper 完全一致:通过 @FeignClient 标记接口,FeignClientFactoryBean 创建 JDK 动态代理,代理中将方法调用转换为 HTTP 请求(拼接 URL、填充参数、执行请求并解码响应)。
设计思路
参考 MapperProxy 的实现,将 SQL 执行替换为 HttpURLConnection 或 HttpClient 调用。
CodeStats 关键类
MapperProxy(可直接修改为 HTTP 代理)
28. 一级/二级缓存如何设计?
核心原理
-
一级缓存:
SqlSession级别的PerpetualCache(HashMap),会话关闭或执行增删改时清空 -
二级缓存:Mapper 级别的缓存,跨会话共享,实体类需实现
Serializable
设计思路
Executor 在查询前先检查一级缓存,再检查二级缓存;update 操作清空相关缓存。
CodeStats 关键类
PerpetualCache(预留接口)
29. WWAIC 范式是什么?
核心原理
Whole-Week AI Coding:开发者在一周内将完整项目的需求、架构设计、模块划分、技术栈约束一次性提交给 AI,由 AI 生成可运行的完整系统。CodeStats 是该范式的首个实证项目。
设计思路
人负责顶层设计(分层、接口、核心类),AI 负责生成实现代码。开发者的角色从代码编写者转变为架构设计者 + 集成验证者。
CodeStats 关键类
整个 CodeStats 项目就是 WWAIC 的产物。
30. 全链路自洽思维如何建立?
核心原理
从硬件(CPU 指令)到操作系统(进程线程、内存管理),再到 JVM(类加载、栈帧、GC),最后到框架(IoC、MVC、ORM、Tomcat),所有技术都遵循“简单规则层层组合”的原则。
设计思路
以 30 个核心问题为主线,手写每个关键模块的迷你实现,从而建立从底层到上层的完整知识图谱。
最终检验
✅ 能在一张白纸上画出从 Java 代码到 CPU 指令的全链路执行流程,并能用自研框架跑通一个完整的 Web 应用。
开源项目:CodeStats
-
GitHub / Gitee:https://gitee.com/zhouzuoli/code-stats.git
一个完全自研的 Java Web 框架,包含:
-
✅ 自研 IoC 容器
-
✅ 自研 MVC 框架
-
✅ 自研嵌入式 Tomcat
-
✅ 自研 MyBatis 风格 Mapper
-
✅ 自研数据库连接池
-
✅ 自研 JdbcTemplate
-
✅ 自研日志框架
-
✅ 自研缓存框架
-
✅ 代码分析引擎(Pipeline 模式)
-
✅ 前端管理界面(数据库客户端、文件管理、AI 助手)
📦 全部代码 3 万行,100% 可运行,无黑盒。欢迎 Star、Clone、学习、扩展!
写在最后
计算机科学没有魔法。 所有看似神奇的框架,底层都是简单规则的层层组合。
当你亲手实现了 IoC 容器、Tomcat 连接器、Mapper 代理之后,再去看 Spring、MyBatis 源码,你会发现:原来就这么回事。
希望这 30 个问题能帮你打通从 CPU 到 Controller 的任督二脉,建立全链路自洽的底层认知。
开始动手吧!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)