Spring FactoryBean 初始化问题

Author Avatar
呃哦 8月 25, 2020

前提

项目使用了 EBean 框架作为数据库依赖管理,通过 FactoryBean 接口,实现了 Ebean Server 的配置初始化操作。

@Component
@Qualifier("defaultDatabase")
@Primary
class EbeanDatabaseFactory(
        private val primaryDataSource: DataSource
) : FactoryBean<Database> {
  override fun getObject(): Database? =
          DatabaseConfig()
                  .apply {
                    dataSource = primaryDataSource
                    isDdlRun = false
                    isDdlGenerate = false
                    isDefaultServer = true
                    namingConvention = UnderscoreNamingConvention()
                    name = "defaultDatabase"
                  }
                  .let(DatabaseFactory::create)

  override fun getObjectType(): Class<*>? = Database::class.java

  override fun isSingleton(): Boolean = true

问题描述

在 ORM 层,通过获取 Database 对象做数据库操作

fun poloDatabase(): Database {
  return DB.byName("defaultDatabase")
}

出现报错:

Caused by: io.ebean.datasource.DataSourceConfigurationException: Configuration error creating DataSource for the default Database. This typically means a missing application-test.yaml or missing ebean-test-config dependency. See https://ebean.io/docs/trouble-shooting#datasource
    at io.ebean.Ebean$ServerManager.<init>(Ebean.java:87)
    at io.ebean.Ebean$ServerManager.<init>(Ebean.java:50)
    at io.ebean.Ebean.<clinit>(Ebean.java:45)
    ... 70 common frames omitted
Caused by: io.ebean.datasource.DataSourceConfigurationException: DataSource user is null?
    at io.ebean.datasource.pool.ConnectionPool.<init>(ConnectionPool.java:220)
    at io.ebean.datasource.core.Factory.createPool(Factory.java:15)
    at io.ebeaninternal.server.core.DefaultContainer.getDataSourceFromConfig(DefaultContainer.java:290)
    at io.ebeaninternal.server.core.DefaultContainer.setDataSource(DefaultContainer.java:234)
    at io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:100)
    at io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:70)
    at io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:36)
    at io.ebean.EbeanServerFactory.create(EbeanServerFactory.java:58)
    at io.ebean.Ebean$ServerManager.getWithCreate(Ebean.java:128)
    at io.ebean.Ebean$ServerManager.<init>(Ebean.java:77)
    ... 72 common frames omitted

问题排查

FactoryBean.getObject 打断点排查没有执行到该方法,既 数据库没有初始化

怀疑是生命周期问题,导致没有注入依赖。

查找官方 spring 文档,发现以下说明

A standard FactoryBean is not expected to initialize eagerly: Its FactoryBean.getObject() will only be called for actual access, even in case of a singleton object.

即 FactoryBean.getObject 需要显式调用。

为何之前没有这个问题

之前在代码中显式要求依赖注入了

@Component
class UserArgumentResolver(
        @Qualifier("defaultDatabase") private var poloDatabase: Database
): HandlerMethodArgumentResolver {

问题解决

显式调用,或者显式依赖注入,在这里感觉有点多余。

FactoryBean 下有个子接口,SmartFactoryBean 实现了 eagerInit 。

因此,数据库 Bean 改为实现该接口。

@Component
@Qualifier("defaultDatabase")
@Primary
class EbeanDatabaseFactory(
        private val primaryDataSource: DataSource
) : SmartFactoryBean<Database> {
  override fun getObject(): Database? =
          DatabaseConfig()
                  .apply {
                    dataSource = primaryDataSource
                    isDdlRun = false
                    isDdlGenerate = false
                    isDefaultServer = true
                    namingConvention = UnderscoreNamingConvention()
                    name = "defaultDatabase"
                  }
                  .let(DatabaseFactory::create)

  override fun getObjectType(): Class<*>? = Database::class.java

  override fun isSingleton(): Boolean = true

  override fun isPrototype(): Boolean = true

  override fun isEagerInit(): Boolean = true
}