告别黑箱,开源项目真实可运行
30 个核心问题 + 3 万行自研代码,从 CPU 指令到手写 Tomcat、IoC、Mapper,彻底打通全链路


作者介绍

CodeStats,资深底层技术爱好者与实战派架构师,WWAIC(全周 AI 编程)范式创始人。专注计算机体系结构、操作系统内核、Java 虚拟机实现原理与自研框架落地。长期在 CSDN 分享硬核技术文章,手写 IoC 容器、嵌入式 Tomcat、MyBatis 风格 Mapper、连接池及代码分析引擎,致力于用通俗语言讲透 Java 程序从 CPU 指令到 Web 框架的完整运行逻辑。

💡 个人代表项目 CodeStats —— 一个完全自研、零商业依赖的 Java Web 平台,100% 代码由 AI 辅助生成,一周内从零交付可运行系统。本文所有示例均取自该项目真实源码。


📑 目录(30 个问题)

  1. CPU 如何执行一条指令?

  2. 中断如何实现“并发”假象?

  3. 用户态与内核态如何切换?

  4. 进程和线程的本质区别是什么?为什么线程切换更轻量?

  5. 如何手写一个 IoC 容器?

  6. 如何手写一个 MVC 框架?

  7. 如何手写嵌入式 Tomcat?

  8. 如何手写 MyBatis 风格的 Mapper?

  9. 如何手写数据库连接池?

  10. 如何手写 JdbcTemplate?

  11. 如何手写日志框架?

  12. 如何手写缓存框架?

  13. 如何手写 Stream 惰性求值?

  14. 如何手写类加载隔离?

  15. 如何手写分页插件?

  16. 如何手写 Session 管理?

  17. 如何手写 Filter 链?

  18. 如何手写静态资源服务器?

  19. 如何手写文件上传解析器?

  20. 如何手写 JSON 序列化器?

  21. 代码分析引擎(Pipeline 模式)

  22. 依赖分析(词法扫描 + 循环检测)

  23. NIO 如何解决 C10K 问题?

  24. 事务管理器如何实现?

  25. AOP 动态代理的核心是什么?

  26. Spring Boot 自动配置如何工作?

  27. Feign 声明式 HTTP 客户端如何模拟?

  28. 一级/二级缓存如何设计?

  29. WWAIC 范式是什么?

  30. 全链路自洽思维如何建立?


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 字段。

设计思路

  • BeanDefinitionbeanClassNamescopelazyInit

  • BeanFactorysingletonObjects 缓存 + 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 序列化后写入响应。

