本节摘要:开发环境的移交目标是"新同事半小时内跑起来":一份环境清单覆盖运行时与容器,构建工具把依赖与打包收进声明式契约从而消灭"在我机器上能跑",热部署依托翻译缓存机制让页面改动即时生效。本站是交接周的第一站,交付环境线的全部清单与一次依赖冲突的排查实录。
第 1 章你接手时的那个下午——没有容器、没有文档、靠一句"本地起服务就能跑"硬啃——就是反面的交接标准。轮到你交棒时,环境移交清单至少要包含五样,并且每一样都以"接手者亲手操作成功"为验收:
| 清单项 | 内容 | 验收动作 |
|---|---|---|
| 运行时与容器 | 版本组合及理由(容器 9.x 配 Java 11 的选型依据) | 新同事装好后容器能启动 |
| 源码与工程描述 | 仓库地址、分支约定、构建描述文件位置 | 拉取后能完成首次构建 |
| 本地数据 | 数据库初始化脚本或最小数据集 | 本地起服务后首页有数据 |
| 验证页面清单 | 三个代表页面(列表、详情、下单)的访问地址 | 逐一打开功能正常 |
| 常见坑速查 | 端口占用、编码设置、时间戳陷阱 | 遇到时不用来问你 |
IDE 配置是清单的软性部分。现代开发环境对页面语法有语义级理解:EL 表达式能基于控制器放进作用域的对象类型做补全——你拼错属性名,编辑器当场画波浪线;脚本片段里能跳转到对应类源码;自定义标签的属性参照描述文件校验。第 6 章辛苦维护的 TLD 契约,在编辑器里变成了即时反馈。把这些能力配置写进交接文档的一页,新同事的"读懂老代码"门槛会低一个量级。
"在我机器上能跑"的经典困境,根源是环境差异没有契约:依赖靠手工拷贝、版本靠口头约定。构建工具的解法是把工程事实写成声明——项目叫什么、依赖哪些库什么版本、按什么结构打包,全部落在一份描述文件里,任何人在任何机器上执行同一条构建命令,产出的部署包逐字节一致。
对 JSP 工程它还顺手管了三件事:依赖包自动就位到私有区(告别手工拷贝)、页面与静态资源按约定目录收进部署包、打包产出可直接投向容器的归档。依赖声明里最值得懂的机制是冲突调解:当两个依赖各自牵出一个同名库的不同版本时,构建工具按"离根近者优先"取一个,也允许你在声明里显式锁定版本压过调解结果。听懂这条规则,下面这单交接周的常见事故就好懂了。
背景:新同事第一天构建就失败——编译报错说某个工具类的某个方法不存在。可这个方法明明上周还有人用,代码库里也找得到它。
操作:按依赖链排查。先确认报错的是编译期而非运行期,说明类找得到、方法签名对不上——典型的"两个版本撞车"。用构建工具的依赖树命令把该库的完整传递路径打出来,果然同一库出现两个版本:工程直接依赖的是新版本(含该方法),而某个第三方组件间接牵进一个老版本。
结果:在依赖声明里显式锁定新版本,重新构建通过。
解读:调解规则"离根近者优先"在这里反噬了工程——第三方组件的间接依赖与工程的直接依赖同级时,谁先被声明谁说了算,这个隐式裁决对新人完全不可见。显式锁定版本等于把裁决权拿回自己手里。这条经验的交接价值极高:依赖树命令与锁版本写法,应当作为"常见坑速查"的第一条写进环境清单。
变式:如果冲突双方都是间接依赖、工程本身并不直接使用该库,更干净的做法是用依赖排除把坏版本掐断,而不是锁定——排除表达的是"我不要这个传递依赖",锁定表达的是"我要这个版本"。按意图选写法,后来者才读得懂你的意图。
热部署让你改完页面刷新即见,2.1 节拆过它的机制:容器按修改时间戳发现页面变更,重走翻译编译流水线。但它的能力边界要交底清楚,否则新同事会得出错误的心智模型。
页面文件的改动——模板文本、脚本片段、EL——走热重译,即时生效,这是热部署的主场。Java 类的改动——控制器、服务、数据访问——默认不走这条路:类由容器加载器持有,替换它们需要重载整个应用上下文,有些环境配置了上下文热重载,有些没有。分不清这两条边界的新人,最常见的困惑就是"我改了个方法怎么不生效"——答案通常是他改的是类,环境又没开类热重载,重启即可。交接清单的"常见坑速查"第二条就是它。
还有一条与生产安全的边界必须写进文档:变更检测与热重译是开发期福利。8.1 节说过预编译要纳入构建脚本,而部分生产配置会显式关闭变更检测——这不是缺陷,是让生产环境行为可预期的保护。新同事若发现"生产改了页面没反应",那是防线在工作,不是故障。
💡 关键直觉:交接的本质不是"我知道的都告诉你",而是"把你的失败也变成文档"。你踩过的坑——依赖冲突、类不热载、时间戳陷阱——每写进清单一条,接手者就少绕一圈。第 1 章那个孤立无援的下午,就是本章存在的全部理由。
开发环境的移交里,编辑器配置常被当成个人偏好跳过,但 JSP 工程有四项配置属于"不配就要踩坑"的公共项。其一,文件编码统一为无签名的 UTF-8:带签名的编码会让页面输出多一个隐形字符,表现为页面顶部多出一段空白或样式错位。其二,换行符与缩进的工程级约定:构建产物的资源比对、脚本对页面的批量扫描,都假定格式统一。其三,页面模板的本地校验开关:让编辑器按工程声明的规范版本校验语法,把 1.2 节那些跨层语法错误拦在保存时。其四,依赖树查看命令绑定成常用任务:10.1 节那单依赖冲突,新人若两步就能调出依赖树,排障时间省七成。四项写进环境清单的"编辑器配置"小节,附上配置步骤截图——这类配置的价值全在"默认就对",错一次的成本远高于配一次的成本。
声明式构建还有一招压箱底的好处值得单独说:让构建产物学会自我介绍。做法是构建时把版本号、构建时间、依赖清单写进产物内的一份清单文件,应用再提供一个内部页面展示这份清单。价值在排障现场:生产环境的部署包到底是哪个版本、何时构建、带着哪些依赖,打开内部页面一眼即知——不用翻部署记录、不用问运维、更不用解包猜。青梧书肆出过一次"预发的包怎么和本地构建对不上"的悬案,排查半小时才发现预发漏更了一个旧包;此后版本自述页面成了每次部署必看的第一个页面。给产物加自述的成本是构建脚本里十几行,收益是消灭一整类"环境里跑的到底是谁"的悬案。这个技巧与版本矩阵表配合使用,环境侧的最后一块盲区就被照亮了。
环境线交完,下一站走部署线:同一个部署包,凭什么在三台环境上表现各异?容器兼容性的暗礁逐个标出来。