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.
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.
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)
}
}