从零自研 Java Web 框架:30 个核心问题系统拆解全流程

发布时间:2026/6/14 8:35:46
从零自研 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 线程javathreadPool.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堆对象但每个线程有自己的栈帧javaConnection conn idleConnections.poll(); // 多个线程同时调用线程安全5. 如何手写一个 IoC 容器一句话核心原理三步扫描包找出Component等注解类 → 创建BeanDefinition→ 实例化 Bean通过反射注入Autowired字段。设计思路BeanDefinitionbeanClassName、scope、lazyInitBeanFactorysingletonObjects缓存 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 { MapString, ? 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 序列化后写入响应。设计思路HandlerMappingpath→(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)) { MapString, 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; charsetutf-8); res.write(toJson(result)); } res.send(); return; } } serveStaticFile(uri, res); }7. 如何手写嵌入式 Tomcat一句话核心原理Tomcat 核心架构Server → Service → Connector Engine → Host → Context → Wrapper。Connector 监听端口解析 HTTPPipeline-Valve 责任链处理请求Wrapper 包装并调用 Servlet设计思路ConnectorServerSocket 线程池解析请求行/Header/BodyPipelineListValve 递归调用invokeNext()Context维护servletMappingsURL → servletNameCodeStats 实现代码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或方法参数对象中获取值结果映射根据返回类型ListT/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包装归还若队列未满则放回否则关闭释放 SemaphoreCodeStats 实现代码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 ListT query(String sql, RowMapperT 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(); ListT results new ArrayList(); while (rs.next()) { results.add(rowMapper.mapRow(rs)); } return results; } } public static T RowMapperT beanRowMapper(ClassT 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格式化 LogEventPatternLayout 支持%d、%level、%msg、%exFilter责任链过滤如LevelRangeFilter设计思路LoggerFactory从 XML 加载配置Logger门面提供log.info()等方法Appender.doAppend()通过模板方法调用子类实现的append()。CodeStats 关键类LoggerFactory,ConsoleAppender,FileAppender,RollingFileAppender,AsyncAppender,PatternLayout,LevelRangeFilter12. 如何手写缓存框架核心原理ConcurrentHashMapTTL 定期清理。每个缓存条目记录expireTimeget()时惰性删除过期条目后台线程定期清理全量过期条目设计思路统一CacheK,V接口支持put(key, value, ttl)、get(key)、computeIfAbsent。实现分为LocalCache基于ConcurrentHashMap和CaffeineCache可选。CodeStats 关键类Cache,LocalCache,CacheFactory,CacheManager13. 如何手写 Stream 惰性求值核心原理流水线模式中间操作不执行只记录PipelineStage终端操作触发遍历每个元素依次经过所有 stage。设计思路MyStream接口filter,map,forEachPipelineStageprocess(input, sink)函数式接口构建阶段每次中间操作返回新MyStreamImpl合并 stage 链执行阶段forEach时从最外层 stage 开始对源集合每个元素递归调用CodeStats 关键类MyStreamImpl,MyPipelineStage14. 如何手写类加载隔离核心原理打破双亲委派模型优先从当前 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服务器端MapString, HttpSession存储会话数据。Request.getSession()根据 Cookie 中的 JSESSIONID 查找或创建新 Session。设计思路SessionManager静态ConcurrentHashMap 原子 ID 生成器HttpSessionImpl存储属性、创建时间、最后访问时间Request 解析 CookieResponse 添加Set-Cookie头CodeStats 关键类SessionManager,HttpSessionImpl17. 如何手写 Filter 链核心原理责任链模式ApplicationFilterChain维护 Filter 列表和当前索引递归调用doFilter()。设计思路FilterChain.doFilter(request, response)中若还有下一个 Filter 则调用它否则调用servlet.service()。每个 Filter 可在调用前后执行自定义逻辑。CodeStats 实现代码ApplicationFilterChain.java18. 如何手写静态资源服务器核心原理路径安全校验防止../越权MIME 类型映射直接通过Files.copy()输出文件流。设计思路serveStaticFile(uri, res)将 URI 转换为文件系统路径以webapps/为根调用Path.normalize()检查是否仍在根目录下根据扩展名设置Content-TypeFiles.copy(file, outputStream)CodeStats 实现代码DispatcherServlet.serveStaticFile()19. 如何手写文件上传解析器核心原理解析multipart/form-data请求体查找边界字符串读取每个 part 的头部和内容将文件内容写入临时文件普通字段存入 Map。设计思路MultipartResolver从Content-Type提取boundary使用BoundaryFinder高效匹配边界逐个 part 解析读取头部 → 判断是普通字段还是文件 → 写入临时文件或直接存值实现AutoCloseable请求结束后删除临时文件CodeStats 关键类MultipartResolver,BoundaryFinder,FileItem20. 如何手写 JSON 序列化器核心原理反射遍历对象字段递归序列化用IdentityHashMap检测循环引用忽略static和transient字段。设计思路JsonUtil.toJson(obj)null→nullString→ 转义引号、换行等Number/Boolean→ 直接toString()Map→ 序列化为{...}Collection/ 数组 → 序列化为[...]其他 JavaBean → 反射获取所有字段包括私有序列化为{fieldName: value}CodeStats 实现代码JsonUtil.java21. 代码分析引擎Pipeline 模式核心原理Pipeline 模式将分析流程拆分为多个独立 StageCollectStage→ParallelAnalyzeStage→AggregateStage→OutputStage设计思路CollectStage递归扫描目录过滤扩展名和排除路径ParallelAnalyzeStage多线程并行分析每个文件统计代码行/注释行/空行/复杂度AggregateStage按指定字段排序汇总语言分布OutputStage输出 JSON/CSV/Console 格式CodeStats 关键类Pipeline,CollectStage,ParallelAnalyzeStage,AggregateStage,OutputStage22. 依赖分析词法扫描 循环检测核心原理通过词法扫描Java 源码提取包名、导入、类声明、字段类型、方法返回类型/参数类型通过同包、导入、java.lang隐式导入规则解析全限定名。用Tarjan 算法检测循环依赖。设计思路SimpleJavaLexer扫描 Token跳过注释和字符串JavaDependencyExtractor解析包名、导入、类声明收集类型引用TypeNameResolver将简单名解析为全限定名DependencyGraph使用 Tarjan 算法找出强连通分量循环依赖组CodeStats 关键类JavaDependencyExtractor,SimpleJavaLexer,TypeNameResolver,DependencyGraph23. NIO 如何解决 C10K 问题核心原理使用Selector单线程管理成千上万个非阻塞 Channel只在有事件可读/可写/连接时才处理避免了 BIO 中每个线程阻塞在read()上。设计思路ServerSocketChannel设置为非阻塞注册OP_ACCEPTSelector.select()阻塞等待事件新连接注册OP_READ数据到来时提交给线程池处理线程池只负责业务逻辑不阻塞 I/OCodeStats 实现代码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 关键类ConfigLoader27. Feign 声明式 HTTP 客户端如何模拟核心原理与 MyBatis Mapper 完全一致通过FeignClient标记接口FeignClientFactoryBean创建 JDK 动态代理代理中将方法调用转换为 HTTP 请求拼接 URL、填充参数、执行请求并解码响应。设计思路参考MapperProxy的实现将 SQL 执行替换为HttpURLConnection或HttpClient调用。CodeStats 关键类MapperProxy可直接修改为 HTTP 代理28. 一级/二级缓存如何设计核心原理一级缓存SqlSession级别的PerpetualCacheHashMap会话关闭或执行增删改时清空二级缓存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 应用。开源项目CodeStatsGitHub / Giteehttps://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 的任督二脉建立全链路自洽的底层认知。开始动手吧