I’m trying to do a generic factory to instantiate a lot of kotlin class of the same interface.
interface Rule {
fun execute()
}
@Component
class Rule2755 : Rule {
@Autowired
lateinit var repo: TestRepository
override fun execute() {
val test1 = repo.findById(341L)
}
}
@RestController
class TestController(val rule2755 : Rule2755) {
@GetMapping("/test1/rule2755")
fun a1() {
val kclass = Class.forName("Rule2755").kotlin
val instance = kclass.createInstance() as Rule
instance.execute() // this doesn't work!
// kotlin.UninitializedPropertyAccessException: lateinit property repo has not been initialized
}
@GetMapping("/test2/rule2755")
fun a2() {
rule2755.execute() // this works!
}
}
Is there a way to force the “createInstance” to initialize the dependency injection?
In order to do its magic, Spring needs to be in charge of things like app startup, creation of all Spring objects, etc. So you’re unlikely to be able to get it to work like that (without a serious amount of work trying to recreate most of what Spring does, which will be undocumented, fragile, error-prone, and tied to the specific version of the Spring libraries).
Also, Spring components generally have a static lifetime: they’re created during app startup, and hang around until it shuts down. Again, fighting that is likely to be non-productive.
If you really need to create new objects (and it’s not clear from your code why you would), then I’d suggest not making them Spring components themselves, but instead passing a relevant Spring class (possibly your TestRepository instance) into their constructor, so that they have access to Spring stuff via that.
Yeah basically what gidds said. What you’re trying to do is the sort of thing you could do with Guice, but I’m not sure why you want to do it. Why do you want to use reflection to create instances of classes in the function, instead of just letting Spring inject them into the class when the class is created?
As others said, we can’t create beans manually, we get them from Spring, so it can initialize them. However, Spring has some support for getting beans dynamically. You can try this code:
@RestController
class TestController(val ctx: ApplicationContext) {
@GetMapping("/test1/rule2755")
fun a1() {
val rule = ctx.getBean("rule2755") as Rule
// or:
val rule = ctx.getBean(Class.forName("Rule2755")) as Rule
}
}
However, it feels strange to use dependency injection like this. If you have say tens of such rules and they are initialized in a similar way, it could make sense to not make them beans, but create your own “rule registry” and get them by name.