自己搭一个Java Web框架,你需要解决哪些问题
自己搭一个 Java Web 框架你需要解决哪些问题不是重复造轮子是把 Spring 替你做的决策挨个想一遍引子你每天都在用 Spring Boot。RestController、Autowired、Transactional——注解一加事情就自动办了。但你有没有想过为什么是这几个注解为什么是这些功能Spring 的设计者在做这个框架时到底在解决什么问题理解这个问题最好的方式不是读 Spring 源码而是回到原点自己想一遍。假设你有一个 Java Web 项目没有任何框架只有 Servlet JDBC。你每天写代码会遇到什么哪些事是重复的哪些事是可以统一处理的把这些问题理清楚你会发现Spring 里的每一个模块都不是拍脑袋想出来的而是被真实痛点逼出来的。下面从六个真实痛点出发逐一拆解。一、XML 配置地狱 → 注解 自动扫描痛点早期 Java 项目包括早期的 Spring大量使用 XML 配置。一个 Bean 一个bean标签一个数据源一堆property。项目稍微大一点applicationContext.xml动辄几百行。更麻烦的是多人协作时 XML 冲突不断合并代码时一不留神就漏了一个节点运行时才暴露。设计决策用注解替代 XML。控制类上加Controller服务类上加Service框架启动时自动扫描指定目录找到所有带注解的类实例化并注入。ControllerpublicclassUserController{AutowiredprivateUserServiceuserService;}要解决的问题消灭 XML 文件冲突配置跟代码放在一起改代码时不用切文件启动时一次性扫描运行时零开销这就是 Spring 的ComponentScan在做的事。核心思路约定大于配置——你加了注解框架就认。不需要额外告诉它。二、前后台交互混乱 → 统一约定 URL 路由痛点没有框架时每个 Servlet 自己解析请求参数、自己拼 JSON、自己处理异常。十个 Servlet 有十种写法。参数解析散落在各处返回格式不统一前端对接时一头雾水。设计决策约定统一的请求/响应格式。前端统一发 JSON后端统一解析返回也分类型匹配普通 JSON、分页 DataStore、文件下载等。URL 路由用散列HashMap快速匹配。启动时扫描所有控制类的方法把 URL 路径和对应方法存进 Map。请求来了直接 O(1) 查找不需要反射遍历。启动时 /user/list → UserController.list() /user/save → UserController.save() 请求来了 GET /user/list → 从 Map 中取出 method → 反射调用要解决的问题前后台交互格式统一减少对接成本URL 到方法的映射要快——散列比反射遍历快几个数量级初始化时一次性装载运行时只做查表这就是 Spring 的DispatcherServletRequestMapping的核心设计启动时建索引运行时做路由。三、参数手工解析 → 反射自动装配痛点每个方法都要自己从HttpServletRequest里取参数、转类型、判空。一个方法五个参数解析逻辑比业务逻辑还长。类型转换也容易出错——字符串转日期、字符串转整数到处都是Integer.parseInt()和SimpleDateFormat。设计决策用反射获取方法的参数名称、个数、类型自动从请求中提取对应值并转换类型。// 框架做的事Methodmethod...;// 通过路由找到的方法Parameter[]paramsmethod.getParameters();// 反射拿参数信息Object[]argsnewObject[params.length];for(inti0;iparams.length;i){Stringnameparams[i].getName();// 参数名Class?typeparams[i].getType();// 参数类型Stringvaluerequest.getParameter(name);// 从请求取值args[i]convert(value,type);// 类型转换}method.invoke(instance,args);// 反射调用要解决的问题参数解析和类型转换集中处理业务方法只关心业务支持常见类型转换String → int、long、Date、BigDecimal 等新增参数不用改解析代码框架自动适配这是 Spring 的HandlerMethodArgumentResolver在做的事。你加一个RequestParam(name) String name框架替你从请求里取值、转换类型、注入方法参数。开发者的精力从取参数释放到用参数。四、日志、监控、连接管理散落各处 → AOP 代理痛点每个方法都要手动写日志logger.info(开始处理xxx)、logger.info(处理完成xxx)。数据库连接的获取和归还也散落在业务代码里。监控埋点、权限校验、事务边界——这些跟业务无关但每个方法都要做的事到处重复。设计决策用 AOP面向切面编程统一处理。核心机制是代理模式框架给目标类创建一个代理对象代理对象在调用真实方法前后插入日志、监控、连接管理等逻辑。// 代理类做的事publicObjectinvoke(Objectproxy,Methodmethod,Object[]args){log.info(调用方法: {},method.getName());// 前置记录日志longstartSystem.currentTimeMillis();// 前置计时开始Objectresultmethod.invoke(target,args);// 真实调用longcostSystem.currentTimeMillis()-start;// 后置计时结束log.info(方法耗时: {}ms,cost);// 后置记录耗时returnresult;}装载时框架扫描带 AOP 注解的类用 JDK 动态代理或 CGLIB 创建代理对象注入到需要的地方。调用方完全无感知。要解决的问题日志、监控、权限等横切关注点集中管理不侵入业务代码加一个新的切面逻辑比如性能监控不改任何业务方法代理在装载时创建运行时只多一层方法调用开销可控这是 Spring AOP 的核心把每个方法都要做的事从方法里抽出来统一放在一个地方。五、事务不一致 → 统一事务管理痛点业务方法里手动管理事务connection.setAutoCommit(false)→ 执行 SQL →connection.commit()。一旦某个方法忘了 commit或者异常时没 rollback数据就脏了。更麻烦的是流程引擎和业务数据各自管事务流程提交了但业务回滚了数据不一致。设计决策统一事务管理器。所有数据库操作使用同一个连接由框架控制提交和回滚。业务方法开始 → 框架获取连接关闭自动提交 → 执行业务 SQL自动生成的或手写的 → 执行流程引擎 SQL让流程引擎的 commit 操作无效 → 全部成功 → 框架统一 commit → 任一失败 → 框架统一 rollback持久层选型上MyBatis 是一个好选择既能用 XML 灵活写复杂 SQL又能用注解快速搞定简单查询。框架在此基础上封装统一的事务管理接口。要解决的问题一个方法里的所有数据库操作要么全成功、要么全回滚流程引擎和业务数据共享同一事务消除不一致开发者不需要手动写commit()和rollback()这是 Spring 的TransactionalPlatformTransactionManager的核心设计。你加一个注解框架替你管事务边界。事务管理是框架最不能出错的部分所以必须统一。六、SQL 拼接易错 → 动态 SQL 绑定变量痛点手写 SQL 拼接条件SELECT * FROM user WHERE name name 。容易写错单引号、容易 SQL 注入、修改字段时要到处找。查询条件变化时SQL 也要跟着改。设计决策数据库中配置表名、字段名、别名。前端传来的字段名跟数据库字段一一对应约定。框架根据前端传的参数自动生成 SQL 语句。-- 前端传了 {name: 张三, age: 30}-- 框架自动生成SELECTname,age,phoneFROMuserWHEREname?ANDage?-- 绑定参数[张三, 30]条件变量必须用绑定方式?占位符不直接拼接。直接拼接有两个问题一是影响数据库预编译缓存命中率二是容易被 SQL 注入攻击。要解决的问题减少 SQL 拼接错误防止 SQL 注入字段变化时只改配置不改代码这是 MyBatis 的#{}占位符在做的事。框架替你处理参数绑定开发者只关心 SQL 逻辑。七、总结框架的本质是什么回看这六个问题有一个共同点它们都是重复决策。每次配置 Bean —— 重复每次解析请求参数 —— 重复每次取参数转类型 —— 重复每次写日志开连接 —— 重复每次管事务的提交和回滚 —— 重复每次拼接 SQL —— 重复框架做的事就是把重复决策变成约定。你遵守约定加注解、用统一格式、按命名规则传参框架替你处理剩下的所有事情。Spring 的每一个模块背后都是一个具体的痛点。理解了痛点你就理解了 Spring 的设计——也就知道什么时候该用 Spring什么时候不用也行。自己搭框架不是造轮子。是把别人替你做的决策自己重新想一遍。想完之后你再回头看 Spring看到的东西会完全不一样。