Spring Boot 和 Kotlin DSL 配置

Spring Boot and Kotlin DSL Configuration

提问人:Olgun Kaya 提问时间:6/13/2023 最后编辑:Abdulrahman HasanatoOlgun Kaya 更新时间:6/23/2023 访问量:387

问:

最近,我被分配到一个项目,该项目禁用了一些自动配置,并主要使用 KotlinDSL 手动配置 spring boot 应用程序。

@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        JpaRepositoriesAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class,
        CassandraDataAutoConfiguration.class,
        CassandraAutoConfiguration.class
})

我面临着我相信 Kotlin lang 与 spring 集成的问题。

让我向您展示设置。

  1. 包含@Transactional方法的抽象登录策略。
  2. 上述抽象类的具体子类。IndividualSignIn,支持两种不同的实现。这些是 Google 和 Apple 个人登录。区别制造者是注入到上述具体类 Bean 中的服务(Google 登录服务、Apple 登录服务)。我将在下面展示设置。

因此,Kotlin dsl 如下所示;

bean(name = "googleUserSignIn") {
            IndividualUserSignIn(
                ref("googleUserSignInService"),
                ref("userHibernateDAO"),
                ref("socialAccountHibernateDAO"),
                ref("userService"),
                ...
            )
        }

bean(name = "appleUserSignIn") {
            IndividualUserSignIn(
                ref("appleUserSignInService"),
                ref("userHibernateDAO"),
                ref("socialAccountHibernateDAO"),
                ref("userService"),
                ...
            )
        }

最后,代理请求的策略如下;

bean<UserSignInFactory>()

这些类的实现如下所示; 首先,AbstractStrategy

abstract class AbstractUserSignIn(
    private val userSignInService: UserSignInService,
    private val userDAO: UserDAO,
    private val socialAccountDAO: SocialAccountDAO,
    private val userService: UserService,
    ....
) {

    @Transactional
    open fun signIn(userSignInRequest: SignInRequest): SignInResult {...}
    
    fun getSignInStrategy(): UserSignInStrategy{ // **(A)**
       return userSignInService.getSignInStrategy()
    }
}

然后是从这个类继承的类;

open class IndividualUserSignIn constructor(
    userSignInService: UserSignInService,
    userDAO: UserDAO,
    socialAccountDAO: SocialAccountDAO,
    userService: UserService,
    ...
) : AbstractUserSignIn(
    userSignInService,
    userDAO,
    socialAccountDAO,
    userService,
    ...
) {

    @PostConstruct
    private fun init()
    {
        println("strategy :" + getSignInStrategy()) // **(B)**
    }
...
}

和 Factory 类。

@Component
open class UserSignInFactory @Autowired constructor(private val userSignInServices: Set<IndividualUserSignIn>) {

    @PostConstruct
    private fun createStrategies() {
        userSignInServices.forEach { strategy ->
            strategyMap[strategy.getSignInStrategy()] = strategy // **(C)**
        }
    }
    ....
    companion object {
        private val strategyMap: EnumMap<UserSignInStrategy, AbstractUserSignIn> = EnumMap(UserSignInStrategy::class.java)
    }
}

点(A)是问题出现的地方。抽象类使用注入的服务让调用方了解其支持实现。

好吧,问题来了。

  1. 在(B)点;在实例化具体策略时,@PostConstruct将按预期工作并打印支持的策略。调试说这是策略本身的实例。
  2. 在(C)点;在遍历 Set 时,我收到 NPE,因为在点 (A) 中使用的注入服务看起来为 null。在这里,集合中的元素是 Spring 生成的代理的实例,指向上面步骤 #1 中的实例。
java spring-boot kotlin 依赖注入 nullpointerexception

评论

