最近给一个使用 Spring 框架的项目写测试的时候发现, 如果测试使用 H2 embedded 数据库, 再创建 Bean 的时候总是会失败. 失败的原因是因为某一个 Bean 在创建的时候会从数据库中读一部分静态数据到内存, 而从 H2 读的时候会提示找不到对应的表.

对 Spring 有了解的朋友们一定会问, 你是不是没有加初始化 SQL 代码呀 (e.g. resources 下的 schema.sql)? 成年人的世界总是事与愿违. 我的第一反应也是这个, 可惜事情没有那么简单, schema.sql 好好的放在了 resources 目录下.

那么到底原因出在哪里了呢? 我稍微读了一下代码, 发现执行初始化 SQL 的代码放在了 ConnectionFactoryInitializer这个类中, 并由 ConnectionFactoryInitializerInvoker 负责调用. 然后我就加了几个断点, 发现 ConnectionFactoryInitializerInvoker 这个 Bean 根本没有被创建.

查到这里的时候, 我有点误入歧途了. 我当时以为是因为在初始化这个 Bean 之前, 有其他的 Bean 初始化失败了. 结果查了半天都没找到问题. 实际上是因为 Bean 初始化的顺序不正确. Spring 先初始化了上面提到的会读数据库的 Bean, 然后才会执行 SQL. 最后解决的方法也比较简单, 就是将读数据这一步推迟到 Bean 全部初始化完成之后.

其实我觉得 Spring 本身应该能意识到这个问题. 很显然上面提到的初始化失败的 Bean 直接或间接地依赖于 Spring Repository. 而 Repository 逻辑上又依赖于这个 ConnectionFactoryInitializer. 那么在初始化的时候很显然就应该先初始化 ConnectionFactoryInitializer.