Spring 6.1.12 与 MyBatis 3.5.16 实验
固定JDK17、Spring Framework6.1.12、MyBatis3.5.16、MyBatis-Spring3.0.3、H2 2.3.232、HikariCP5.1.0。H2验证框架与缓存边界,不证明MySQL隔离与持久化;MockMvc不启动真实Servlet容器或TLS。
从仓库根运行,Maven路径按本机安装选择:
mvn -q -f labs/spring-senior/pom.xml compile exec:java -Dexec.mainClass=study.spring.LifecycleLab
mvn -q -f labs/spring-senior/pom.xml exec:java -Dexec.mainClass=study.spring.ProxyLab -Dexec.args=cglib
mvn -q -f labs/spring-senior/pom.xml exec:java -Dexec.mainClass=study.spring.CircleLab -Dexec.args=setter
mvn -q -f labs/spring-senior/pom.xml exec:java -Dexec.mainClass=study.spring.TxLab -Dexec.args=required
mvn -q -f labs/spring-senior/pom.xml exec:java -Dexec.mainClass=study.spring.MyBatisLab
mvn -q -f labs/spring-senior/pom.xml exec:java -Dexec.mainClass=study.spring.WebLabLifecycleLab支持normal/fail;ProxyLab支持jdk/cglib及两种代理各自的-short/-throw;CircleLab支持setter/constructor/forbidden/late-wrapper/prototype;TxLab原有required/requires-new/nested/pool/after-commit保持不变。每次Maven调用运行一个main,断言失败以非零退出。
完整回归入口:指定 JDK17 与可用 Maven 后执行 JAVA_HOME=$(/usr/libexec/java_home -v17) bash labs/spring-senior/run.sh all,或用 lifecycle / circle / proxy / candidate / config / aop / tx / sync / jdbc / jdbc-mysql / mybatis / web / mvc / client / async / infra / binding / security / core / boot 单独运行。Maven 不在 PATH 时通过 MAVEN_CMD=/absolute/path/to/mvn 指定。all 共运行 90 个核心框架场景(U21 起 core 含 schedule 组 3 个:scheduled/shutdown/cache)、1 个独立 Security 场景及 6 个 Boot 场景(共 97 场景,PASS 断言行更多),首轮需联网下载固定依赖;无外部监听端口。U18 起 pom 新增 com.mysql:mysql-connector-j:9.2.0(仅 jdbc-mysql 组使用,2026-10-25 经本机 Maven 镜像实拉);U19 起 pom 新增 jackson-databind:2.17.2、hibernate-validator:8.0.1.Final、jakarta.validation-api:3.0.2、expressly:5.0.0(mvc 组使用);jdbc-mysql 组需要本机 mysqld 二进制(MYSQLD_BIN 可覆盖),它以 --initialize-insecure 在 mktemp -d 唯一数据目录 + 随机高端口 + 独立 socket 上启动一次性隔离实例,跑完 mysqladmin shutdown 并删除数据目录,绝不触碰本机既有 MySQL 服务或数据。源码文件/符号核对独立运行 python3 labs/spring-senior/verify_sources.py all --check;正文的精确行和整文件SHA核验使用 python3 scripts/check_source_excerpts.py,两者不代替语义或运行验证。
边界:TxLab保留TransactionTemplate传播和提交回调,另把external/self/checked/checked-rollback/future-failed/future-late/thread委托给真实AnnotatedTxLab。self与checked已替换旧占位;MyBatisLab现运行真实Spring会话、BATCH、Cursor及物理提交前同步失败的缓存窗口。H2不证明MySQL,提交前回滚不等于网络提交未知。下表区分实际结果与未验证范围。
T18 输入、预期与实测
本批新增 sync(9 模式,H2)、jdbc(4 模式,H2)与 jdbc-mysql(5 模式,一次性隔离 MySQL 实例)三组。实际 Oracle JDK 17(显式 /Library/Java/JavaVirtualMachines/jdk-17.jdk),Framework 6.1.12;MySQL 实验组合为 Homebrew MySQL 9.7.1(Innovation 发行版)+ Connector/J 9.2.0,结论只声明该组合。
| 公开命令 | 输入与实际判据 |
|---|---|
run.sh sync | 9 模式:sync-phases 七回调按 @Order 序列且 STATUS=COMMITTED、sync-fail beforeCommit 抛错回滚+afterCompletion 吞错兄弟仍收状态、events 四阶段+condition+fallbackExecution(无事务只投 fallback;AFTER_COMMIT 相监听器回滚时全静默)、programmatic checked 包装 UndeclaredThrowableException/runtime 回滚/setRollbackOnly 静默回滚、propagation MANDATORY 无事务拒绝+NEVER 两向+SUPPORTS 无事务仅激活同步/有事务同连接参与、isolation SERIALIZABLE 应用+H2 不拒绝 READ_UNCOMMITTED+参与型不升级+validateExistingTransaction 失败、savepoint create/rollbackTo/release 与外层回滚清空、suspension REQUIRES_NEW 第二连接+池占用 1→2→1+内层完成先于外层 beforeCommit、rollback-rules 深度胜出+同深度按注册顺序+checked 默认提交 |
run.sh jdbc | 4 模式(H2 2.3.232):translate-h2 原生 JdbcSQLIntegrityConstraintViolationException(23505) 经错误码表与子类翻译器双路映射 DuplicateKeyException、batch-h2 同事务 catch 部分提交 rows=[1,2,3,9] 且 updateCounts=[1,1,EXECUTE_FAILED,1](H2 执行越过失败点)/参与型失败全回滚、keys-h2 单条 KeyHolder+批量 3 键+多键 getKey 失败+无 identity 返回 null、readonly-h2 readOnly 提示 INSERT 照常落库 |
run.sh jdbc-mysql | 5 模式(隔离 MySQL 9.7.1 + Connector/J 9.2.0):translate-mysql 裸 SQLIntegrityConstraintViolationException(23000/1062) 双路映射 DuplicateKeyException+1146 BadSqlGrammar、batch-mysql rewrite=false 失败抛裸约束异常(无 BatchUpdateException/updateCounts)但批内后续语句仍执行(rows=[1,2,3,9])、batch-mysql-rewrite=true 合并为多值语句整条失败(rows=[9])、keys-mysql 单条/批量键 3 条(rewrite 两档相同)、dirtyread-mysql READ-UNCOMMITTED 实测脏读+Spring 隔离值下发会话 READ-UNCOMMITTED+写方回滚后消失+readOnly 驱动侧拒绝写语句 |
以上 18 个模式均已实跑通过(PASS 断言行见 evidence/u18-2026-10-25.json 与各模式 stdout)。开发期真实红绿三次(无 @EnableTransactionManagement 时 @TransactionalEventListener 退化为普通监听器在发布点立即执行;批处理 catch 放在模板外=整事务回滚非部分提交;H2 READ_UNCOMMITTED 不被拒绝而是原样返回),另如实记录开发期修正:batch 布局 B 期望行、H2 异常子类假设、rollback-rules 误用 IOException 子类当"无规则"反例、reader 连接 final 捕获与 try/finally 收尾。未测:响应式事务与 R2DBC、JTA/JtaTransactionManager、CallbackPreferring 管理器、Oracle/PostgreSQL 驱动差异、Connector/J 其他属性组合、MVCC/间隙锁(归 MySQL 票)。MyBatis-Spring 会话绑定归 U42、JPA 归 U43。
T17 输入、预期与实测
本批新增 aop 组实验,pom 仅新增 org.aspectj:aspectjweaver:1.9.22.1(@Aspect 表达式切点运行期必需;Boot 3.3.3 管理版本,2026-10-25 本机 Maven 镜像实拉)。实际 JDK 17(显式 /Library/Java/JavaVirtualMachines/jdk-17.jdk),Framework 6.1.12 与运行构建分记。weaving 模式经 exec:exec 派生 JVM 加 -javaagent(exec:java 子优先 realm 不织入,实测结论)。
| 公开命令 | 输入与实际判据 |
|---|---|
run.sh aop | 8 模式:advice-order 五类型 backbone 与异常分支(同类型次序 undefined 只记录)、binding args 求值时点(proceed 改参可见)+ 坏参数静默失配 beanUnproxied、aspect-order @Order 嵌套 1-2-2-1 与 undefined 对照、tx-layering 吞异常切面内层提交 rows=[1]/外层回滚 rows=[]/rethrow 修复(H2 数据行)、self-call 绕过 + currentProxy 未开启失败消息 + exposeProxy 修复、final-methods final 方法静默/final 类 BeanCreationException/代理类名、introduction @DeclareParents 点号模式 + Advised 增删、weaving LTW 无容器对照(自调用与 final 均被增强) |
以上 8 个模式(16 条 PASS 行)均已实跑通过,evidence/u17-2026-10-25.json 记录三次真实红绿(args 绑定快照假设/坏参数响亮失败假设/$ 类型模式假设被实测推翻)与开发期修复(H2 DB_CLOSE_DELAY、代理壳字段陷阱、FinalClassAspect、public 切面、aspectOf include、exec:exec 派生 JVM、U16 events 模式竞态补 latch)。未测:XML aop:config、perthis/pertarget、无 -parameters 的参数名发现失败(按 bindArgumentsByName 分支推演)、opaque、delegateRef 引介、原生镜像 AOT、compile-time/post-compile 织入(需 ajc 构建链)、字段/构造器 joinpoint。
T16 输入、预期与实测
本批新增四组实验,固定依赖未变(spring-context 6.1.12 传递 spring-core/beans/expression/aop,XML/扫描/转换/SpEL 全覆盖);pom 仅新增 <parameters>true</parameters>。实际 JDK 17(/usr/libexec/java_home -v17),Framework 6.1.12 与运行构建分记。
| 公开命令 | 输入与实际判据 |
|---|---|
run.sh candidate | 8 模式独立 Context:歧义 refresh 即失败且消息含两个候选名、primary 胜出且双方仍登记、qualifier 收窄、List 按 @Order 排序而 Map 按登记顺序、required 启动失败/optional null/getObject 惰性失败、泛型唯一命中、字段名+参数名兜底(parametersFlag=true 证明 -parameters)、prototype 按注入点各解析一次 |
run.sh config | 4 模式:XML 构造→属性顺序与 lazy 定义不预实例化、扫描 ASSIGNABLE_TYPE 排除与 @Lazy 只有定义、@Import+环境分支 ImportSelector(定义期运行)、scoped proxy 共享代理每次调用新 target |
run.sh infra | 4 模式:PropertySource 首个命中(addFirst/addLast 实测翻转、system 覆盖 @PropertySource 文件、嵌套占位符读取时解析、required 缺失失败后 context 仍 active)、profile 四激活集合与 qa|uat 表达式、Resource 句柄与 exists 分离+临时文件清理、事件同步同线程有序+异常上抛截断+payload 包装+异步 uncaught 脱钩 |
run.sh binding | 4 模式:自定义 Converter 与 ConversionFailedException 带源值/目标类型、DataBinder setter 绑定与 typeMismatch/rejectedValue、declarativeBinding=true 无 allowedFields 静默跳过(6.1 行为)、Validator 错误码、SpEL 逐次求值与 Standard 执行 T() vs Simple 拒绝 |
以上 20 个模式均已实跑通过(PASS 断言行见 evidence/u16-2026-10-25.json 与各模式 stdout)。开发期失败已修正:prototype 最初误断言"两 holder 共享实例"(实测每个注入点各解析一次)、DataBinder 无 setter 目标静默零绑定(补 setter)、registerBean 后漏 refresh、qa 上下文漏激活、6.1 SimpleAsyncTaskExecutor 无 uncaught 处理器 setter(改 ThreadFactory 携带)。未测自定义 scope 生命周期、父子容器候选、FactoryBean 泛型、跨进程事件、SpEL 编译模式与 DoS 全谱系、jakarta Bean Validation 集成、Web 作用域(request/session)代理。
T14 输入、预期与实测
运行 run.sh tx 共12模式;run.sh mybatis 共5模式。本批五个原事务模式、七个真实注解模式及五个MyBatis模式均已实跑通过。实际JDK17.0.19+11-LTS,Framework6.1.12、MyBatis3.5.16、MyBatis-Spring3.0.3依赖版本未改。
| 输入 | 结果与证据含义 |
|---|---|
| external / self | external活动事务且最终空表;self由this绕代理,事务不活动,最终[1] |
| checked / checked-rollback | 同为真实检查异常;默认提交[1],rollbackFor回滚为空 |
| future-failed / future-late | 返回时已失败则空表;在代理返回后才失败则[1],无sleep竞争 |
| thread | 主事务写1回滚,工作线程不继承事务、写2自动提交,最终[2] |
| native | 保留prepare/L1/L2/Mapper失效/外部写旧缓存;新增强制rollback不发布待提交值,prepare总数6 |
| spring | 同事务L1复用、同一Spring连接;非事务两次读不共享L1;真实提交/回滚,holder已解绑 |
| batch | flush前0、flush后同事务见2、rollback后0;重复主键flush失败,SQLState23505,池active=0 |
| cursor | 非事务返回后已关闭;事务内完整消费;消费异常也释放Cursor/holder/池连接 |
| cache-failure | MyBatis同步发布ghost后,另一个beforeCommit失败;数据库old、缓存ghost,证明非原子窗口 |
后两个资源实验和batch使用独立Hikari池,失败必须命中指定异常/结果,不能将任意异常算成功。未测MySQL驱动流式fetch、网络commit失败、NESTED与BATCH/缓存联合回退、跨实例失效、插件互改SQL、Vavr或Cancelled Future。注解代理与事务资源的权威讲解见事务正文,持久层边界见MyBatis正文。
T15 输入、预期与实测
本批实际JDK为Liberica17.0.19+11-LTS。原核心pom不变;独立 security/pom.xml 只新增Security Web/Core/Crypto 6.3.3,Framework BOM固定6.1.12,依赖树已核对。安全程序用本地{noop}fixture凭据验证真实Provider,不是生产密码方案,不作漏洞或生产安全保证。
| 公开命令 | 输入与实际判据 |
|---|---|
run.sh web | 真实MockMvc同步200、指定Controller异常400、Callable成功/失败重调度、第二拦截器短路418、MVC前Filter指定异常;逐个核对状态/响应体/完整回调顺序,finally关闭1线程队列2的MVC池 |
run.sh mvc(U19) | 七模式:协商200/406映射阶段拒/415/体损坏400、分组组语义+Default不参与反例+方法级@Min、multipart解析+64KiB不拒(容器限制在mock外)、本controller深度0优先+advice problem+json、CORS实际放行回显/拒绝403+真实CorsProcessor预检、浅ETag强格式+304、会话传递+缺失400;MockMvc边界,无真实容器 |
run.sh async | proxy/context/failures/cancel四个独立进程;具名io/cpu池隔离与self不切线程,原始池泄漏反例、A/空/B及异常恢复previous,Future/void/非法返回类型/饱和拒绝精确错误通道,指定Future的合作取消 |
run.sh security | 独立Maven模块;真实FilterChainProxy/Basic认证/Provider/授权/异常翻译/上下文清理;匿名与错密码401、无权限403、reader200、随后匿名401、业务异常后无身份残留 |
以上6个命令场景本批均已实跑;all含42条PASS。U21 schedule 三模式见 evidence/u21-2026-10-25.json(红绿:周期任务过载饿死后继场景、CGLIB代理壳字段不共享、ConcurrentMapCache默认缓存null)。 U20 client 三模式见 evidence/u20-2026-10-25.json(开发期红绿:onStatus须抛出而非返回、JDK工厂readTimeout为请求级超时且异常两种形态、装饰器SessionLimitExceededException为RuntimeException且关闭责任在调用方、DROP需超限字节数)。 U19 mvc 七模式(14条PASS行)见 evidence/u19-2026-10-25.json:开发期修正三处——406实为映射阶段拒绝(原假设handler已执行)、ShallowEtagHeaderFilter默认强格式非弱格式、@SessionAttribute缺失抛ServletRequestBindingException(6.1.12实测)而非子类异常。未测:容器ERROR分派页、真实容器multipart限制、网关CORS覆盖、浏览器预检缓存、Jackson3。Future/闩锁/队列轮询及显式池终结等待均3秒,失败路径释放闩锁、关闭上下文/池并清调用线程状态。cancel仅证明所选合作任务执行到其finally,不把exited闩锁当作任意后台副作用已结束。
未测真实Servlet容器的dispatcher映射、ERROR/timeout/断连、TLS、响应已提交后安全异常、所有Security默认保护、真实JDBC/HTTP中断、OAuth2/CSRF/CORS/Session完整策略及跨进程恢复。standalone MockMvc调用真实MVC和安全过滤器,但mock请求/响应及显式asyncDispatch不验证容器网络行为。
权威正文分别为Web分发、异步与上下文、安全链。阶段复盘保留十二题;站点从自测回到原文后可点可见的八篇权威索引,答案面板内Markdown链接不当作已可点击。
T13 输入、预期与实测
本批实际 Java 为 JAVA_HOME=$(/usr/libexec/java_home -v17) 选出的 Liberica OpenJDK 17.0.19+11-LTS;教材来源 Framework v6.1.12 / Boot v3.3.3 另行固定,不声称本机 JDK 就是某个源码标签构建。未修改 pom 或依赖版本。下面的 PASS 来自真实回调、引用比较、异常及计数断言,不是预计日志。
| 公开入口 | 输入与预期 | 本批实际结果 |
|---|---|---|
run.sh lifecycle | normal/fail;容器发现BFPP/BPP,属性填充/初始化,lazy首次取,prototype交付后调用方销毁 | 两模式通过;normal含bfpp→bpp.construct→construct→populate→aware→before→init→after;fail没有after/destroy但Resource关闭 |
run.sh circle | setter成功且早期/最终同一代理、ping拦截一次;constructor/forbidden/late-wrapper/prototype失败 | 五模式通过;异常链必须含创建环,晚包装额外核对身份冲突;原型环首次getBean触发 |
run.sh proxy | JDK/CGLIB类型、self/direct/proxy入口、短路目标不执行、异常finally反向退栈 | 六模式通过;短路A+ short A-;抛错A+ B+ inner B- A-且异常交给调用方 |
run.sh boot | 默认/用户/关闭/排除/用户+关闭/runner失败 | 六模式通过;成功refreshed started runner ready closed,失败无ready;详见Boot输入表 |
所有 Context 在正常或失败路径关闭。LifecycleLab不注册PostConstruct/PreDestroy注解处理器,不以本实验验证这些注解;request scope、清理自身抛错、强杀、混合环所有起点、任意第三方BPP、动态切点、基本类型null返回和final方法副作用未测。Boot 为 NONE,不测端口、TLS、异步预热和外部探针。现有事务五模式、MyBatis与Web模式仅作为回归,不表示完成T14/T15新教材。
真实源文件与复现入口
正文就近嵌入唯一实验源文件,可直接复制:
以下脚本是实际执行入口,不是第二份复制的实验实现。
#!/usr/bin/env bash
# Fixed-version teaching tests. No external database, ports, or user data.
set -euo pipefail
lab_root="$(cd "$(dirname "$0")" && pwd)"
lab_mvn="${MAVEN_CMD:-mvn}"
lab_group="${1:-all}"
case "$lab_group" in lifecycle|circle|proxy|candidate|config|aop|tx|sync|jdbc|jdbc-mysql|mybatis|web|mvc|client|schedule|async|infra|binding|security|core|boot|all) ;; *) echo 'usage: bash run.sh lifecycle|circle|proxy|candidate|config|aop|tx|sync|jdbc|jdbc-mysql|mybatis|web|mvc|client|schedule|async|infra|binding|security|core|boot|all' >&2; exit 2;; esac
run_case() {
"$lab_mvn" -q -f "$1" compile exec:java "-Dexec.mainClass=$2" "-Dexec.args=${3:-}"
}
if [[ "$lab_group" == lifecycle || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in normal fail; do run_case "$lab_root/pom.xml" study.spring.LifecycleLab "$lab_mode"; done
fi
if [[ "$lab_group" == circle || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in setter constructor forbidden late-wrapper prototype; do run_case "$lab_root/pom.xml" study.spring.CircleLab "$lab_mode"; done
fi
if [[ "$lab_group" == proxy || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in jdk cglib jdk-short cglib-short jdk-throw cglib-throw; do run_case "$lab_root/pom.xml" study.spring.ProxyLab "$lab_mode"; done
fi
if [[ "$lab_group" == candidate || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in ambiguity primary qualifier collection missing generics name-fallback prototype-inject; do run_case "$lab_root/pom.xml" study.spring.CandidateLab "$lab_mode"; done
fi
if [[ "$lab_group" == config || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in xml scan import scoped-proxy; do run_case "$lab_root/pom.xml" study.spring.ConfigLab "$lab_mode"; done
fi
if [[ "$lab_group" == infra || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in env profile resource events; do run_case "$lab_root/pom.xml" study.spring.InfraLab "$lab_mode"; done
fi
if [[ "$lab_group" == binding || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in conversion binding validator spel; do run_case "$lab_root/pom.xml" study.spring.BindingLab "$lab_mode"; done
fi
if [[ "$lab_group" == aop || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in advice-order binding aspect-order tx-layering self-call final-methods introduction; do run_case "$lab_root/pom.xml" study.spring.AopLab "$lab_mode"; done
# AspectJ load-time weaving needs a forked JVM: exec:java's child-first realm does not
# weave. The agent jar is the pom's aspectjweaver 1.9.22.1 resolved in the local repo.
u17_agent="$HOME/.m2/repository/org/aspectj/aspectjweaver/1.9.22.1/aspectjweaver-1.9.22.1.jar"
if [[ ! -f "$u17_agent" ]]; then "$lab_mvn" -q org.apache.maven.plugins:maven-dependency-plugin:3.7.1:get -Dartifact=org.aspectj:aspectjweaver:1.9.22.1; fi
u17_java="${JAVA_HOME:-}/bin/java"; [[ -x "$u17_java" ]] || u17_java="java"
run_case_weave() {
"$lab_mvn" -q -f "$lab_root/pom.xml" compile exec:exec -Dexec.executable="$u17_java" "-Dexec.args=-javaagent:$u17_agent -cp %classpath study.spring.AopLab $1"
}
run_case_weave weaving
fi
if [[ "$lab_group" == tx || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in required requires-new nested pool after-commit; do run_case "$lab_root/pom.xml" study.spring.TxLab "$lab_mode"; done
for lab_mode in external self checked checked-rollback future-failed future-late thread; do run_case "$lab_root/pom.xml" study.spring.TxLab "$lab_mode"; done
fi
if [[ "$lab_group" == sync || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in sync-phases sync-fail events programmatic propagation isolation savepoint suspension rollback-rules; do run_case "$lab_root/pom.xml" study.spring.TransactionLab "$lab_mode"; done
fi
if [[ "$lab_group" == jdbc || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in translate-h2 batch-h2 keys-h2 readonly-h2; do run_case "$lab_root/pom.xml" study.spring.JdbcLab "$lab_mode"; done
fi
if [[ "$lab_group" == jdbc-mysql ]]; then
# U18 real-MySQL contrast: an isolated throwaway server on a unique port/socket/data dir.
# Never touches any existing MySQL installation or data directory; everything is removed on exit.
u18_mysqld="${MYSQLD_BIN:-$(command -v mysqld || true)}"
if [[ -z "$u18_mysqld" || ! -x "$u18_mysqld" ]]; then
echo "jdbc-mysql requires a local mysqld binary (set MYSQLD_BIN); skipping is the caller's choice" >&2
exit 3
fi
u18_bin_dir="$(cd "$(dirname "$u18_mysqld")" && pwd)"
u18_basedir="$(dirname "$u18_bin_dir")"
u18_admin="$u18_bin_dir/mysqladmin"
u18_client="$u18_bin_dir/mysql"
u18_base="$(mktemp -d /tmp/u18-mysql.XXXXXX)"
u18_port=$((13318 + RANDOM % 2000))
while lsof -nP -iTCP:"$u18_port" -sTCP:LISTEN >/dev/null 2>&1; do u18_port=$((13318 + RANDOM % 2000)); done
u18_cleanup() {
"$u18_admin" --socket="$u18_base/u18.sock" -uroot shutdown >/dev/null 2>&1 || pkill -f "u18-mysql[.]X" >/dev/null 2>&1 || true
for _ in 1 2 3 4 5 6 7 8 9 10; do
"$u18_admin" --socket="$u18_base/u18.sock" -uroot ping >/dev/null 2>&1 || break
sleep 1
done
rm -rf "$u18_base"
}
trap u18_cleanup EXIT
"$u18_mysqld" --initialize-insecure --datadir="$u18_base/data" --basedir="$u18_basedir" --log-error="$u18_base/init.err"
"$u18_mysqld" --datadir="$u18_base/data" --basedir="$u18_basedir" --port="$u18_port" --socket="$u18_base/u18.sock" \
--bind-address=127.0.0.1 --skip-name-resolve --mysqlx=OFF --log-error="$u18_base/run.err" --daemonize
u18_ready=0
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do
if "$u18_admin" --socket="$u18_base/u18.sock" -uroot ping >/dev/null 2>&1; then u18_ready=1; break; fi
sleep 1
done
if [[ "$u18_ready" != 1 ]]; then echo "isolated mysqld failed to start; see $u18_base/run.err" >&2; exit 3; fi
"$u18_client" --socket="$u18_base/u18.sock" -uroot -e "CREATE USER 'u18'@'%' IDENTIFIED WITH caching_sha2_password BY 'u18pass'; CREATE DATABASE u18; GRANT ALL PRIVILEGES ON *.* TO 'u18'@'%'; FLUSH PRIVILEGES;"
export U18_MYSQL_URL="jdbc:mysql://127.0.0.1:$u18_port/u18?user=u18&password=u18pass&sslMode=DISABLED&allowPublicKeyRetrieval=true"
echo "u18 isolated mysql: port=$u18_port datadir=$u18_base/data"
for lab_mode in translate-mysql batch-mysql batch-mysql-rewrite keys-mysql dirtyread-mysql; do run_case "$lab_root/pom.xml" study.spring.JdbcLab "$lab_mode"; done
fi
if [[ "$lab_group" == mybatis || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in native spring batch cursor cache-failure; do run_case "$lab_root/pom.xml" study.spring.MyBatisLab "$lab_mode"; done
fi
if [[ "$lab_group" == web || "$lab_group" == core || "$lab_group" == all ]]; then
run_case "$lab_root/pom.xml" study.spring.WebLab
fi
if [[ "$lab_group" == mvc || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in negotiation validation multipart exception cors etag session; do run_case "$lab_root/pom.xml" study.spring.MvcLab "$lab_mode"; done
fi
if [[ "$lab_group" == client || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in http stomp ws; do run_case "$lab_root/pom.xml" study.spring.ClientLab "$lab_mode"; done
fi
if [[ "$lab_group" == schedule || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in scheduled shutdown cache; do run_case "$lab_root/pom.xml" study.spring.ScheduleCacheLab "$lab_mode"; done
fi
if [[ "$lab_group" == async || "$lab_group" == core || "$lab_group" == all ]]; then
for lab_mode in proxy context failures cancel; do run_case "$lab_root/pom.xml" study.spring.AsyncLab "$lab_mode"; done
fi
if [[ "$lab_group" == security || "$lab_group" == all ]]; then
run_case "$lab_root/security/pom.xml" study.security.SecurityLab
fi
if [[ "$lab_group" == boot || "$lab_group" == all ]]; then
run_case "$lab_root/boot/pom.xml" study.boot.BootLab
run_case "$lab_root/boot/pom.xml" study.boot.BootLab --lab.user=true
run_case "$lab_root/boot/pom.xml" study.boot.BootLab --study.notice.enabled=false
run_case "$lab_root/boot/pom.xml" study.boot.BootLab --spring.autoconfigure.exclude=study.boot.NoticeAutoConfiguration
run_case "$lab_root/boot/pom.xml" study.boot.BootLab '--lab.user=true --study.notice.enabled=false'
run_case "$lab_root/boot/pom.xml" study.boot.BootLab --lab.runner-fail=true
run_case "$lab_root/boot/pom.xml" study.boot.PackagingDiagLab apptype
# faildiag:启动失败期望成功退出,但诊断报告必须包含 Description/Action(真实 reporter 输出)
"$lab_mvn" -q -f "$lab_root/boot/pom.xml" compile exec:java -Dexec.mainClass=study.boot.PackagingDiagLab -Dexec.args=faildiag > "$lab_root/boot/target/u22-faildiag.log" 2>&1
grep -q "STARTUP FAILED AS EXPECTED" "$lab_root/boot/target/u22-faildiag.log" || { echo 'faildiag did not fail-as-expected' >&2; exit 1; }
grep -q "APPLICATION FAILED TO START" "$lab_root/boot/target/u22-faildiag.log" || { echo 'missing analysis report banner' >&2; exit 1; }
grep -q "Description:" "$lab_root/boot/target/u22-faildiag.log" || { echo 'missing Description' >&2; exit 1; }
grep -q "Action:" "$lab_root/boot/target/u22-faildiag.log" || { echo 'missing Action' >&2; exit 1; }
grep -q "RECOVERY token validated and context started" "$lab_root/boot/target/u22-faildiag.log" || { echo 'missing recovery verification' >&2; exit 1; }
echo "PASS u22 faildiag FailureAnalyzer report (Description/Action) + valid-token recovery"
# U22 打包链:真实 repackage 产物 + 分层清单 + 可执行运行 + 元数据断言(唯一 jar 名,跑完清理)
u22_java="${JAVA_HOME:-$(/usr/libexec/java_home -v 17)}"
u22_boot_jar="$lab_root/boot/target/boot-lab-1.jar"
"$lab_mvn" -q -f "$lab_root/boot/pom.xml" -DskipTests package
"$u22_java/bin/java" -Djarmode=layertools -jar "$u22_boot_jar" list > "$lab_root/boot/target/u22-layers.txt"
grep -q "^dependencies" "$lab_root/boot/target/u22-layers.txt" || { echo 'missing dependencies layer' >&2; exit 1; }
"$u22_java/bin/java" -Djarmode=layertools -jar "$u22_boot_jar" extract --destination "$lab_root/boot/target/u22-extracted" > /dev/null
test -f "$lab_root/boot/target/u22-extracted/dependencies/BOOT-INF/lib/spring-boot-3.3.3.jar" || { echo 'layered extract missing spring-boot jar' >&2; exit 1; }
test -f "$lab_root/boot/target/u22-extracted/spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class" || { echo 'loader layer missing JarLauncher' >&2; exit 1; }
test -f "$lab_root/boot/target/u22-extracted/application/BOOT-INF/classes/study/boot/BootLab.class" || { echo 'application layer missing classes' >&2; exit 1; }
"$u22_java/bin/java" -jar "$u22_boot_jar" | grep -q "PASS Boot runnerFail=false" || { echo 'executable jar run failed' >&2; exit 1; }
test -f "$lab_root/boot/target/classes/META-INF/spring-configuration-metadata.json" || { echo 'configuration metadata not generated' >&2; exit 1; }
grep -q '"study.notice.enabled"' "$lab_root/boot/target/classes/META-INF/spring-configuration-metadata.json" || { echo 'metadata missing study.notice.enabled' >&2; exit 1; }
rm -rf "$lab_root/boot/target/u22-extracted"
echo "PASS u22 packaging: layertools list/extract + executable-jar run + configuration metadata"
fi脚本遇到任一断言失败即停止并返回非零,不把后续模式未执行算作通过。