1赞 AndrewL 6/19/2023
感觉像是订购的问题......你可能需要明确地告诉 Spring 应该首先实例化哪些 bean。我尝试使用 Spring 注解而不是 DSL 方法进行重现,它对我有用。您能分享一下您如何为以这种方式创建的任何 Bean/组件调用 Kotlin DSL 吗?
0赞 Olgun Kaya 6/19/2023
@AndrewL感谢您的评论,但是,如果您检查摘要/问题定义部分中的项目#1。正在为单个登录策略正确注入服务。而且,在我的测试中,我看到工厂在策略实例化后实例化。我仍然在 Kotlin dsl 的行为附近。但是,谢谢,根据您质疑豆子是如何实例化的;我们正在为包括 bean、config 等在内的所有内容注册 ApplicationContextInitializer。
2赞 djmonki 6/19/2023
你有没有考虑过将具体策略定义为懒惰的豆子?因此,将它们包装成:.然后更新 UserSignInFactory 以适应延迟初始化为: .因此,在工厂访问策略之前,会使用注入的正确依赖项来实例化策略。只是一个想法。bean(name = "googleUserSignIn") { lazy { IndividualUserSignIn( ........ ) } }(private val userSignInServices: Lazy<Set<IndividualUserSignIn>>)
0赞 Olgun Kaya 6/19/2023
@djmonki从未想过,但肯定会尝试一下
0赞 Olgun Kaya 6/21/2023
@djmonki我尝试了您的评论并且奏效了。懒惰的豆子似乎起作用了。至少在这一点上是这样,但是当我检查注入的豆子时,它们不是代理。这让我怀疑@Transactional会起作用。不过,如果您将您的评论作为答案,我会接受它。

答:

-3赞 mahdi hashemi 6/23/2023 #1

似乎问题与Spring bean的初始化顺序有关。具体而言,UserSignInFactory bean 在 IndividualUserSignInService 的 Bean 完全初始化之前就已初始化,这导致在调用 getSignInStrategy() 时 AbstractUserSignIn 中的 userSignInService 字段为 null。 这可以使用 Kotlin DSL 中 bean 方法的 depends-on 属性来完成:

@DependsOn("googleUserSignInService")
bean(name = "googleUserSignIn") {
    IndividualUserSignIn(
        ref("googleUserSignInService"),
        ref("userHibernateDAO"),
        ref("socialAccountHibernateDAO"),
        ref("userService")
    )
}

@DependsOn("appleUserSignInService")
bean(name = "appleUserSignIn") {
    IndividualUserSignIn(
        ref("appleUserSignInService"),
        ref("userHibernateDAO"),
        ref("socialAccountHibernateDAO"),
        ref("userService"),
        ...
    )
}

是的,dependsOn 属性确实不存在,我从一个似乎错误的代码中获取了这个示例 我使用@DependsOn注释进行更正

评论

0赞 Olgun Kaya 6/23/2023
您确定存在 dependsOn 属性吗?
2赞 DavidW 6/25/2023
看起来像 ChatGPT。它可能组成了 dependsOn 属性
1赞 djmonki 6/23/2023 #2

考虑将具体策略定义为懒惰的 bean。

这将确保在工厂访问策略之前,使用注入的正确依赖项来实例化策略。

更新为惰性 bean:

bean(name = "googleUserSignIn") {
  lazy {
    IndividualUserSignIn(
      ref("googleUserSignInService"),
      ref("userHibernateDAO"),
      ref("socialAccountHibernateDAO"),
      ref("userService"),
      ...
    )
  }
}

bean(name = "appleUserSignIn") {
  lazy {
    IndividualUserSignIn(
      ref("appleUserSignInService"),
      ref("userHibernateDAO"),
      ref("socialAccountHibernateDAO"),
      ref("userService"),
      ...
    )
  }
}

然后更新 UserSignInFactory 类以适应延迟初始化:

@Component
open class UserSignInFactory @Autowired constructor(
  private val userSignInServices: Lazy<Set<IndividualUserSignIn>>) {

  @PostConstruct
  private fun createStrategies() {
    userSignInServices.forEach { strategy ->
      strategyMap[strategy.getSignInStrategy()] = strategy // **(C)**
    }
  }
  ....
  companion object {
    private val strategyMap: 
    EnumMap<UserSignInStrategy, AbstractUserSignIn> = EnumMap(UserSignInStrategy::class.java)
  }
}

评论

0赞 Olgun Kaya 6/23/2023
虽然这似乎有点棘手,但仍然是解决问题的一种方式。我接受这个作为答案。