英文:
Spring Boot and Kotlin DSL Configuration
问题
最近,我被分配到一个项目中,该项目禁用了一些自动配置,大部分使用KotlinDSL手动配置Spring Boot应用程序。
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
CassandraDataAutoConfiguration.class,
CassandraAutoConfiguration.class
})
我认为我在Spring集成中遇到了Kotlin语言的问题。
让我展示一下设置。
- 一个带有@Transactional方法的抽象登录策略。
- 上述抽象类的具体子类。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)**
}
...
}
以及工厂类。
@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)。抽象类使用注入的服务来让调用者知道其支持的实现。
问题在于:
- 在点(B):在具体策略被实例化时,@PostConstruct按预期工作并打印支持的策略。调试显示这是策略本身的实例。
- 在点(C):遍历Set时,我收到了一个NPE,因为在点(A)中使用的注入服务看起来为空。这里的set中的元素是从步骤#1生成的Spring代理实例,指向上述实例。
英文:
Lately, I have been assigned to a project that disables some of the auto-config and configures the spring boot app mostly manually using KotlinDSL.
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
CassandraDataAutoConfiguration.class,
CassandraAutoConfiguration.class
})
I am facing I believe Kotlin lang issue with spring integration.
Let me show you the setup.
- An abstract sign-in strategy with a @Transactional method in it.
- A concrete child of the above abstract class. IndividualSignIn with supporting two different implementations. Those are Google and Apple individual sign-ins. The difference maker is the service (Google sign-in service, Apple sign-in service) which is injected into the above concrete class beans. I'll show the setup below.
So, the Kotlin dsl is like the below;
bean(name = "googleUserSignIn") {
IndividualUserSignIn(
ref("googleUserSignInService"),
ref("userHibernateDAO"),
ref("socialAccountHibernateDAO"),
ref("userService"),
...
)
}
bean(name = "appleUserSignIn") {
IndividualUserSignIn(
ref("appleUserSignInService"),
ref("userHibernateDAO"),
ref("socialAccountHibernateDAO"),
ref("userService"),
...
)
}
And finally, the strategy which proxies the request is like below;
bean<UserSignInFactory>()
The implementation of these classes are look like below;
first the 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()
}
}
Then the classes inherited from this class;
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)**
}
...
}
and the Factory class.
@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)
}
}
Point (A) is where the problem arises. The abstract class uses the injected service to let callers know about its supporting implementation.
Well, here the problem is.
- At point (B); While the concrete strategies are being instantiated, the @PostConstruct works as expected and prints the supported strategy. Debugging says this is the instance itself of strategy.
- At point (C); While traversing the Set, I am receiving an NPE because the injected service that is used in point (A) looks null. Here the elements in the set, are instances of spring-generated proxies pointing to instances from step #1 above.
答案1
得分: 1
考虑将具体策略定义为懒加载的 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)
}
}
请注意,上述代码中的注释 (C)
是为了表示代码中的某个特定位置,无需翻译。
英文:
Consider defining the concrete strategies as lazy beans.
This will ensure that the strategies are instantiated with the correct dependencies injected before they are accessed in the factory.
Updated as lazy beans:
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"),
...
)
}
}
Then update UserSignInFactory class to accommodate lazy initialisation:
@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)
}
}
答案2
得分: -3
以下是已翻译的内容:
似乎问题与Spring bean的初始化顺序有关。特别是,在完全初始化IndividualUserSignInService的bean之前,UserSignInFactory 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注解进行了更正。
英文:
it seems that the issue is related to the initialization order of the Spring beans. In particular, the UserSignInFactory bean is being initialized before the beans for IndividualUserSignInService are fully initialized, which is causing the userSignInService field in AbstractUserSignIn to be null when getSignInStrategy() is called.
This can be done using the depends-on attribute of the bean method in Kotlin DSL:
@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"),
...
)
}
Yes, it is true that the dependsOn property does not exist, and I took this example from a code that seemed wrong
I used @DependsOn annotation for correction
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论