设计思路

  • 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)) {
            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

设计思路

  • ConnectorServerSocket + 线程池,解析请求行/Header/Body

  • PipelineList<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() 实现归还而非真正关闭。

设计思路

  1. 初始化创建 minIdle 个连接放入队列

  2. getConnection():获取 Semaphore 许可 → 从队列 poll()(空则新建) → 验证无效则递归重试 → 返回 PooledConnection 包装

  3. 归还:若队列未满则放回,否则关闭;释放 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 关键类

LoggerFactoryConsoleAppenderFileAppenderRollingFileAppenderAsyncAppenderPatternLayoutLevelRangeFilter


12. 如何手写缓存框架?

核心原理

ConcurrentHashMap + TTL + 定期清理。

  • 每个缓存条目记录 expireTime

  • get() 时惰性删除过期条目

  • 后台线程定期清理全量过期条目

设计思路

统一 Cache<K,V> 接口,支持 put(key, value, ttl)get(key)computeIfAbsent。实现分为 LocalCache(基于 ConcurrentHashMap)和 CaffeineCache(可选)。

CodeStats 关键类

CacheLocalCacheCacheFactoryCacheManager


13. 如何手写 Stream 惰性求值?

核心原理

流水线模式:中间操作不执行,只记录 PipelineStage;终端操作触发遍历,每个元素依次经过所有 stage。

设计思路

  • MyStream 接口:filtermapforEach

  • PipelineStageprocess(input, sink) 函数式接口

  • 构建阶段:每次中间操作返回新 MyStreamImpl,合并 stage 链

  • 执行阶段forEach 时从最外层 stage 开始,对源集合每个元素递归调用

CodeStats 关键类

MyStreamImplMyPipelineStage


14. 如何手写类加载隔离?

核心原理

打破双亲委派模型:优先从当前 Web 应用加载类,再委托父加载器。
Tomcat 的 WebappClassLoader 先 findClass() 再 super.loadClass()

设计思路

继承 URLClassLoader,重写 loadClass()

  1. 已加载类直接返回

  2. 系统类(java.javax.)委托父加载器

  3. 优先 findClass() 从当前应用 WEB-INF/classes 和 WEB-INF/lib 加载

  4. 未找到再委托父加载器

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 关键类

SessionManagerHttpSessionImpl


17. 如何手写 Filter 链?

核心原理

责任链模式ApplicationFilterChain 维护 Filter 列表和当前索引,递归调用 doFilter()

设计思路

FilterChain.doFilter(request, response) 中:若还有下一个 Filter 则调用它,否则调用 servlet.service()。每个 Filter 可在调用前后执行自定义逻辑。

CodeStats 实现代码

ApplicationFilterChain.java


18. 如何手写静态资源服务器?

核心原理

路径安全校验(防止 ../ 越权),MIME 类型映射,直接通过 Files.copy() 输出文件流。

设计思路

serveStaticFile(uri, res)

  1. 将 URI 转换为文件系统路径(以 webapps/ 为根)

  2. 调用 Path.normalize(),检查是否仍在根目录下

  3. 根据扩展名设置 Content-Type

  4. Files.copy(file, outputStream)

CodeStats 实现代码

DispatcherServlet.serveStaticFile()


19. 如何手写文件上传解析器?

核心原理

解析 multipart/form-data 请求体,查找边界字符串,读取每个 part 的头部和内容,将文件内容写入临时文件,普通字段存入 Map。

设计思路

  • MultipartResolver

    • 从 Content-Type 提取 boundary

    • 使用 BoundaryFinder 高效匹配边界

    • 逐个 part 解析:读取头部 → 判断是普通字段还是文件 → 写入临时文件或直接存值

  • 实现 AutoCloseable,请求结束后删除临时文件

CodeStats 关键类

MultipartResolverBoundaryFinderFileItem


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 关键类

PipelineCollectStageParallelAnalyzeStageAggregateStageOutputStage


22. 依赖分析(词法扫描 + 循环检测)

核心原理

通过词法扫描 Java 源码提取包名、导入、类声明、字段类型、方法返回类型/参数类型,通过同包、导入、java.lang 隐式导入规则解析全限定名。用 Tarjan 算法检测循环依赖。

设计思路

  • SimpleJavaLexer:扫描 Token,跳过注释和字符串

  • JavaDependencyExtractor:解析包名、导入、类声明,收集类型引用

  • TypeNameResolver:将简单名解析为全限定名

  • DependencyGraph:使用 Tarjan 算法找出强连通分量(循环依赖组)

CodeStats 关键类

JavaDependencyExtractorSimpleJavaLexerTypeNameResolverDependencyGraph


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

一个完全自研的 Java Web 框架,包含:

  • ✅ 自研 IoC 容器

  • ✅ 自研 MVC 框架

  • ✅ 自研嵌入式 Tomcat

  • ✅ 自研 MyBatis 风格 Mapper

  • ✅ 自研数据库连接池

  • ✅ 自研 JdbcTemplate

  • ✅ 自研日志框架

  • ✅ 自研缓存框架

  • ✅ 代码分析引擎(Pipeline 模式)

  • ✅ 前端管理界面(数据库客户端、文件管理、AI 助手)

📦 全部代码 3 万行100% 可运行,无黑盒。欢迎 Star、Clone、学习、扩展!


写在最后

计算机科学没有魔法。 所有看似神奇的框架,底层都是简单规则的层层组合。

当你亲手实现了 IoC 容器、Tomcat 连接器、Mapper 代理之后,再去看 Spring、MyBatis 源码,你会发现:原来就这么回事。

希望这 30 个问题能帮你打通从 CPU 到 Controller 的任督二脉,建立全链路自洽的底层认知。

开始动手吧!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